iommu/vt-d: Fix qi_batch NULL pointer with nested parent domain

JIRA: https://issues.redhat.com/browse/RHEL-73035
Upstream Status: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
CVE: CVE-2024-56668

commit 74536f91962d5f6af0a42414773ce61e653c10ee
Author: Yi Liu <yi.l.liu@intel.com>
Date:   Fri Dec 13 09:17:51 2024 +0800

    iommu/vt-d: Fix qi_batch NULL pointer with nested parent domain

    The qi_batch is allocated when assigning cache tag for a domain. While
    for nested parent domain, it is missed. Hence, when trying to map pages
    to the nested parent, NULL dereference occurred. Also, there is potential
    memleak since there is no lock around domain->qi_batch allocation.

    To solve it, add a helper for qi_batch allocation, and call it in both
    the __cache_tag_assign_domain() and __cache_tag_assign_parent_domain().

      BUG: kernel NULL pointer dereference, address: 0000000000000200
      #PF: supervisor read access in kernel mode
      #PF: error_code(0x0000) - not-present page
      PGD 8104795067 P4D 0
      Oops: Oops: 0000 [#1] PREEMPT SMP NOPTI
      CPU: 223 UID: 0 PID: 4357 Comm: qemu-system-x86 Not tainted 6.13.0-rc1-00028-g4b50c3c3b998-dirty #2632
      Call Trace:
       ? __die+0x24/0x70
       ? page_fault_oops+0x80/0x150
       ? do_user_addr_fault+0x63/0x7b0
       ? exc_page_fault+0x7c/0x220
       ? asm_exc_page_fault+0x26/0x30
       ? cache_tag_flush_range_np+0x13c/0x260
       intel_iommu_iotlb_sync_map+0x1a/0x30
       iommu_map+0x61/0xf0
       batch_to_domain+0x188/0x250
       iopt_area_fill_domains+0x125/0x320
       ? rcu_is_watching+0x11/0x50
       iopt_map_pages+0x63/0x100
       iopt_map_common.isra.0+0xa7/0x190
       iopt_map_user_pages+0x6a/0x80
       iommufd_ioas_map+0xcd/0x1d0
       iommufd_fops_ioctl+0x118/0x1c0
       __x64_sys_ioctl+0x93/0xc0
       do_syscall_64+0x71/0x140
       entry_SYSCALL_64_after_hwframe+0x76/0x7e

    Fixes: 705c1cdf1e73 ("iommu/vt-d: Introduce batched cache invalidation")
    Cc: stable@vger.kernel.org
    Co-developed-by: Lu Baolu <baolu.lu@linux.intel.com>
    Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
    Signed-off-by: Yi Liu <yi.l.liu@intel.com>
    Reviewed-by: Kevin Tian <kevin.tian@intel.com>
    Link: https://lore.kernel.org/r/20241210130322.17175-1-yi.l.liu@intel.com
    Signed-off-by: Joerg Roedel <jroedel@suse.de>

(cherry picked from commit 74536f91962d5f6af0a42414773ce61e653c10ee)
Signed-off-by: Jerry Snitselaar <jsnitsel@redhat.com>
This commit is contained in:
Jerry Snitselaar 2025-01-06 10:40:57 -07:00
parent 5c0fca1d2e
commit 955cde8bf0
1 changed files with 27 additions and 7 deletions

View File

@ -105,12 +105,35 @@ static void cache_tag_unassign(struct dmar_domain *domain, u16 did,
spin_unlock_irqrestore(&domain->cache_lock, flags);
}
/* domain->qi_batch will be freed in iommu_free_domain() path. */
static int domain_qi_batch_alloc(struct dmar_domain *domain)
{
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&domain->cache_lock, flags);
if (domain->qi_batch)
goto out_unlock;
domain->qi_batch = kzalloc(sizeof(*domain->qi_batch), GFP_ATOMIC);
if (!domain->qi_batch)
ret = -ENOMEM;
out_unlock:
spin_unlock_irqrestore(&domain->cache_lock, flags);
return ret;
}
static int __cache_tag_assign_domain(struct dmar_domain *domain, u16 did,
struct device *dev, ioasid_t pasid)
{
struct device_domain_info *info = dev_iommu_priv_get(dev);
int ret;
ret = domain_qi_batch_alloc(domain);
if (ret)
return ret;
ret = cache_tag_assign(domain, did, dev, pasid, CACHE_TAG_IOTLB);
if (ret || !info->ats_enabled)
return ret;
@ -139,6 +162,10 @@ static int __cache_tag_assign_parent_domain(struct dmar_domain *domain, u16 did,
struct device_domain_info *info = dev_iommu_priv_get(dev);
int ret;
ret = domain_qi_batch_alloc(domain);
if (ret)
return ret;
ret = cache_tag_assign(domain, did, dev, pasid, CACHE_TAG_NESTING_IOTLB);
if (ret || !info->ats_enabled)
return ret;
@ -190,13 +217,6 @@ int cache_tag_assign_domain(struct dmar_domain *domain,
u16 did = domain_get_id_for_dev(domain, dev);
int ret;
/* domain->qi_bach will be freed in iommu_free_domain() path. */
if (!domain->qi_batch) {
domain->qi_batch = kzalloc(sizeof(*domain->qi_batch), GFP_KERNEL);
if (!domain->qi_batch)
return -ENOMEM;
}
ret = __cache_tag_assign_domain(domain, did, dev, pasid);
if (ret || domain->domain.type != IOMMU_DOMAIN_NESTED)
return ret;