Skip to content

Commit bd46cec

Browse files
committed
drm/gem: Fix race in drm_gem_handle_create_tail()
Object creation is a careful dance where we must guarantee that the object is fully constructed before it is visible to other threads, and GEM buffer objects are no difference. Final publishing happens by calling drm_gem_handle_create(). After that the only allowed thing to do is call drm_gem_object_put() because a concurrent call to the GEM_CLOSE ioctl with a correctly guessed id (which is trivial since we have a linear allocator) can already tear down the object again. Luckily most drivers get this right, the very few exceptions I've pinged the relevant maintainers for. Unfortunately we also need drm_gem_handle_create() when creating additional handles for an already existing object (e.g. GETFB ioctl or the various bo import ioctl), and hence we cannot have a drm_gem_handle_create_and_put() as the only exported function to stop these issues from happening. Now unfortunately the implementation of drm_gem_handle_create() isn't living up to standards: It does correctly finishe object initialization at the global level, and hence is safe against a concurrent tear down. But it also sets up the file-private aspects of the handle, and that part goes wrong: We fully register the object in the drm_file.object_idr before calling drm_vma_node_allow() or obj->funcs->open, which opens up races against concurrent removal of that handle in drm_gem_handle_delete(). Fix this with the usual two-stage approach of first reserving the handle id, and then only registering the object after we've completed the file-private setup. Jacek reported this with a testcase of concurrently calling GEM_CLOSE on a freshly-created object (which also destroys the object), but it should be possible to hit this with just additional handles created through import or GETFB without completed destroying the underlying object with the concurrent GEM_CLOSE ioctl calls. Note that the close-side of this race was fixed in f6cd7da ("drm: Release driver references to handle before making it available again"), which means a cool 9 years have passed until someone noticed that we need to make this symmetry or there's still gaps left :-/ Without the 2-stage close approach we'd still have a race, therefore that's an integral part of this bugfix. More importantly, this means we can have NULL pointers behind allocated id in our drm_file.object_idr. We need to check for that now: - drm_gem_handle_delete() checks for ERR_OR_NULL already - drm_gem.c:object_lookup() also chekcs for NULL - drm_gem_release() should never be called if there's another thread still existing that could call into an IOCTL that creates a new handle, so cannot race. For paranoia I added a NULL check to drm_gem_object_release_handle() though. - most drivers (etnaviv, i915, msm) are find because they use idr_find(), which maps both ENOENT and NULL to NULL. - drivers using idr_for_each_entry() should also be fine, because idr_get_next does filter out NULL entries and continues the iteration. - The same holds for drm_show_memory_stats(). v2: Use drm_WARN_ON (Thomas) Reported-by: Jacek Lawrynowicz <[email protected]> Tested-by: Jacek Lawrynowicz <[email protected]> Reviewed-by: Thomas Zimmermann <[email protected]> Cc: [email protected] Cc: Jacek Lawrynowicz <[email protected]> Cc: Maarten Lankhorst <[email protected]> Cc: Maxime Ripard <[email protected]> Cc: Thomas Zimmermann <[email protected]> Cc: David Airlie <[email protected]> Cc: Simona Vetter <[email protected]> Signed-off-by: Simona Vetter <[email protected]> Signed-off-by: Simona Vetter <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent f6bfc9a commit bd46cec

File tree

2 files changed

+12
-1
lines changed

2 files changed

+12
-1
lines changed

drivers/gpu/drm/drm_gem.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ drm_gem_object_release_handle(int id, void *ptr, void *data)
325325
struct drm_file *file_priv = data;
326326
struct drm_gem_object *obj = ptr;
327327

328+
if (drm_WARN_ON(obj->dev, !data))
329+
return 0;
330+
328331
if (obj->funcs->close)
329332
obj->funcs->close(obj, file_priv);
330333

@@ -445,7 +448,7 @@ drm_gem_handle_create_tail(struct drm_file *file_priv,
445448
idr_preload(GFP_KERNEL);
446449
spin_lock(&file_priv->table_lock);
447450

448-
ret = idr_alloc(&file_priv->object_idr, obj, 1, 0, GFP_NOWAIT);
451+
ret = idr_alloc(&file_priv->object_idr, NULL, 1, 0, GFP_NOWAIT);
449452

450453
spin_unlock(&file_priv->table_lock);
451454
idr_preload_end();
@@ -466,6 +469,11 @@ drm_gem_handle_create_tail(struct drm_file *file_priv,
466469
goto err_revoke;
467470
}
468471

472+
/* mirrors drm_gem_handle_delete to avoid races */
473+
spin_lock(&file_priv->table_lock);
474+
obj = idr_replace(&file_priv->object_idr, obj, handle);
475+
WARN_ON(obj != NULL);
476+
spin_unlock(&file_priv->table_lock);
469477
*handlep = handle;
470478
return 0;
471479

include/drm/drm_file.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ struct drm_file {
300300
*
301301
* Mapping of mm object handles to object pointers. Used by the GEM
302302
* subsystem. Protected by @table_lock.
303+
*
304+
* Note that allocated entries might be NULL as a transient state when
305+
* creating or deleting a handle.
303306
*/
304307
struct idr object_idr;
305308

0 commit comments

Comments
 (0)