[PATCH] drm/i915: Prevent double unref following alloc failure during execbuffer

Chris Wilson
2013-12-04T09:52:58Z

Whilst looking up the objects required for an execbuffer, an untimely
allocation failure in creating the vma results in the object being
unreferenced from two lists. The ownership during the lookup is meant to
be moved from the list of objects being looked to the vma, and this
double unreference upon error results in a use-after-free.

Fixes regression from
commit 27173f1f95db5e74ceb35fe9a2f2f348ea11bac9
Author: Ben Widawsky <ben@bwidawsk.net>
Date: Wed Aug 14 11:38:36 2013 +0200

drm/i915: Convert execbuf code to use vmas

Based on the fix by Ben Widawsky.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Cc: Ben Widawsky <ben@bwidawsk.net>
Cc: stable@vger.kernel.org
---
drivers/gpu/drm/i915/i915_gem_execbuffer.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/i915_gem_execbuffer.c
index c7af37187dee..5663b873a1aa 100644
--- a/drivers/gpu/drm/i915/i915_gem_execbuffer.c
+++ b/drivers/gpu/drm/i915/i915_gem_execbuffer.c
@@ -94,7 +94,7 @@ eb_lookup_vmas(struct eb_vmas *eb,
struct drm_i915_private *dev_priv = vm->dev->dev_private;
struct drm_i915_gem_object *obj;
struct list_head objects;
- int i, ret = 0;
+ int i, ret;

INIT_LIST_HEAD(&objects);
spin_lock(&file->table_lock);
@@ -107,7 +107,7 @@ eb_lookup_vmas(struct eb_vmas *eb,
DRM_DEBUG("Invalid object handle %d at index %d\n",
exec[i].handle, i);
ret = -ENOENT;
- goto out;
+ goto err;
}

if (!list_empty(&obj->obj_exec_link)) {
@@ -115,7 +115,7 @@ eb_lookup_vmas(struct eb_vmas *eb,
DRM_DEBUG("Object %p [handle %d, index %d] appears more than once in object list\n",
obj, exec[i].handle, i);
ret = -EINVAL;
- goto out;
+ goto err;
}

drm_gem_object_reference(&obj->base);
@@ -124,7 +124,7 @@ eb_lookup_vmas(struct eb_vmas *eb,
spin_unlock(&file->table_lock);

i = 0;
- list_for_each_entry(obj, &objects, obj_exec_link) {
+ while (!list_empty(&objects)) {
struct i915_vma *vma;
struct i915_address_space *bind_vm = vm;

@@ -136,6 +136,10 @@ eb_lookup_vmas(struct eb_vmas *eb,
(i == (args->buffer_count - 1))))
bind_vm = &dev_priv->gtt.base;

+ obj = list_first_entry(&objects,
+ struct drm_i915_gem_object,
+ obj_exec_link);
+
/*
* NOTE: We can leak any vmas created here when something fails
* later on. But that's no issue since vma_unbind can deal with
@@ -148,10 +152,12 @@ eb_lookup_vmas(struct eb_vmas *eb,
if (IS_ERR(vma)) {
DRM_DEBUG("Failed to lookup VMA\n");
ret = PTR_ERR(vma);
- goto out;
+ goto err;
}

+ /* Transfer ownership from objects to the vma */
list_add_tail(&vma->exec_list, &eb->vmas);
+ list_del_init(&obj->obj_exec_link);

vma->exec_entry = &exec[i];
if (eb->and < 0) {
@@ -165,16 +171,18 @@ eb_lookup_vmas(struct eb_vmas *eb,
++i;
}

+ return 0;

-out:
+
+err:
while (!list_empty(&objects)) {
obj = list_first_entry(&objects,
struct drm_i915_gem_object,
obj_exec_link);
list_del_init(&obj->obj_exec_link);
- if (ret)
- drm_gem_object_unreference(&obj->base);
+ drm_gem_object_unreference(&obj->base);
}
+ /* objects already transfered to the vma will be reaped by eb_destroy */
return ret;
}

--
1.8.5