Skip to content

Commit d4c7fcc

Browse files
committed
Merge tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd
Pull iommufd fixes from Jason Gunthorpe: "Fix two user triggerable use-after-free issues: - Possible race UAF setting up mmaps - Syzkaller found UAF when erroring an file descriptor creation ioctl due to the fput() work queue" * tag 'for-linus-iommufd' of git://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd: iommufd/selftest: Update the fail_nth limit iommufd: WARN if an object is aborted with an elevated refcount iommufd: Fix race during abort for file descriptors iommufd: Fix refcounting race during mmap
2 parents b183f25 + 43f6bee commit d4c7fcc

5 files changed

Lines changed: 56 additions & 20 deletions

File tree

drivers/iommu/iommufd/device.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,8 @@ iommufd_hw_pagetable_detach(struct iommufd_device *idev, ioasid_t pasid)
711711
iopt_remove_reserved_iova(&hwpt_paging->ioas->iopt, idev->dev);
712712
mutex_unlock(&igroup->lock);
713713

714+
iommufd_hw_pagetable_put(idev->ictx, hwpt);
715+
714716
/* Caller must destroy hwpt */
715717
return hwpt;
716718
}
@@ -1057,7 +1059,6 @@ void iommufd_device_detach(struct iommufd_device *idev, ioasid_t pasid)
10571059
hwpt = iommufd_hw_pagetable_detach(idev, pasid);
10581060
if (!hwpt)
10591061
return;
1060-
iommufd_hw_pagetable_put(idev->ictx, hwpt);
10611062
refcount_dec(&idev->obj.users);
10621063
}
10631064
EXPORT_SYMBOL_NS_GPL(iommufd_device_detach, "IOMMUFD");

drivers/iommu/iommufd/eventq.c

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,12 @@ static int iommufd_eventq_init(struct iommufd_eventq *eventq, char *name,
393393
const struct file_operations *fops)
394394
{
395395
struct file *filep;
396-
int fdno;
397396

398397
spin_lock_init(&eventq->lock);
399398
INIT_LIST_HEAD(&eventq->deliver);
400399
init_waitqueue_head(&eventq->wait_queue);
401400

401+
/* The filep is fput() by the core code during failure */
402402
filep = anon_inode_getfile(name, fops, eventq, O_RDWR);
403403
if (IS_ERR(filep))
404404
return PTR_ERR(filep);
@@ -408,10 +408,7 @@ static int iommufd_eventq_init(struct iommufd_eventq *eventq, char *name,
408408
eventq->filep = filep;
409409
refcount_inc(&eventq->obj.users);
410410

411-
fdno = get_unused_fd_flags(O_CLOEXEC);
412-
if (fdno < 0)
413-
fput(filep);
414-
return fdno;
411+
return get_unused_fd_flags(O_CLOEXEC);
415412
}
416413

417414
static const struct file_operations iommufd_fault_fops =
@@ -452,7 +449,6 @@ int iommufd_fault_alloc(struct iommufd_ucmd *ucmd)
452449
return 0;
453450
out_put_fdno:
454451
put_unused_fd(fdno);
455-
fput(fault->common.filep);
456452
return rc;
457453
}
458454

@@ -536,7 +532,6 @@ int iommufd_veventq_alloc(struct iommufd_ucmd *ucmd)
536532

537533
out_put_fdno:
538534
put_unused_fd(fdno);
539-
fput(veventq->common.filep);
540535
out_abort:
541536
iommufd_object_abort_and_destroy(ucmd->ictx, &veventq->common.obj);
542537
out_unlock_veventqs:

drivers/iommu/iommufd/iommufd_private.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,8 @@ static inline void iommufd_hw_pagetable_put(struct iommufd_ctx *ictx,
454454
if (hwpt->obj.type == IOMMUFD_OBJ_HWPT_PAGING) {
455455
struct iommufd_hwpt_paging *hwpt_paging = to_hwpt_paging(hwpt);
456456

457-
lockdep_assert_not_held(&hwpt_paging->ioas->mutex);
458-
459457
if (hwpt_paging->auto_domain) {
458+
lockdep_assert_not_held(&hwpt_paging->ioas->mutex);
460459
iommufd_object_put_and_try_destroy(ictx, &hwpt->obj);
461460
return;
462461
}

drivers/iommu/iommufd/main.c

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "iommufd_test.h"
2424

2525
struct iommufd_object_ops {
26+
size_t file_offset;
2627
void (*pre_destroy)(struct iommufd_object *obj);
2728
void (*destroy)(struct iommufd_object *obj);
2829
void (*abort)(struct iommufd_object *obj);
@@ -121,6 +122,10 @@ void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj)
121122
old = xas_store(&xas, NULL);
122123
xa_unlock(&ictx->objects);
123124
WARN_ON(old != XA_ZERO_ENTRY);
125+
126+
if (WARN_ON(!refcount_dec_and_test(&obj->users)))
127+
return;
128+
124129
kfree(obj);
125130
}
126131

@@ -131,10 +136,30 @@ void iommufd_object_abort(struct iommufd_ctx *ictx, struct iommufd_object *obj)
131136
void iommufd_object_abort_and_destroy(struct iommufd_ctx *ictx,
132137
struct iommufd_object *obj)
133138
{
134-
if (iommufd_object_ops[obj->type].abort)
135-
iommufd_object_ops[obj->type].abort(obj);
139+
const struct iommufd_object_ops *ops = &iommufd_object_ops[obj->type];
140+
141+
if (ops->file_offset) {
142+
struct file **filep = ((void *)obj) + ops->file_offset;
143+
144+
/*
145+
* A file should hold a users refcount while the file is open
146+
* and put it back in its release. The file should hold a
147+
* pointer to obj in their private data. Normal fput() is
148+
* deferred to a workqueue and can get out of order with the
149+
* following kfree(obj). Using the sync version ensures the
150+
* release happens immediately. During abort we require the file
151+
* refcount is one at this point - meaning the object alloc
152+
* function cannot do anything to allow another thread to take a
153+
* refcount prior to a guaranteed success.
154+
*/
155+
if (*filep)
156+
__fput_sync(*filep);
157+
}
158+
159+
if (ops->abort)
160+
ops->abort(obj);
136161
else
137-
iommufd_object_ops[obj->type].destroy(obj);
162+
ops->destroy(obj);
138163
iommufd_object_abort(ictx, obj);
139164
}
140165

@@ -550,16 +575,23 @@ static int iommufd_fops_mmap(struct file *filp, struct vm_area_struct *vma)
550575
if (vma->vm_flags & VM_EXEC)
551576
return -EPERM;
552577

578+
mtree_lock(&ictx->mt_mmap);
553579
/* vma->vm_pgoff carries a page-shifted start position to an immap */
554580
immap = mtree_load(&ictx->mt_mmap, vma->vm_pgoff << PAGE_SHIFT);
555-
if (!immap)
581+
if (!immap || !refcount_inc_not_zero(&immap->owner->users)) {
582+
mtree_unlock(&ictx->mt_mmap);
556583
return -ENXIO;
584+
}
585+
mtree_unlock(&ictx->mt_mmap);
586+
557587
/*
558588
* mtree_load() returns the immap for any contained mmio_addr, so only
559589
* allow the exact immap thing to be mapped
560590
*/
561-
if (vma->vm_pgoff != immap->vm_pgoff || length != immap->length)
562-
return -ENXIO;
591+
if (vma->vm_pgoff != immap->vm_pgoff || length != immap->length) {
592+
rc = -ENXIO;
593+
goto err_refcount;
594+
}
563595

564596
vma->vm_pgoff = 0;
565597
vma->vm_private_data = immap;
@@ -570,10 +602,11 @@ static int iommufd_fops_mmap(struct file *filp, struct vm_area_struct *vma)
570602
immap->mmio_addr >> PAGE_SHIFT, length,
571603
vma->vm_page_prot);
572604
if (rc)
573-
return rc;
605+
goto err_refcount;
606+
return 0;
574607

575-
/* vm_ops.open won't be called for mmap itself. */
576-
refcount_inc(&immap->owner->users);
608+
err_refcount:
609+
refcount_dec(&immap->owner->users);
577610
return rc;
578611
}
579612

@@ -651,6 +684,12 @@ void iommufd_ctx_put(struct iommufd_ctx *ictx)
651684
}
652685
EXPORT_SYMBOL_NS_GPL(iommufd_ctx_put, "IOMMUFD");
653686

687+
#define IOMMUFD_FILE_OFFSET(_struct, _filep, _obj) \
688+
.file_offset = (offsetof(_struct, _filep) + \
689+
BUILD_BUG_ON_ZERO(!__same_type( \
690+
struct file *, ((_struct *)NULL)->_filep)) + \
691+
BUILD_BUG_ON_ZERO(offsetof(_struct, _obj)))
692+
654693
static const struct iommufd_object_ops iommufd_object_ops[] = {
655694
[IOMMUFD_OBJ_ACCESS] = {
656695
.destroy = iommufd_access_destroy_object,
@@ -661,6 +700,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
661700
},
662701
[IOMMUFD_OBJ_FAULT] = {
663702
.destroy = iommufd_fault_destroy,
703+
IOMMUFD_FILE_OFFSET(struct iommufd_fault, common.filep, common.obj),
664704
},
665705
[IOMMUFD_OBJ_HW_QUEUE] = {
666706
.destroy = iommufd_hw_queue_destroy,
@@ -683,6 +723,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
683723
[IOMMUFD_OBJ_VEVENTQ] = {
684724
.destroy = iommufd_veventq_destroy,
685725
.abort = iommufd_veventq_abort,
726+
IOMMUFD_FILE_OFFSET(struct iommufd_veventq, common.filep, common.obj),
686727
},
687728
[IOMMUFD_OBJ_VIOMMU] = {
688729
.destroy = iommufd_viommu_destroy,

tools/testing/selftests/iommu/iommufd_fail_nth.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ static bool fail_nth_next(struct __test_metadata *_metadata,
113113
* necessarily mean a test failure, just that the limit has to be made
114114
* bigger.
115115
*/
116-
ASSERT_GT(400, nth_state->iteration);
116+
ASSERT_GT(1000, nth_state->iteration);
117117
if (nth_state->iteration != 0) {
118118
ssize_t res;
119119
ssize_t res2;

0 commit comments

Comments
 (0)