[PATCH v1 4/4] iommu: Unpublish the old attach handle before the PASID replace callback

From: Nicolin Chen

Date: Tue Sep 29 2026 - 17:54:52 EST


Similar to iommu_replace_group_handle(), iommu_replace_device_pasid() has
the same inert reserve as the group path and the UAF window:

core, holding group->mutex fault path, no group->mutex
========================== ===========================

xa_cmpxchg() ,-- old handle stays published
no-op, the slot is occupied |
__iommu_set_group_pasid() |
driver attach/detach ops |
iopf_queue_flush_dev() |
| iommu_attach_handle_get()
| reads old_handle
xa_store(new entry) `-- window closes
mutex_unlock()
kfree(old_handle) [caller]
UAF: old_handle->domain->iopf_handler()

Store XA_ZERO_ENTRY outright, just as the group path now does.

Fixes: 8a9e1e773f60 ("iommu: Introduce a replace API for device pasid")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Nicolin Chen <nicolinc@xxxxxxxxxx>
---
drivers/iommu/iommu.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 52e59f38cec18..2ff81e94a7b7b 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -3751,8 +3751,13 @@ int iommu_replace_device_pasid(struct iommu_domain *domain,
}

entry = iommu_make_pasid_array_entry(domain, handle);
- curr = xa_cmpxchg(&group->pasid_array, pasid, NULL,
- XA_ZERO_ENTRY, GFP_KERNEL);
+ /*
+ * iommu_attach_handle_get() runs without the group mutex, so unpublish
+ * the old handle before the driver callback: a fault must not resolve
+ * to either domain while the switch is in progress. This reserves the
+ * slot too, so the store below cannot fail.
+ */
+ curr = xa_store(&group->pasid_array, pasid, XA_ZERO_ENTRY, GFP_KERNEL);
if (xa_is_err(curr)) {
ret = xa_err(curr);
goto out_unlock;
@@ -3776,7 +3781,7 @@ int iommu_replace_device_pasid(struct iommu_domain *domain,
if (curr == entry) {
WARN_ON(1);
ret = -EINVAL;
- goto out_unlock;
+ goto out_store;
}

curr_domain = pasid_array_entry_to_domain(curr);
@@ -3786,16 +3791,16 @@ int iommu_replace_device_pasid(struct iommu_domain *domain,
ret = __iommu_set_group_pasid(domain, group,
pasid, curr_domain);
if (ret)
- goto out_unlock;
+ entry = curr; /* restore the old handle */
}

+out_store:
/*
- * The above xa_cmpxchg() reserved the memory, and the
- * group->mutex is held, this cannot fail.
+ * The xa_store() above reserved the memory, and the group->mutex
+ * is held, this cannot fail.
*/
WARN_ON(xa_is_err(xa_store(&group->pasid_array,
pasid, entry, GFP_KERNEL)));
-
out_unlock:
mutex_unlock(&group->mutex);
return ret;
--
2.43.0