|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH 03/14] AMD/IOMMU: Fix multiple reference counting errors
On 11/21/18 7:21 AM, Andrew Cooper wrote:
> Most of these issues would be XSAs if these paths were accessible to guests.
>
> First, override the {get,put}_gfn() helpers to use gfn_t, which was the
> original purpose of this patch.
>
> guest_iommu_get_table_mfn() has two bugs. First, it gets a ref on one gfn,
> and puts a ref for a different gfn. This is only a latent bug for now, as we
> don't do per-gfn locking yet. Next, the mfn return value is unsafe to use
> after put_gfn() is called, as the guest could have freed the page in the
> meantime.
>
> In addition, get_gfn_from_base_reg() erroneously asserts that base_raw can't
> be 0, but it may legitimately be. On top of that, the return value from
> guest_iommu_get_table_mfn() is passed into map_domain_page() before checking
> that it is a real mfn.
>
> Most of the complexity here is inlining guest_iommu_get_table_mfn() and
> holding the gfn reference until the operation is complete.
>
> Furthermore, guest_iommu_process_command() is altered to take a local copy of
> cmd_entry_t, rather than passing a pointer to guest controlled memory into
> each of the handling functions. It is also modified to break on error rather
> than continue. These changes are in line with the spec which states that the
> IOMMU will strictly read a command entry once, and will cease processing if an
> error is encountered.
>
> Signed-off-by: Andrew Cooper <andrew.cooper3@xxxxxxxxxx>
Acked-by: Brian Woods <brian.woods@xxxxxxx>
> ---
> CC: Jan Beulich <JBeulich@xxxxxxxx>
> CC: Wei Liu <wei.liu2@xxxxxxxxxx>
> CC: Roger Pau Monné <roger.pau@xxxxxxxxxx>
> CC: Suravee Suthikulpanit <suravee.suthikulpanit@xxxxxxx>
> CC: Brian Woods <brian.woods@xxxxxxx>
>
> This patch my no means indicates that the code is ready for production use.
> ---
> xen/drivers/passthrough/amd/iommu_guest.c | 224
> +++++++++++++++++++-----------
> 1 file changed, 146 insertions(+), 78 deletions(-)
>
> diff --git a/xen/drivers/passthrough/amd/iommu_guest.c
> b/xen/drivers/passthrough/amd/iommu_guest.c
> index 96175bb..03ca0cf 100644
> --- a/xen/drivers/passthrough/amd/iommu_guest.c
> +++ b/xen/drivers/passthrough/amd/iommu_guest.c
> @@ -21,6 +21,13 @@
> #include <asm/amd-iommu.h>
> #include <asm/hvm/svm/amd-iommu-proto.h>
>
> +/* Override {get,put}_gfn to work with gfn_t */
> +#undef get_gfn
> +#define get_gfn(d, g, t) get_gfn_type(d, gfn_x(g), t, P2M_ALLOC)
> +#undef get_gfn_query
> +#define get_gfn_query(d, g, t) get_gfn_type(d, gfn_x(g), t, 0)
> +#undef put_gfn
> +#define put_gfn(d, g) __put_gfn(p2m_get_hostp2m(d), gfn_x(g))
>
> #define IOMMU_MMIO_SIZE 0x8000
> #define IOMMU_MMIO_PAGE_NR 0x8
> @@ -117,13 +124,6 @@ static unsigned int host_domid(struct domain *d,
> uint64_t g_domid)
> return d->domain_id;
> }
>
> -static unsigned long get_gfn_from_base_reg(uint64_t base_raw)
> -{
> - base_raw &= PADDR_MASK;
> - ASSERT ( base_raw != 0 );
> - return base_raw >> PAGE_SHIFT;
> -}
> -
> static void guest_iommu_deliver_msi(struct domain *d)
> {
> uint8_t vector, dest, dest_mode, delivery_mode, trig_mode;
> @@ -138,23 +138,6 @@ static void guest_iommu_deliver_msi(struct domain *d)
> vmsi_deliver(d, vector, dest, dest_mode, delivery_mode, trig_mode);
> }
>
> -static unsigned long guest_iommu_get_table_mfn(struct domain *d,
> - uint64_t base_raw,
> - unsigned int entry_size,
> - unsigned int pos)
> -{
> - unsigned long idx, gfn, mfn;
> - p2m_type_t p2mt;
> -
> - gfn = get_gfn_from_base_reg(base_raw);
> - idx = (pos * entry_size) >> PAGE_SHIFT;
> -
> - mfn = mfn_x(get_gfn(d, gfn + idx, &p2mt));
> - put_gfn(d, gfn);
> -
> - return mfn;
> -}
> -
> static void guest_iommu_enable_dev_table(struct guest_iommu *iommu)
> {
> uint32_t length_raw =
> get_field_from_reg_u32(iommu->dev_table.reg_base.lo,
> @@ -176,7 +159,10 @@ static void guest_iommu_enable_ring_buffer(struct
> guest_iommu *iommu,
> void guest_iommu_add_ppr_log(struct domain *d, u32 entry[])
> {
> uint16_t gdev_id;
> - unsigned long mfn, tail, head;
> + unsigned long tail, head;
> + mfn_t mfn;
> + gfn_t gfn;
> + p2m_type_t p2mt;
> ppr_entry_t *log, *log_base;
> struct guest_iommu *iommu;
>
> @@ -197,11 +183,24 @@ void guest_iommu_add_ppr_log(struct domain *d, u32
> entry[])
> return;
> }
>
> - mfn = guest_iommu_get_table_mfn(d, reg_to_u64(iommu->ppr_log.reg_base),
> - sizeof(ppr_entry_t), tail);
> - ASSERT(mfn_valid(_mfn(mfn)));
> + gfn = _gfn(PFN_DOWN(reg_to_u64(iommu->ppr_log.reg_base)) +
> + PFN_DOWN(tail * sizeof(*log)));
>
> - log_base = map_domain_page(_mfn(mfn));
> + mfn = get_gfn(d, gfn, &p2mt);
> + if ( mfn_eq(mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + AMD_IOMMU_DEBUG(
> + "Error: guest iommu ppr log bad gfn %"PRI_gfn", type %u, mfn %"
> + PRI_mfn", reg_base %#"PRIx64", tail %#lx\n",
> + gfn_x(gfn), p2mt, mfn_x(mfn),
> + reg_to_u64(iommu->ppr_log.reg_base), tail);
> + guest_iommu_disable(iommu);
> + goto out;
> + }
> +
> + ASSERT(mfn_valid(mfn));
> +
> + log_base = map_domain_page(mfn);
> log = log_base + tail % (PAGE_SIZE / sizeof(ppr_entry_t));
>
> /* Convert physical device id back into virtual device id */
> @@ -220,12 +219,18 @@ void guest_iommu_add_ppr_log(struct domain *d, u32
> entry[])
> unmap_domain_page(log_base);
>
> guest_iommu_deliver_msi(d);
> +
> +out:
> + put_gfn(d, gfn);
> }
>
> void guest_iommu_add_event_log(struct domain *d, u32 entry[])
> {
> uint16_t dev_id;
> - unsigned long mfn, tail, head;
> + unsigned long tail, head;
> + mfn_t mfn;
> + gfn_t gfn;
> + p2m_type_t p2mt;
> event_entry_t *log, *log_base;
> struct guest_iommu *iommu;
>
> @@ -246,11 +251,24 @@ void guest_iommu_add_event_log(struct domain *d, u32
> entry[])
> return;
> }
>
> - mfn = guest_iommu_get_table_mfn(d, reg_to_u64(iommu->event_log.reg_base),
> - sizeof(event_entry_t), tail);
> - ASSERT(mfn_valid(_mfn(mfn)));
> + gfn = _gfn(PFN_DOWN(reg_to_u64(iommu->event_log.reg_base)) +
> + PFN_DOWN(tail * sizeof(*log)));
> +
> + mfn = get_gfn(d, gfn, &p2mt);
> + if ( mfn_eq(mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + AMD_IOMMU_DEBUG(
> + "Error: guest iommu event log bad gfn %"PRI_gfn", type %u, mfn %"
> + PRI_mfn", reg_base %#"PRIx64", tail %#lx\n",
> + gfn_x(gfn), p2mt, mfn_x(mfn),
> + reg_to_u64(iommu->ppr_log.reg_base), tail);
> + guest_iommu_disable(iommu);
> + goto out;
> + }
> +
> + ASSERT(mfn_valid(mfn));
>
> - log_base = map_domain_page(_mfn(mfn));
> + log_base = map_domain_page(mfn);
> log = log_base + tail % (PAGE_SIZE / sizeof(event_entry_t));
>
> /* re-write physical device id into virtual device id */
> @@ -269,6 +287,9 @@ void guest_iommu_add_event_log(struct domain *d, u32
> entry[])
> unmap_domain_page(log_base);
>
> guest_iommu_deliver_msi(d);
> +
> +out:
> + put_gfn(d, gfn);
> }
>
> static int do_complete_ppr_request(struct domain *d, cmd_entry_t *cmd)
> @@ -346,10 +367,8 @@ static int do_invalidate_iotlb_pages(struct domain *d,
> cmd_entry_t *cmd)
>
> static int do_completion_wait(struct domain *d, cmd_entry_t *cmd)
> {
> - bool_t com_wait_int_en, com_wait_int, i, s;
> + bool com_wait_int_en, com_wait_int, i, s;
> struct guest_iommu *iommu;
> - unsigned long gfn;
> - p2m_type_t p2mt;
>
> iommu = domain_iommu(d);
>
> @@ -362,7 +381,10 @@ static int do_completion_wait(struct domain *d,
> cmd_entry_t *cmd)
> if ( s )
> {
> uint64_t gaddr_lo, gaddr_hi, gaddr_64, data;
> - void *vaddr;
> + mfn_t mfn;
> + gfn_t gfn;
> + p2m_type_t p2mt;
> + uint64_t *ptr;
>
> data = (uint64_t)cmd->data[3] << 32 | cmd->data[2];
> gaddr_lo = get_field_from_reg_u32(cmd->data[0],
> @@ -374,13 +396,24 @@ static int do_completion_wait(struct domain *d,
> cmd_entry_t *cmd)
>
> gaddr_64 = (gaddr_hi << 32) | (gaddr_lo << 3);
>
> - gfn = gaddr_64 >> PAGE_SHIFT;
> - vaddr = map_domain_page(get_gfn(d, gfn ,&p2mt));
> - put_gfn(d, gfn);
> + gfn = _gfn(gaddr_64 >> PAGE_SHIFT);
> + mfn = get_gfn(d, gfn, &p2mt);
>
> - write_u64_atomic((uint64_t *)(vaddr + (gaddr_64 & (PAGE_SIZE-1))),
> - data);
> - unmap_domain_page(vaddr);
> + if ( mfn_eq(mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + /* XXX - What to do here, error wise? */
> + guest_iommu_disable(iommu);
> + put_gfn(d, gfn);
> +
> + return 0;
> + }
> +
> + ptr = map_domain_page(mfn) + (gaddr_64 & ~PAGE_MASK);
> +
> + write_u64_atomic(ptr, data);
> + unmap_domain_page(ptr);
> +
> + put_gfn(d, gfn);
> }
>
> com_wait_int_en = iommu_get_bit(iommu->reg_ctrl.lo,
> @@ -400,9 +433,10 @@ static int do_invalidate_dte(struct domain *d,
> cmd_entry_t *cmd)
> dev_entry_t *gdte, *mdte, *dte_base;
> struct amd_iommu *iommu = NULL;
> struct guest_iommu *g_iommu;
> - uint64_t gcr3_gfn, gcr3_mfn;
> + mfn_t dte_mfn, gcr3_mfn;
> + gfn_t dte_gfn, gcr3_gfn;
> uint8_t glx, gv;
> - unsigned long dte_mfn, flags;
> + unsigned long flags;
> p2m_type_t p2mt;
>
> g_iommu = domain_iommu(d);
> @@ -417,35 +451,49 @@ static int do_invalidate_dte(struct domain *d,
> cmd_entry_t *cmd)
> if ( (gbdf * sizeof(dev_entry_t)) > g_iommu->dev_table.size )
> return 0;
>
> - dte_mfn = guest_iommu_get_table_mfn(d,
> -
> reg_to_u64(g_iommu->dev_table.reg_base),
> - sizeof(dev_entry_t), gbdf);
> - ASSERT(mfn_valid(_mfn(dte_mfn)));
> + dte_gfn = _gfn(PFN_DOWN(reg_to_u64(g_iommu->dev_table.reg_base)) +
> + PFN_DOWN(gbdf * sizeof(*gdte)));
> + dte_mfn = get_gfn(d, dte_gfn, &p2mt);
> +
> + if ( mfn_eq(dte_mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + put_gfn(d, dte_gfn);
> + return 0;
> + }
> +
> + ASSERT(mfn_valid(dte_mfn));
>
> /* Read guest dte information */
> - dte_base = map_domain_page(_mfn(dte_mfn));
> + dte_base = map_domain_page(dte_mfn);
>
> gdte = dte_base + gbdf % (PAGE_SIZE / sizeof(dev_entry_t));
>
> gdom_id = get_domid_from_dte(gdte);
> - gcr3_gfn = get_guest_cr3_from_dte(gdte);
> + gcr3_gfn = _gfn(get_guest_cr3_from_dte(gdte));
> glx = get_glx_from_dte(gdte);
> gv = get_gv_from_dte(gdte);
>
> unmap_domain_page(dte_base);
> + put_gfn(d, dte_gfn);
>
> /* Do not update host dte before gcr3 has been set */
> - if ( gcr3_gfn == 0 )
> + if ( gfn_x(gcr3_gfn) == 0 )
> return 0;
>
> - gcr3_mfn = mfn_x(get_gfn(d, gcr3_gfn, &p2mt));
> - put_gfn(d, gcr3_gfn);
> + gcr3_mfn = get_gfn(d, gcr3_gfn, &p2mt);
>
> - ASSERT(mfn_valid(_mfn(gcr3_mfn)));
> + if ( mfn_eq(gcr3_mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + put_gfn(d, gcr3_gfn);
> + return 0;
> + }
> +
> + ASSERT(mfn_valid(gcr3_mfn));
>
> iommu = find_iommu_for_device(0, mbdf);
> if ( !iommu )
> {
> + put_gfn(d, gcr3_gfn);
> AMD_IOMMU_DEBUG("%s: Fail to find iommu for bdf %x!\n",
> __func__, mbdf);
> return -ENODEV;
> @@ -458,18 +506,19 @@ static int do_invalidate_dte(struct domain *d,
> cmd_entry_t *cmd)
>
> spin_lock_irqsave(&iommu->lock, flags);
> iommu_dte_set_guest_cr3((u32 *)mdte, hdom_id,
> - gcr3_mfn << PAGE_SHIFT, gv, glx);
> + mfn_to_maddr(gcr3_mfn), gv, glx);
>
> amd_iommu_flush_device(iommu, req_id);
> spin_unlock_irqrestore(&iommu->lock, flags);
>
> + put_gfn(d, gcr3_gfn);
> +
> return 0;
> }
>
> static void guest_iommu_process_command(unsigned long _d)
> {
> - unsigned long opcode, tail, head, entries_per_page, cmd_mfn;
> - cmd_entry_t *cmd, *cmd_base;
> + unsigned long tail, head;
> struct domain *d = (struct domain *)_d;
> struct guest_iommu *iommu;
>
> @@ -493,56 +542,75 @@ static void guest_iommu_process_command(unsigned long
> _d)
> return;
> }
>
> - entries_per_page = PAGE_SIZE / sizeof(cmd_entry_t);
> -
> while ( head != tail )
> {
> + mfn_t mfn;
> + gfn_t gfn;
> + p2m_type_t p2mt;
> + cmd_entry_t cmd, *ptr;
> int ret = 0;
> + unsigned int opcode;
> +
> + gfn = _gfn(PFN_DOWN(reg_to_u64(iommu->cmd_buffer.reg_base)) +
> + PFN_DOWN(head * sizeof(cmd)));
>
> - cmd_mfn = guest_iommu_get_table_mfn(d,
> -
> reg_to_u64(iommu->cmd_buffer.reg_base),
> - sizeof(cmd_entry_t), head);
> - ASSERT(mfn_valid(_mfn(cmd_mfn)));
> + mfn = get_gfn(d, gfn, &p2mt);
> + if ( mfn_eq(mfn, INVALID_MFN) || !p2m_is_ram(p2mt) )
> + {
> + AMD_IOMMU_DEBUG(
> + "Error: guest iommu cmd buffer bad gfn %"PRI_gfn", type %u,
> mfn %"
> + PRI_mfn", reg_base %#"PRIx64", head %#lx\n",
> + gfn_x(gfn), p2mt, mfn_x(mfn),
> + reg_to_u64(iommu->cmd_buffer.reg_base), head);
> + put_gfn(d, gfn);
> + guest_iommu_disable(iommu);
> + return;
> + }
>
> - cmd_base = map_domain_page(_mfn(cmd_mfn));
> - cmd = cmd_base + head % entries_per_page;
> + ptr = map_domain_page(mfn) + head % (PAGE_SIZE /
> sizeof(cmd_entry_t));
> + memcpy(&cmd, ptr, sizeof(cmd));
> + unmap_domain_page(ptr);
> + put_gfn(d, gfn);
>
> - opcode = get_field_from_reg_u32(cmd->data[1],
> + opcode = get_field_from_reg_u32(cmd.data[1],
> IOMMU_CMD_OPCODE_MASK,
> IOMMU_CMD_OPCODE_SHIFT);
> switch ( opcode )
> {
> case IOMMU_CMD_COMPLETION_WAIT:
> - ret = do_completion_wait(d, cmd);
> + ret = do_completion_wait(d, &cmd);
> break;
> case IOMMU_CMD_INVALIDATE_DEVTAB_ENTRY:
> - ret = do_invalidate_dte(d, cmd);
> + ret = do_invalidate_dte(d, &cmd);
> break;
> case IOMMU_CMD_INVALIDATE_IOMMU_PAGES:
> - ret = do_invalidate_pages(d, cmd);
> + ret = do_invalidate_pages(d, &cmd);
> break;
> case IOMMU_CMD_INVALIDATE_IOTLB_PAGES:
> - ret = do_invalidate_iotlb_pages(d, cmd);
> + ret = do_invalidate_iotlb_pages(d, &cmd);
> break;
> case IOMMU_CMD_INVALIDATE_INT_TABLE:
> break;
> case IOMMU_CMD_COMPLETE_PPR_REQUEST:
> - ret = do_complete_ppr_request(d, cmd);
> + ret = do_complete_ppr_request(d, &cmd);
> break;
> case IOMMU_CMD_INVALIDATE_IOMMU_ALL:
> - ret = do_invalidate_all(d, cmd);
> + ret = do_invalidate_all(d, &cmd);
> break;
> default:
> - AMD_IOMMU_DEBUG("CMD: Unknown command cmd_type = %lx "
> + AMD_IOMMU_DEBUG("CMD: Unknown command cmd_type = %#x "
> "head = %ld\n", opcode, head);
> + ret = -EINVAL;
> break;
> }
>
> - unmap_domain_page(cmd_base);
> if ( ++head >= iommu->cmd_buffer.entries )
> head = 0;
> if ( ret )
> + {
> guest_iommu_disable(iommu);
> + break;
> + }
> }
>
> /* Now shift cmd buffer head pointer */
> @@ -818,10 +886,10 @@ int guest_iommu_set_base(struct domain *d, uint64_t
> base)
>
> for ( int i = 0; i < IOMMU_MMIO_PAGE_NR; i++ )
> {
> - unsigned long gfn = base + i;
> + gfn_t gfn = _gfn(base + i);
>
> get_gfn_query(d, gfn, &t);
> - p2m_change_type_one(d, gfn, t, p2m_mmio_dm);
> + p2m_change_type_one(d, gfn_x(gfn), t, p2m_mmio_dm);
> put_gfn(d, gfn);
> }
>
>
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |