History

drm/gem: Try to fix change_handle ioctl, attempt 4

[airlied: just added some comments on how to reenable] On-list because the cat is out of the bag and we're clearly not good enough to figure this out in private. The story thus far: 5e28b7b94408 ("drm: Set old handle to NULL before prime swap in change_handle") tried to fix a race condition between the gem_close and gem_change_handle ioctls, but got a few things wrong: - There's a confusion with the local variable handle, which is actually the new handle, and so the two-stage trick was actually applied to the wrong idr slot. 7164d78559b0 ("drm/gem: fix race between change_handle and handle_delete") tried to fix that by adding yet another code block, but forgot to add the error handling. Which meant we now have two paths, both kinda wrong. - dc366607c41c ("drm: Replace old pointer to new idr") tried to apply another fix, but inconsistently, again because of the handle confusion - this would be the right fix (kinda, somewhat, it's a mess) if we'd do the two-stage approach for the new handle. Except that wasn't the intent of the original fix. We also didn't have an igt merged for the original ioctl, which is a big no-go. This was attempted to address off-list in the original bugfix, and amd QA people claimed the bug was fixed now. Very clearly that's not the case. Here's my attempt to sort this out: - Rename the local variable to new_handle, the old aliasing with args->handle is just too dangerously confusing. - Merge the gem obj lookup with the two-stage idr_replace so that we avoid getting ourselves confused there. - This means we don't have a surplus temporary reference anymore, only an inherited from the idr. A concurrent gem_close on the new_handle could steal that. Fix that with the same two-stage approach create_tail uses. This is a bit overkill as documented in the comment, but I also don't trust my ability to understand this all correctly, so go with the established pattern we have from other ioctls instead for maximum paranoia. - Adjust error paths. I've tried to make the error and success paths common, because they are identical except for which handle is removed and on which we call idr_replace to (re)install the object again. But that made things messier to read, so I've left it at the more verbose version, which unfortunately hides the symmetry in the entire code flow a bit. - While at it, also replace the 7 space indent with 1 tab. And finally, because I flat out don't trust my abilities here at all anymore: - Disable the ioctl until we have the igt situation and everything else sorted out on-list and with full consensus. v2: Sashiko noticed that I didn't handle the error path for idr_replace correctly, it must be checked with IS_ERR_OR_NULL like in gem_handle_delete. So yeah, definitely should just the existing paths 1:1 because this is endless amounts of tricky. Also add the Fixes: line for the original ioctl, I forgot that too.

Simona Vetter committed 5 months ago

Commit 1a4f03d1 parent 8ff3adc2 files · +37 −41Browse files

Unified · Split

2 files
  1. drivers/gpu/drm/drm_gem.c +35 −40
  2. drivers/gpu/drm/drm_ioctl.c +2 −1
drivers/gpu/drm/drm_gem.c+35−40
@@ -1015,12 +1015,25 @@ err: · 1014 unmodified lines
10151015 return ret;
10161016 }
10171017
1018+/*
1019+ * This ioctl is disabled for security reasons but also it failed
1020+ * to follow process in terms of adding testing in igt and verifying
1021+ * all the corner cases which made fixing security bugs in it even
1022+ * harder than necessary.
1023+ *
1024+ * To re-enable this ioctl
1025+ * 1. land working IGT tests in igt-gpu-tools that cover
1026+ * all corner cases and race conditions.
1027+ * 2. handle idr_preload
1028+ * 3. handle == 0
1029+ * 4. handle == new_handle semantics definition.
1030+ */
10181031 int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
10191032 struct drm_file *file_priv)
10201033 {
10211034 struct drm_gem_change_handle *args = data;
1022− struct drm_gem_object *obj, *idrobj;
1023− int handle, ret;
1035+ struct drm_gem_object *obj;
1036+ int new_handle, ret;
10241037
10251038 if (!drm_core_check_feature(dev, DRIVER_GEM))
10261039 return -EOPNOTSUPP;
@@ -1028,52 +1041,36 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, · 1 unmodified line
10281041 /* idr_alloc() limitation. */
10291042 if (args->new_handle > INT_MAX)
10301043 return -EINVAL;
1031− handle = args->new_handle;
1032−
1033− obj = drm_gem_object_lookup(file_priv, args->handle);
1034− if (!obj)
1035− return -ENOENT;
1044+ new_handle = args->new_handle;
10361045
1037− if (args->handle == handle) {
1038− ret = 0;
1039− goto out;
1040− }
1046+ if (args->handle == new_handle)
1047+ return 0;
10411048
10421049 mutex_lock(&file_priv->prime.lock);
1043−
10441050 spin_lock(&file_priv->table_lock);
1045−
1046− /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
1047− * then later replace with the correct object. This is not necessary
1048− * here, because the only operations that could race are drm_prime
1049− * bookkeeping, and we hold the prime lock.
1050− */
1051− ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
1051+ ret = idr_alloc(&file_priv->object_idr, NULL, new_handle, new_handle + 1,
10521052 GFP_NOWAIT);
10531053
1054− if (ret < 0) {
1055− spin_unlock(&file_priv->table_lock);
1056− goto out_unlock;
1057− }
1058−
1059− idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
1060− if (idrobj != obj) {
1061− idr_replace(&file_priv->object_idr, idrobj, handle);
1062− idr_remove(&file_priv->object_idr, args->new_handle);
1063− spin_unlock(&file_priv->table_lock);
1064− ret = -ENOENT;
1065− goto out_unlock;
1066− }
1067−
1068− idr_replace(&file_priv->object_idr, NULL, args->handle);
1054+ if (ret < 0) {
1055+ spin_unlock(&file_priv->table_lock);
1056+ goto out_unlock;
1057+ }
1058+
1059+ obj = idr_replace(&file_priv->object_idr, NULL, args->handle);
1060+ if (IS_ERR_OR_NULL(obj)) {
1061+ idr_remove(&file_priv->object_idr, new_handle);
1062+ spin_unlock(&file_priv->table_lock);
1063+ ret = -ENOENT;
1064+ goto out_unlock;
1065+ }
10691066 spin_unlock(&file_priv->table_lock);
10701067
10711068 if (obj->dma_buf) {
10721069 ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf,
1073− handle);
1070+ new_handle);
10741071 if (ret < 0) {
10751072 spin_lock(&file_priv->table_lock);
1076− idr_remove(&file_priv->object_idr, handle);
1073+ idr_remove(&file_priv->object_idr, new_handle);
10771074 idr_replace(&file_priv->object_idr, obj, args->handle);
10781075 spin_unlock(&file_priv->table_lock);
10791076 goto out_unlock;
@@ -1086,14 +1083,12 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, · 6 unmodified lines
10861083
10871084 spin_lock(&file_priv->table_lock);
10881085 idr_remove(&file_priv->object_idr, args->handle);
1089− idrobj = idr_replace(&file_priv->object_idr, obj, handle);
1086+ obj = idr_replace(&file_priv->object_idr, obj, new_handle);
10901087 spin_unlock(&file_priv->table_lock);
1091− WARN_ON(idrobj != NULL);
1088+ WARN_ON(obj != NULL);
10921089
10931090 out_unlock:
10941091 mutex_unlock(&file_priv->prime.lock);
1095−out:
1096− drm_gem_object_put(obj);
10971092
10981093 return ret;
10991094 }
drivers/gpu/drm/drm_ioctl.c+2−1
@@ -660,7 +660,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = { · 659 unmodified lines
660660 DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, DRM_RENDER_ALLOW),
661661 DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),
662662 DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH),
663− DRM_IOCTL_DEF(DRM_IOCTL_GEM_CHANGE_HANDLE, drm_gem_change_handle_ioctl, DRM_RENDER_ALLOW),
663+ /* see drm_gem.c:drm_gem_change_handle_ioctl for why this is invalid */
664+ DRM_IOCTL_DEF(DRM_IOCTL_GEM_CHANGE_HANDLE, drm_invalid_op, DRM_RENDER_ALLOW),
664665
665666 DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
666667

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t