iommufd for 6.8 rc

Four syzkaller found bugs:
 
 - Corruption during error unwind in iommufd_access_change_ioas()
 
 - Overlapping IDs in the test suite due to out of order destruction
 
 - Missing locking for access->ioas in the test suite
 
 - False failures in the test suite validation logic with huge pages
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQRRRCHOFoQz/8F5bUaFwuHvBreFYQUCZeH0gQAKCRCFwuHvBreF
 YcC5AP91VuNxoExC6Zx9c8i1U2SR5IbvVAHT572qgdhaKoVzugEAtfKJA2ELheL8
 s1F+TDOo8fR/aawIAPzK5GCuHc5oIwg=
 =S44j
 -----END PGP SIGNATURE-----

Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd

Pull iommufd fixes from Jason Gunthorpe:
 "Four syzkaller found bugs:

   - Corruption during error unwind in iommufd_access_change_ioas()

   - Overlapping IDs in the test suite due to out of order destruction

   - Missing locking for access->ioas in the test suite

   - False failures in the test suite validation logic with huge pages"

* tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd:
  iommufd/selftest: Don't check map/unmap pairing with HUGE_PAGES
  iommufd: Fix protection fault in iommufd_test_syz_conv_iova
  iommufd/selftest: Fix mock_dev_num bug
  iommufd: Fix iopt_access_list_id overwrite bug
This commit is contained in:
Linus Torvalds 2024-03-01 17:22:46 -08:00
commit e613c90dda
2 changed files with 54 additions and 24 deletions

View file

@ -1330,20 +1330,23 @@ int iopt_disable_large_pages(struct io_pagetable *iopt)
int iopt_add_access(struct io_pagetable *iopt, struct iommufd_access *access)
{
u32 new_id;
int rc;
down_write(&iopt->domains_rwsem);
down_write(&iopt->iova_rwsem);
rc = xa_alloc(&iopt->access_list, &access->iopt_access_list_id, access,
xa_limit_16b, GFP_KERNEL_ACCOUNT);
rc = xa_alloc(&iopt->access_list, &new_id, access, xa_limit_16b,
GFP_KERNEL_ACCOUNT);
if (rc)
goto out_unlock;
rc = iopt_calculate_iova_alignment(iopt);
if (rc) {
xa_erase(&iopt->access_list, access->iopt_access_list_id);
xa_erase(&iopt->access_list, new_id);
goto out_unlock;
}
access->iopt_access_list_id = new_id;
out_unlock:
up_write(&iopt->iova_rwsem);

View file

@ -36,7 +36,7 @@ static struct mock_bus_type iommufd_mock_bus_type = {
},
};
static atomic_t mock_dev_num;
static DEFINE_IDA(mock_dev_ida);
enum {
MOCK_DIRTY_TRACK = 1,
@ -63,8 +63,8 @@ enum {
* In syzkaller mode the 64 bit IOVA is converted into an nth area and offset
* value. This has a much smaller randomization space and syzkaller can hit it.
*/
static unsigned long iommufd_test_syz_conv_iova(struct io_pagetable *iopt,
u64 *iova)
static unsigned long __iommufd_test_syz_conv_iova(struct io_pagetable *iopt,
u64 *iova)
{
struct syz_layout {
__u32 nth_area;
@ -88,6 +88,21 @@ static unsigned long iommufd_test_syz_conv_iova(struct io_pagetable *iopt,
return 0;
}
static unsigned long iommufd_test_syz_conv_iova(struct iommufd_access *access,
u64 *iova)
{
unsigned long ret;
mutex_lock(&access->ioas_lock);
if (!access->ioas) {
mutex_unlock(&access->ioas_lock);
return 0;
}
ret = __iommufd_test_syz_conv_iova(&access->ioas->iopt, iova);
mutex_unlock(&access->ioas_lock);
return ret;
}
void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
unsigned int ioas_id, u64 *iova, u32 *flags)
{
@ -100,7 +115,7 @@ void iommufd_test_syz_conv_iova_id(struct iommufd_ucmd *ucmd,
ioas = iommufd_get_ioas(ucmd->ictx, ioas_id);
if (IS_ERR(ioas))
return;
*iova = iommufd_test_syz_conv_iova(&ioas->iopt, iova);
*iova = __iommufd_test_syz_conv_iova(&ioas->iopt, iova);
iommufd_put_object(ucmd->ictx, &ioas->obj);
}
@ -123,6 +138,7 @@ enum selftest_obj_type {
struct mock_dev {
struct device dev;
unsigned long flags;
int id;
};
struct selftest_obj {
@ -430,20 +446,27 @@ static size_t mock_domain_unmap_pages(struct iommu_domain *domain,
/*
* iommufd generates unmaps that must be a strict
* superset of the map's performend So every starting
* IOVA should have been an iova passed to map, and the
* superset of the map's performend So every
* starting/ending IOVA should have been an iova passed
* to map.
*
* First IOVA must be present and have been a first IOVA
* passed to map_pages
* This simple logic doesn't work when the HUGE_PAGE is
* turned on since the core code will automatically
* switch between the two page sizes creating a break in
* the unmap calls. The break can land in the middle of
* contiguous IOVA.
*/
if (first) {
WARN_ON(ent && !(xa_to_value(ent) &
MOCK_PFN_START_IOVA));
first = false;
if (!(domain->pgsize_bitmap & MOCK_HUGE_PAGE_SIZE)) {
if (first) {
WARN_ON(ent && !(xa_to_value(ent) &
MOCK_PFN_START_IOVA));
first = false;
}
if (pgcount == 1 &&
cur + MOCK_IO_PAGE_SIZE == pgsize)
WARN_ON(ent && !(xa_to_value(ent) &
MOCK_PFN_LAST_IOVA));
}
if (pgcount == 1 && cur + MOCK_IO_PAGE_SIZE == pgsize)
WARN_ON(ent && !(xa_to_value(ent) &
MOCK_PFN_LAST_IOVA));
iova += MOCK_IO_PAGE_SIZE;
ret += MOCK_IO_PAGE_SIZE;
@ -631,7 +654,7 @@ static void mock_dev_release(struct device *dev)
{
struct mock_dev *mdev = container_of(dev, struct mock_dev, dev);
atomic_dec(&mock_dev_num);
ida_free(&mock_dev_ida, mdev->id);
kfree(mdev);
}
@ -653,8 +676,12 @@ static struct mock_dev *mock_dev_create(unsigned long dev_flags)
mdev->dev.release = mock_dev_release;
mdev->dev.bus = &iommufd_mock_bus_type.bus;
rc = dev_set_name(&mdev->dev, "iommufd_mock%u",
atomic_inc_return(&mock_dev_num));
rc = ida_alloc(&mock_dev_ida, GFP_KERNEL);
if (rc < 0)
goto err_put;
mdev->id = rc;
rc = dev_set_name(&mdev->dev, "iommufd_mock%u", mdev->id);
if (rc)
goto err_put;
@ -1156,7 +1183,7 @@ static int iommufd_test_access_pages(struct iommufd_ucmd *ucmd,
}
if (flags & MOCK_FLAGS_ACCESS_SYZ)
iova = iommufd_test_syz_conv_iova(&staccess->access->ioas->iopt,
iova = iommufd_test_syz_conv_iova(staccess->access,
&cmd->access_pages.iova);
npages = (ALIGN(iova + length, PAGE_SIZE) -
@ -1258,8 +1285,8 @@ static int iommufd_test_access_rw(struct iommufd_ucmd *ucmd,
}
if (flags & MOCK_FLAGS_ACCESS_SYZ)
iova = iommufd_test_syz_conv_iova(&staccess->access->ioas->iopt,
&cmd->access_rw.iova);
iova = iommufd_test_syz_conv_iova(staccess->access,
&cmd->access_rw.iova);
rc = iommufd_access_rw(staccess->access, iova, tmp, length, flags);
if (rc)