drm/i915/userptr: add user_size limit check
[linux-2.6-microblaze.git] / drivers / gpu / drm / i915 / gem / i915_gem_userptr.c
index 4c72d74..b64b0f3 100644 (file)
@@ -129,9 +129,10 @@ userptr_mn_invalidate_range_start(struct mmu_notifier *_mn,
                spin_unlock(&mn->lock);
 
                ret = i915_gem_object_unbind(obj,
-                                            I915_GEM_OBJECT_UNBIND_ACTIVE);
+                                            I915_GEM_OBJECT_UNBIND_ACTIVE |
+                                            I915_GEM_OBJECT_UNBIND_BARRIER);
                if (ret == 0)
-                       ret = __i915_gem_object_put_pages(obj, I915_MM_SHRINKER);
+                       ret = __i915_gem_object_put_pages(obj);
                i915_gem_object_put(obj);
                if (ret)
                        return ret;
@@ -459,31 +460,36 @@ __i915_gem_userptr_get_pages_worker(struct work_struct *_work)
        if (pvec != NULL) {
                struct mm_struct *mm = obj->userptr.mm->mm;
                unsigned int flags = 0;
+               int locked = 0;
 
                if (!i915_gem_object_is_readonly(obj))
                        flags |= FOLL_WRITE;
 
                ret = -EFAULT;
                if (mmget_not_zero(mm)) {
-                       down_read(&mm->mmap_sem);
                        while (pinned < npages) {
+                               if (!locked) {
+                                       down_read(&mm->mmap_sem);
+                                       locked = 1;
+                               }
                                ret = get_user_pages_remote
                                        (work->task, mm,
                                         obj->userptr.ptr + pinned * PAGE_SIZE,
                                         npages - pinned,
                                         flags,
-                                        pvec + pinned, NULL, NULL);
+                                        pvec + pinned, NULL, &locked);
                                if (ret < 0)
                                        break;
 
                                pinned += ret;
                        }
-                       up_read(&mm->mmap_sem);
+                       if (locked)
+                               up_read(&mm->mmap_sem);
                        mmput(mm);
                }
        }
 
-       mutex_lock(&obj->mm.lock);
+       mutex_lock_nested(&obj->mm.lock, I915_MM_GET_PAGES);
        if (obj->userptr.work == &work->work) {
                struct sg_table *pages = ERR_PTR(ret);
 
@@ -763,6 +769,23 @@ i915_gem_userptr_ioctl(struct drm_device *dev,
                            I915_USERPTR_UNSYNCHRONIZED))
                return -EINVAL;
 
+       /*
+        * XXX: There is a prevalence of the assumption that we fit the
+        * object's page count inside a 32bit _signed_ variable. Let's document
+        * this and catch if we ever need to fix it. In the meantime, if you do
+        * spot such a local variable, please consider fixing!
+        *
+        * Aside from our own locals (for which we have no excuse!):
+        * - sg_table embeds unsigned int for num_pages
+        * - get_user_pages*() mixed ints with longs
+        */
+
+       if (args->user_size >> PAGE_SHIFT > INT_MAX)
+               return -E2BIG;
+
+       if (overflows_type(args->user_size, obj->base.size))
+               return -E2BIG;
+
        if (!args->user_size)
                return -EINVAL;
 
@@ -773,15 +796,11 @@ i915_gem_userptr_ioctl(struct drm_device *dev,
                return -EFAULT;
 
        if (args->flags & I915_USERPTR_READ_ONLY) {
-               struct i915_address_space *vm;
-
                /*
                 * On almost all of the older hw, we cannot tell the GPU that
                 * a page is readonly.
                 */
-               vm = rcu_dereference_protected(dev_priv->kernel_context->vm,
-                                              true); /* static vm */
-               if (!vm || !vm->has_read_only)
+               if (!dev_priv->gt.vm->has_read_only)
                        return -ENODEV;
        }