[PATCH 3/5] iommu/vt-d: Fix iopf_refcount leak on RID domain replacement

From: Lu Baolu

Date: Fri Jul 31 2026 - 01:58:10 EST


intel_iommu_attach_device() enables IOPF for the new domain but never
disables it for the old one. device_block_translation(), called at the
start of the function, tears down translation but does not touch any IOPF
state; blocking_domain_attach_dev() has to call iopf_for_domain_remove()
explicitly before invoking it for exactly this reason.

identity_domain_attach_dev() has the same problem. Its comment claims
that no PRI handling is needed because the device has been put in the
blocking state, but the blocking state and the IOPF reference count are
independent of each other.

As a result, replacing a domain that has an iopf_handler with another
domain at RID level leaks a reference in info->iopf_refcount. The count
never drops back to zero, so iopf_queue_remove_device() is never called
and iommu_disable_pci_pri() triggers its WARN_ON(info->iopf_refcount)
when the device is released.

The PASID paths already handle this correctly by way of
iopf_for_domain_replace(); convert the two RID paths to do the same.
Using the replace helper rather than a bare remove keeps the enable
before the disable, so the reference count does not transiently reach
zero and evict the device from the IOPF queue.

Fixes: 17fce9d2336d ("iommu/vt-d: Put iopf enablement in domain attach path")
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Closes: https://sashiko.dev/#/patchset/20260602233426.357499-1-baolu.lu%40linux.intel.com
Assisted-by: Claude:claude-opus-5
Signed-off-by: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
---
drivers/iommu/intel/iommu.c | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 528b59e5f4ce..6baf1c075dc9 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -3158,13 +3158,13 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,
if (ret)
return ret;

- ret = iopf_for_domain_set(domain, dev);
+ ret = iopf_for_domain_replace(domain, old, dev);
if (ret)
return ret;

ret = dmar_domain_attach_device(to_dmar_domain(domain), dev);
if (ret)
- iopf_for_domain_remove(domain, dev);
+ iopf_for_domain_replace(old, domain, dev);

return ret;
}
@@ -3870,10 +3870,13 @@ static int identity_domain_attach_dev(struct iommu_domain *domain,
return 0;

/*
- * No PRI support with the global identity domain. No need to enable or
- * disable PRI in this path as the iommu has been put in the blocking
- * state.
+ * The identity domain has no iopf_handler, so no IOPF reference is
+ * taken for it. The reference held by the old domain must still be
+ * released here; putting the device in the blocking state above does
+ * not affect the IOPF reference count.
*/
+ iopf_for_domain_remove(old, dev);
+
if (sm_supported(iommu))
ret = intel_pasid_setup_pass_through(iommu, dev, IOMMU_NO_PASID);
else
--
2.43.0