[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

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.