]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
iommu/vt-d: Fix UAF on sva unbind with pending IOPFs
authorLu Baolu <baolu.lu@linux.intel.com>
Wed, 23 Jul 2025 07:20:45 +0000 (15:20 +0800)
committerWill Deacon <will@kernel.org>
Wed, 23 Jul 2025 12:01:56 +0000 (13:01 +0100)
Commit 17fce9d2336d ("iommu/vt-d: Put iopf enablement in domain attach
path") disables IOPF on device by removing the device from its IOMMU's
IOPF queue when the last IOPF-capable domain is detached from the device.
Unfortunately, it did this in a wrong place where there are still pending
IOPFs. As a result, a use-after-free error is potentially triggered and
eventually a kernel panic with a kernel trace similar to the following:

 refcount_t: underflow; use-after-free.
 WARNING: CPU: 3 PID: 313 at lib/refcount.c:28 refcount_warn_saturate+0xd8/0xe0
 Workqueue: iopf_queue/dmar0-iopfq iommu_sva_handle_iopf
 Call Trace:
   <TASK>
   iopf_free_group+0xe/0x20
   process_one_work+0x197/0x3d0
   worker_thread+0x23a/0x350
   ? rescuer_thread+0x4a0/0x4a0
   kthread+0xf8/0x230
   ? finish_task_switch.isra.0+0x81/0x260
   ? kthreads_online_cpu+0x110/0x110
   ? kthreads_online_cpu+0x110/0x110
   ret_from_fork+0x13b/0x170
   ? kthreads_online_cpu+0x110/0x110
   ret_from_fork_asm+0x11/0x20
   </TASK>
  ---[ end trace 0000000000000000 ]---

The intel_pasid_tear_down_entry() function is responsible for blocking
hardware from generating new page faults and flushing all in-flight
ones. Therefore, moving iopf_for_domain_remove() after this function
should resolve this.

Fixes: 17fce9d2336d ("iommu/vt-d: Put iopf enablement in domain attach path")
Reported-by: Ethan Milon <ethan.milon@eviden.com>
Closes: https://lore.kernel.org/r/e8b37f3e-8539-40d4-8993-43a1f3ffe5aa@eviden.com
Suggested-by: Ethan Milon <ethan.milon@eviden.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Link: https://lore.kernel.org/r/20250723072045.1853328-1-baolu.lu@linux.intel.com
Signed-off-by: Will Deacon <will@kernel.org>
drivers/iommu/intel/iommu.c

index d1791b50f79115210fa0543756926be0e24e3658..a4f53b62dfbb10155da03212a0de999ec8ed4411 100644 (file)
@@ -4081,8 +4081,8 @@ static int blocking_domain_set_dev_pasid(struct iommu_domain *domain,
 {
        struct device_domain_info *info = dev_iommu_priv_get(dev);
 
-       iopf_for_domain_remove(old, dev);
        intel_pasid_tear_down_entry(info->iommu, dev, pasid, false);
+       iopf_for_domain_remove(old, dev);
        domain_remove_dev_pasid(old, dev, pasid);
 
        return 0;