iommufd: Fix UAF in selftest IOPF reporting

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@smail.nju.edu.cn/
Fixes: ddee199710 ("iommufd/selftest: Add IOPF support for mock device")
Cc: stable@vger.kernel.org
Suggested-by: Jason Gunthorpe <jgg@ziepe.ca>
Assisted-by: Codex:gpt-5.6-terra
Signed-off-by: Peiyang He <peiyang_he@smail.nju.edu.cn>
Link: https://patch.msgid.link/38C8DF0A118B7176+20260811095551.2756745-1-peiyang_he@smail.nju.edu.cn
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
This commit is contained in:
Peiyang He 2026-08-11 17:55:51 +08:00 committed by Jason Gunthorpe
parent d616de490e
commit 8c07df7cdf

View File

@ -178,6 +178,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;
@ -998,6 +999,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;
@ -1023,7 +1025,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;
@ -1078,7 +1082,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;
@ -1093,7 +1099,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:
@ -1137,7 +1145,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;
@ -1742,10 +1752,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)
@ -1756,7 +1772,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;
@ -1864,14 +1882,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);
@ -1888,8 +1911,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;
@ -1909,7 +1934,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;
}
@ -1920,7 +1947,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;