mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
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:
parent
d616de490e
commit
8c07df7cdf
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user