Skip to content

Commit 1c8782d

Browse files
committed
drm/i915/userptr: Disallow wrapping GTT into a userptr
If we allow the user to convert a GTT mmap address into a userptr, we may end up in recursion hell, where currently we hit a mutex deadlock but other possibilities include use-after-free during the unbind/cancel_userptr. [ 143.203989] gem_userptr_bli D 0 902 898 0x00000000 [ 143.204054] Call Trace: [ 143.204137] __schedule+0x511/0x1180 [ 143.204195] ? pci_mmcfg_check_reserved+0xc0/0xc0 [ 143.204274] schedule+0x57/0xe0 [ 143.204327] schedule_timeout+0x383/0x670 [ 143.204374] ? trace_hardirqs_on_caller+0x187/0x280 [ 143.204457] ? trace_hardirqs_on_thunk+0x1a/0x1c [ 143.204507] ? usleep_range+0x110/0x110 [ 143.204657] ? irq_exit+0x89/0x100 [ 143.204710] ? retint_kernel+0x2d/0x2d [ 143.204794] ? trace_hardirqs_on_caller+0x187/0x280 [ 143.204857] ? _raw_spin_unlock_irq+0x33/0x60 [ 143.204944] wait_for_common+0x1f0/0x2f0 [ 143.205006] ? out_of_line_wait_on_atomic_t+0x170/0x170 [ 143.205103] ? wake_up_q+0xa0/0xa0 [ 143.205159] ? flush_workqueue_prep_pwqs+0x15a/0x2c0 [ 143.205237] wait_for_completion+0x1d/0x20 [ 143.205292] flush_workqueue+0x2e9/0xbb0 [ 143.205339] ? flush_workqueue+0x163/0xbb0 [ 143.205418] ? __schedule+0x533/0x1180 [ 143.205498] ? check_flush_dependency+0x1a0/0x1a0 [ 143.205681] i915_gem_userptr_mn_invalidate_range_start+0x1c7/0x270 [i915] [ 143.205865] ? i915_gem_userptr_dmabuf_export+0x40/0x40 [i915] [ 143.205955] __mmu_notifier_invalidate_range_start+0xc6/0x120 [ 143.206044] ? __mmu_notifier_invalidate_range_start+0x51/0x120 [ 143.206123] zap_page_range_single+0x1c7/0x1f0 [ 143.206171] ? unmap_single_vma+0x160/0x160 [ 143.206260] ? unmap_mapping_range+0xa9/0x1b0 [ 143.206308] ? vma_interval_tree_subtree_search+0x75/0xd0 [ 143.206397] unmap_mapping_range+0x18f/0x1b0 [ 143.206444] ? zap_vma_ptes+0x70/0x70 [ 143.206524] ? __pm_runtime_resume+0x67/0xa0 [ 143.206723] i915_gem_release_mmap+0x1ba/0x1c0 [i915] [ 143.206846] i915_vma_unbind+0x5c2/0x690 [i915] [ 143.206925] ? __lock_is_held+0x52/0x100 [ 143.207076] i915_gem_object_set_tiling+0x1db/0x650 [i915] [ 143.207236] i915_gem_set_tiling_ioctl+0x1d3/0x3b0 [i915] [ 143.207377] ? i915_gem_set_tiling_ioctl+0x5/0x3b0 [i915] [ 143.207457] drm_ioctl+0x36c/0x670 [ 143.207535] ? debug_lockdep_rcu_enabled.part.0+0x1a/0x30 [ 143.207730] ? i915_gem_object_set_tiling+0x650/0x650 [i915] [ 143.207793] ? drm_getunique+0x120/0x120 [ 143.207875] ? __handle_mm_fault+0x996/0x14a0 [ 143.207939] ? vm_insert_page+0x340/0x340 [ 143.208028] ? up_write+0x28/0x50 [ 143.208086] ? vm_mmap_pgoff+0x160/0x190 [ 143.208163] do_vfs_ioctl+0x12c/0xa60 [ 143.208218] ? debug_lockdep_rcu_enabled+0x35/0x40 [ 143.208267] ? ioctl_preallocate+0x150/0x150 [ 143.208353] ? __do_page_fault+0x36a/0x6e0 [ 143.208400] ? mark_held_locks+0x23/0xc0 [ 143.208479] ? up_read+0x1f/0x40 [ 143.208526] ? entry_SYSCALL_64_fastpath+0x5/0xc6 [ 143.208669] ? __fget_light+0xa7/0xc0 [ 143.208747] SyS_ioctl+0x41/0x70 To prevent the possibility of a deadlock, we defer scheduling the worker until after we have proven that given the current mm, the userptr range does not overlap a GGTT mmaping. If another thread tries to remap the GGTT over the userptr before the worker is scheduled, it will be stopped by its invalidate-range flushing the current work, before the deadlock can occur. v2: Improve discussion of how we end up in the deadlock. v3: Don't forget to mark the userptr as active after a successful gup_fast. Rename overlaps_ggtt to noncontiguous_or_overlaps_ggtt. v4: Fix test ordering between invalid GTT mmaping and range completion (Tvrtko) Reported-by: Michał Winiarski <[email protected]> Testcase: igt/gem_userptr_blits/map-fixed-invalidate-gup Signed-off-by: Chris Wilson <[email protected]> Cc: Michał Winiarski <[email protected]> Cc: Tvrtko Ursulin <[email protected]> Link: http://patchwork.freedesktop.org/patch/msgid/[email protected] Reviewed-by: Tvrtko Ursulin <[email protected]>
1 parent d151e9c commit 1c8782d

File tree

1 file changed

+62
-26
lines changed

1 file changed

+62
-26
lines changed

drivers/gpu/drm/i915/i915_gem_userptr.c

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,37 @@ __i915_gem_userptr_set_active(struct drm_i915_gem_object *obj,
488488
return ret;
489489
}
490490

491+
static bool noncontiguous_or_overlaps_ggtt(struct drm_i915_gem_object *obj,
492+
struct mm_struct *mm)
493+
{
494+
const struct vm_operations_struct *gem_vm_ops =
495+
obj->base.dev->driver->gem_vm_ops;
496+
unsigned long addr = obj->userptr.ptr;
497+
const unsigned long end = addr + obj->base.size;
498+
struct vm_area_struct *vma;
499+
500+
/* Check for a contiguous set of vma covering the userptr, if any
501+
* are absent, they will EFAULT. More importantly if any point back
502+
* to a drm_i915_gem_object GTT mmaping, we may trigger a deadlock
503+
* between the deferred gup of this userptr and the object being
504+
* unbound calling invalidate_range -> cancel_userptr.
505+
*/
506+
for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) {
507+
if (vma->vm_start > addr) /* gap */
508+
break;
509+
510+
if (vma->vm_ops == gem_vm_ops) /* GTT mmapping */
511+
break;
512+
513+
if (vma->vm_end >= end)
514+
return false;
515+
516+
addr = vma->vm_end;
517+
}
518+
519+
return true;
520+
}
521+
491522
static void
492523
__i915_gem_userptr_get_pages_worker(struct work_struct *_work)
493524
{
@@ -556,8 +587,7 @@ __i915_gem_userptr_get_pages_worker(struct work_struct *_work)
556587
}
557588

558589
static struct sg_table *
559-
__i915_gem_userptr_get_pages_schedule(struct drm_i915_gem_object *obj,
560-
bool *active)
590+
__i915_gem_userptr_get_pages_schedule(struct drm_i915_gem_object *obj)
561591
{
562592
struct get_pages_work *work;
563593

@@ -594,18 +624,18 @@ __i915_gem_userptr_get_pages_schedule(struct drm_i915_gem_object *obj,
594624
INIT_WORK(&work->work, __i915_gem_userptr_get_pages_worker);
595625
schedule_work(&work->work);
596626

597-
*active = true;
598627
return ERR_PTR(-EAGAIN);
599628
}
600629

601630
static struct sg_table *
602631
i915_gem_userptr_get_pages(struct drm_i915_gem_object *obj)
603632
{
604633
const int num_pages = obj->base.size >> PAGE_SHIFT;
634+
struct mm_struct *mm = obj->userptr.mm->mm;
605635
struct page **pvec;
606636
struct sg_table *pages;
607-
int pinned, ret;
608637
bool active;
638+
int pinned;
609639

610640
/* If userspace should engineer that these pages are replaced in
611641
* the vma between us binding this page into the GTT and completion
@@ -632,37 +662,43 @@ i915_gem_userptr_get_pages(struct drm_i915_gem_object *obj)
632662
return ERR_PTR(-EAGAIN);
633663
}
634664

635-
/* Let the mmu-notifier know that we have begun and need cancellation */
636-
ret = __i915_gem_userptr_set_active(obj, true);
637-
if (ret)
638-
return ERR_PTR(ret);
639-
640665
pvec = NULL;
641666
pinned = 0;
642-
if (obj->userptr.mm->mm == current->mm) {
643-
pvec = drm_malloc_gfp(num_pages, sizeof(struct page *),
644-
GFP_TEMPORARY);
645-
if (pvec == NULL) {
646-
__i915_gem_userptr_set_active(obj, false);
647-
return ERR_PTR(-ENOMEM);
648-
}
649667

650-
pinned = __get_user_pages_fast(obj->userptr.ptr, num_pages,
651-
!obj->userptr.read_only, pvec);
668+
down_read(&mm->mmap_sem);
669+
if (unlikely(noncontiguous_or_overlaps_ggtt(obj, mm))) {
670+
pinned = -EFAULT;
671+
} else if (mm == current->mm) {
672+
pvec = drm_malloc_gfp(num_pages, sizeof(struct page *),
673+
GFP_TEMPORARY |
674+
__GFP_NORETRY |
675+
__GFP_NOWARN);
676+
if (pvec) /* defer to worker if malloc fails */
677+
pinned = __get_user_pages_fast(obj->userptr.ptr,
678+
num_pages,
679+
!obj->userptr.read_only,
680+
pvec);
652681
}
653682

654683
active = false;
655-
if (pinned < 0)
656-
pages = ERR_PTR(pinned), pinned = 0;
657-
else if (pinned < num_pages)
658-
pages = __i915_gem_userptr_get_pages_schedule(obj, &active);
659-
else
684+
if (pinned < 0) {
685+
pages = ERR_PTR(pinned);
686+
pinned = 0;
687+
} else if (pinned < num_pages) {
688+
pages = __i915_gem_userptr_get_pages_schedule(obj);
689+
active = pages == ERR_PTR(-EAGAIN);
690+
} else {
660691
pages = __i915_gem_userptr_set_pages(obj, pvec, num_pages);
661-
if (IS_ERR(pages)) {
662-
__i915_gem_userptr_set_active(obj, active);
663-
release_pages(pvec, pinned, 0);
692+
active = !IS_ERR(pages);
664693
}
694+
if (active)
695+
__i915_gem_userptr_set_active(obj, true);
696+
up_read(&mm->mmap_sem);
697+
698+
if (IS_ERR(pages))
699+
release_pages(pvec, pinned, 0);
665700
drm_free_large(pvec);
701+
666702
return pages;
667703
}
668704

0 commit comments

Comments
 (0)