[PATCH] iommufd: Fix UAF in selftest IOPF reporting
From: Peiyang He
Date: Tue Aug 11 2026 - 05:59:30 EST
IOMMUFD selftest TRIGGER_IOPF borrows an attach handle from
group->pasid_array without synchronizing against PASID detach,
then a concurrent iommu_report_device_fault() can dereference
that borrowed handle's domain pointer after the detach erases
the handle and frees the backing struct iommufd_attach_handle.
TRIGGER_IOPF then dereferences the freed handle, causing a UAF.
Fix by adding a iopf_rwsem in mock_dev to follow the expected design
of a real driver. Hold its read side across the whole
iommu_report_device_fault() call, and its write side around every
path that attaches, detaches, or replaces a device domain.
This can block new reports and drains in-flight reports before an old
attach handle or the IOPF fault parameter can be removed.
Also take the write side while registering a mock device, since
it can invoke the mock driver's default-domain attach callback.
Closes: https://lore.kernel.org/all/D5E3AA41600B2056+f4e15662-bd2b-43ea-91cb-518de429e72c@xxxxxxxxxxxxxxxx/
Fixes: ddee19971081 ("iommufd/selftest: Add IOPF support for mock device")
Cc: stable@xxxxxxxxxxxxxxx
Suggested-by: Jason Gunthorpe <jgg@xxxxxxxx>
Assisted-by: Codex:gpt-5.6-terra
Signed-off-by: Peiyang He <peiyang_he@xxxxxxxxxxxxxxxx>
---
drivers/iommu/iommufd/selftest.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a526..727b59799d5f 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -177,6 +177,7 @@ struct mock_dev {
struct device dev;
struct mock_viommu *viommu;
struct rw_semaphore viommu_rwsem;
+ struct rw_semaphore iopf_rwsem;
unsigned long flags;
unsigned long vdev_id;
int id;
@@ -997,6 +998,7 @@ static struct mock_dev *mock_dev_create(unsigned long dev_flags)
return ERR_PTR(-ENOMEM);
init_rwsem(&mdev->viommu_rwsem);
+ init_rwsem(&mdev->iopf_rwsem);
device_initialize(&mdev->dev);
mdev->flags = dev_flags;
mdev->dev.release = mock_dev_release;
@@ -1022,7 +1024,9 @@ static struct mock_dev *mock_dev_create(unsigned long dev_flags)
goto err_put;
}
+ down_write(&mdev->iopf_rwsem);
rc = iommu_mock_device_add(&mdev->dev, &mock_iommu.iommu_dev);
+ up_write(&mdev->iopf_rwsem);
if (rc)
goto err_put;
return mdev;
@@ -1077,7 +1081,9 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
}
sobj->idev.idev = idev;
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
rc = iommufd_device_attach(idev, IOMMU_NO_PASID, &pt_id);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
if (rc)
goto out_unbind;
@@ -1092,7 +1098,9 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd,
return 0;
out_detach:
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_device_detach(idev, IOMMU_NO_PASID);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
out_unbind:
iommufd_device_unbind(idev);
out_mdev:
@@ -1136,7 +1144,9 @@ static int iommufd_test_mock_domain_replace(struct iommufd_ucmd *ucmd,
if (IS_ERR(sobj))
return PTR_ERR(sobj);
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
rc = iommufd_device_replace(sobj->idev.idev, IOMMU_NO_PASID, &pt_id);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
if (rc)
goto out_sobj;
@@ -1739,10 +1749,16 @@ static int iommufd_test_trigger_iopf(struct iommufd_ucmd *ucmd,
{
struct iopf_fault event = {};
struct iommufd_device *idev;
+ struct mock_dev *mdev;
idev = iommufd_get_device(ucmd, cmd->trigger_iopf.dev_id);
if (IS_ERR(idev))
return PTR_ERR(idev);
+ if (!iommufd_selftest_is_mock_dev(idev->dev)) {
+ iommufd_put_object(ucmd->ictx, &idev->obj);
+ return -EINVAL;
+ }
+ mdev = to_mock_dev(idev->dev);
event.fault.prm.flags = IOMMU_FAULT_PAGE_REQUEST_LAST_PAGE;
if (cmd->trigger_iopf.pasid != IOMMU_NO_PASID)
@@ -1753,7 +1769,9 @@ static int iommufd_test_trigger_iopf(struct iommufd_ucmd *ucmd,
event.fault.prm.grpid = cmd->trigger_iopf.grpid;
event.fault.prm.perm = cmd->trigger_iopf.perm;
+ down_read(&mdev->iopf_rwsem);
iommu_report_device_fault(idev->dev, &event);
+ up_read(&mdev->iopf_rwsem);
iommufd_put_object(ucmd->ictx, &idev->obj);
return 0;
@@ -1861,14 +1879,19 @@ static int iommufd_test_pasid_attach(struct iommufd_ucmd *ucmd,
if (IS_ERR(sobj))
return PTR_ERR(sobj);
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
rc = iommufd_device_attach(sobj->idev.idev, cmd->pasid_attach.pasid,
&cmd->pasid_attach.pt_id);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
if (rc)
goto out_sobj;
rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd));
- if (rc)
+ if (rc) {
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_device_detach(sobj->idev.idev, cmd->pasid_attach.pasid);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
+ }
out_sobj:
iommufd_put_object(ucmd->ictx, &sobj->obj);
@@ -1885,8 +1908,10 @@ static int iommufd_test_pasid_replace(struct iommufd_ucmd *ucmd,
if (IS_ERR(sobj))
return PTR_ERR(sobj);
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
rc = iommufd_device_replace(sobj->idev.idev, cmd->pasid_attach.pasid,
&cmd->pasid_attach.pt_id);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
if (rc)
goto out_sobj;
@@ -1906,7 +1931,9 @@ static int iommufd_test_pasid_detach(struct iommufd_ucmd *ucmd,
if (IS_ERR(sobj))
return PTR_ERR(sobj);
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_device_detach(sobj->idev.idev, cmd->pasid_detach.pasid);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_put_object(ucmd->ictx, &sobj->obj);
return 0;
}
@@ -1917,7 +1944,9 @@ void iommufd_selftest_destroy(struct iommufd_object *obj)
switch (sobj->type) {
case TYPE_IDEV:
+ down_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_device_detach(sobj->idev.idev, IOMMU_NO_PASID);
+ up_write(&sobj->idev.mock_dev->iopf_rwsem);
iommufd_device_unbind(sobj->idev.idev);
mock_dev_destroy(sobj->idev.mock_dev);
break;
base-commit: 075b74841bd0065a3bda3440873c747938e69b68
--
2.43.0