diff options
| author | Peiyang He <peiyang_he@smail.nju.edu.cn> | 2026-08-11 17:55:51 +0800 |
|---|---|---|
| committer | Jason Gunthorpe <jgg@nvidia.com> | 2026-08-13 14:30:55 -0300 |
| commit | 8c07df7cdfcf52f1ff276c588612aabc6c6b8399 (patch) | |
| tree | 86c4a8df9fcd245ce2e06e4453533c5c0c781054 | |
| parent | d616de490ec0242dcf78f02f1adf7baa035c4d0d (diff) | |
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: ddee19971081 ("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>
| -rw-r--r-- | drivers/iommu/iommufd/selftest.c | 31 |
1 files changed, 30 insertions, 1 deletions
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c index b021fc6bc5f5..ee706f18f7e9 100644 --- a/drivers/iommu/iommufd/selftest.c +++ b/drivers/iommu/iommufd/selftest.c @@ -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; |
