From: Teddy Astie Subject: x86/IOMMU: move tracking in iommu_identity_mapping() If for some reason xmalloc() fails after having mapped the reserved regions, an error is reported, but the regions remain mapped in the P2M. Similarly if an error occurs during set_identity_p2m_entry() (except on the first call), the partial mappings of the region would be retained without being tracked anywhere, and hence without there being a way to remove them again from the domain's P2M. Move the setting up of the list entry ahead of trying to map the region. In cases other than the first mapping failing, keep record of the full region, such that a subsequent unmapping request can be properly torn down. To compensate for the potentially excess unmapping requests, don't log a warning from p2m_remove_identity_entry() when there really was nothing mapped at a given GFN. This is XSA-460 / CVE-2024-31145. Fixes: 2201b67b9128 ("VT-d: improve RMRR region handling") Fixes: c0e19d7c6c42 ("IOMMU: generalize VT-d's tracking of mapped RMRR regions") Signed-off-by: Teddy Astie Signed-off-by: Jan Beulich Reviewed-by: Roger Pau Monné --- a/xen/arch/x86/mm/p2m.c +++ b/xen/arch/x86/mm/p2m.c @@ -1267,9 +1267,11 @@ int p2m_remove_identity_entry(struct dom else { gfn_unlock(p2m, gfn, 0); - printk(XENLOG_G_WARNING - "non-identity map d%d:%lx not cleared (mapped to %lx)\n", - d->domain_id, gfn_l, mfn_x(mfn)); + if ( (p2mt != p2m_invalid && p2mt != p2m_mmio_dm) || + a != p2m_access_n || !mfn_eq(mfn, INVALID_MFN) ) + printk(XENLOG_G_WARNING + "non-identity map %pd:%lx not cleared (mapped to %lx)\n", + d, gfn_l, mfn_x(mfn)); ret = 0; } --- a/xen/drivers/passthrough/x86/iommu.c +++ b/xen/drivers/passthrough/x86/iommu.c @@ -267,24 +267,36 @@ int iommu_identity_mapping(struct domain if ( p2ma == p2m_access_x ) return -ENOENT; - while ( base_pfn < end_pfn ) - { - int err = set_identity_p2m_entry(d, base_pfn, p2ma, flag); - - if ( err ) - return err; - base_pfn++; - } - map = xmalloc(struct identity_map); if ( !map ) return -ENOMEM; + map->base = base; map->end = end; map->access = p2ma; map->count = 1; + + /* + * Insert into list ahead of mapping, so the range can be found when + * trying to clean up. + */ list_add_tail(&map->list, &hd->arch.identity_maps); + for ( ; base_pfn < end_pfn; ++base_pfn ) + { + int err = set_identity_p2m_entry(d, base_pfn, p2ma, flag); + + if ( !err ) + continue; + + if ( (map->base >> PAGE_SHIFT_4K) == base_pfn ) + { + list_del(&map->list); + xfree(map); + } + return err; + } + return 0; }