The selftest mock device teardown only detaches IOMMU_NO_PASID. If userspace attaches a non-default PASID with IOMMU_TEST_OP_PASID_ATTACH and never issues IOMMU_TEST_OP_PASID_DETACH, the attachment keeps the reference taken by iommufd_device_attach(). Unbind then fails and the iommufd device and IOMMU group are leaked, triggering: WARNING: drivers/iommu/iommufd/iommufd_private.h:241 at iommufd_device_unbind+0x80/0xb0 WARNING: drivers/iommu/iommu.c:744 at __iommu_group_free_device+0x1b2/0x400 WARNING: drivers/iommu/iommufd/main.c:389 at iommufd_fops_release+0x380/0x3b0 WARNING: drivers/iommu/iommufd/main.c:399 at iommufd_fops_release+0x3a3/0x3b0 Track attached PASIDs in an IDA, as VFIO does, and detach them all before detaching IOMMU_NO_PASID and unbinding the mock device. Fixes: c1b52b0a97ae ("iommufd/selftest: Add test ops to test pasid attach/detach") Cc: stable@vger.kernel.org Signed-off-by: Peiyang He --- Found by a modified Syzkaller. drivers/iommu/iommufd/selftest.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c index 25387969a..18938002f 100644 --- a/drivers/iommu/iommufd/selftest.c +++ b/drivers/iommu/iommufd/selftest.c @@ -196,6 +196,7 @@ static inline struct mock_dev *to_mock_dev(struct device *dev) struct selftest_obj { struct iommufd_object obj; enum selftest_obj_type type; + struct ida pasids; union { struct { @@ -1058,6 +1059,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd, sobj = iommufd_object_alloc(ucmd->ictx, sobj, IOMMUFD_OBJ_SELFTEST); if (IS_ERR(sobj)) return PTR_ERR(sobj); + ida_init(&sobj->pasids); sobj->idev.ictx = ucmd->ictx; sobj->type = TYPE_IDEV; @@ -1880,8 +1882,21 @@ static int iommufd_test_pasid_attach(struct iommufd_ucmd *ucmd, return PTR_ERR(sobj); down_write(&sobj->idev.mock_dev->iopf_rwsem); + if (ida_exists(&sobj->pasids, cmd->pasid_attach.pasid)) { + rc = -EBUSY; + goto out_unlock; + } + + rc = ida_alloc_range(&sobj->pasids, cmd->pasid_attach.pasid, + cmd->pasid_attach.pasid, GFP_KERNEL); + if (rc < 0) + goto out_unlock; + rc = iommufd_device_attach(sobj->idev.idev, cmd->pasid_attach.pasid, &cmd->pasid_attach.pt_id); + if (rc) + ida_free(&sobj->pasids, cmd->pasid_attach.pasid); +out_unlock: up_write(&sobj->idev.mock_dev->iopf_rwsem); if (rc) goto out_sobj; @@ -1890,6 +1905,8 @@ static int iommufd_test_pasid_attach(struct iommufd_ucmd *ucmd, if (rc) { down_write(&sobj->idev.mock_dev->iopf_rwsem); iommufd_device_detach(sobj->idev.idev, cmd->pasid_attach.pasid); + if (ida_exists(&sobj->pasids, cmd->pasid_attach.pasid)) + ida_free(&sobj->pasids, cmd->pasid_attach.pasid); up_write(&sobj->idev.mock_dev->iopf_rwsem); } @@ -1933,6 +1950,8 @@ static int iommufd_test_pasid_detach(struct iommufd_ucmd *ucmd, down_write(&sobj->idev.mock_dev->iopf_rwsem); iommufd_device_detach(sobj->idev.idev, cmd->pasid_detach.pasid); + if (ida_exists(&sobj->pasids, cmd->pasid_detach.pasid)) + ida_free(&sobj->pasids, cmd->pasid_detach.pasid); up_write(&sobj->idev.mock_dev->iopf_rwsem); iommufd_put_object(ucmd->ictx, &sobj->obj); return 0; @@ -1941,10 +1960,15 @@ static int iommufd_test_pasid_detach(struct iommufd_ucmd *ucmd, void iommufd_selftest_destroy(struct iommufd_object *obj) { struct selftest_obj *sobj = to_selftest_obj(obj); + int pasid; switch (sobj->type) { case TYPE_IDEV: down_write(&sobj->idev.mock_dev->iopf_rwsem); + while ((pasid = ida_find_first(&sobj->pasids)) >= 0) { + iommufd_device_detach(sobj->idev.idev, pasid); + ida_free(&sobj->pasids, pasid); + } iommufd_device_detach(sobj->idev.idev, IOMMU_NO_PASID); up_write(&sobj->idev.mock_dev->iopf_rwsem); iommufd_device_unbind(sobj->idev.idev);