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
1015 return ret;1015 return ret;
1016}1016}
1017 1017
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 */
1018int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,1031int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
1019 struct drm_file *file_priv)1032 struct drm_file *file_priv)
1020{1033{
1021 struct drm_gem_change_handle *args = data;1034 struct drm_gem_change_handle *args = data;
1022 struct drm_gem_object *obj, *idrobj;1035 struct drm_gem_object *obj;
1023 int handle, ret;1036 int new_handle, ret;
1024 1037
1025 if (!drm_core_check_feature(dev, DRIVER_GEM))1038 if (!drm_core_check_feature(dev, DRIVER_GEM))
1026 return -EOPNOTSUPP;1039 return -EOPNOTSUPP;
@@ -1028,52 +1041,36 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, · 1 unmodified line
1028 /* idr_alloc() limitation. */1041 /* idr_alloc() limitation. */
1029 if (args->new_handle > INT_MAX)1042 if (args->new_handle > INT_MAX)
1030 return -EINVAL;1043 return -EINVAL;
1031 handle = args->new_handle;1044 new_handle = args->new_handle;
1032
1033 obj = drm_gem_object_lookup(file_priv, args->handle);
1034 if (!obj)
1035 return -ENOENT;
1036 1045
1037 if (args->handle == handle) {1046 if (args->handle == new_handle)
1038 ret = 0;1047 return 0;
1039 goto out;
1040 }
1041 1048
1042 mutex_lock(&file_priv->prime.lock);1049 mutex_lock(&file_priv->prime.lock);
1043
1044 spin_lock(&file_priv->table_lock);1050 spin_lock(&file_priv->table_lock);
1045 1051 ret = idr_alloc(&file_priv->object_idr, NULL, new_handle, new_handle + 1,
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,
1052 GFP_NOWAIT);1052 GFP_NOWAIT);
1053 1053
1054 if (ret < 0) {1054 if (ret < 0) {
1055 spin_unlock(&file_priv->table_lock);1055 spin_unlock(&file_priv->table_lock);
1056 goto out_unlock;1056 goto out_unlock;
1057 }1057 }
1058 1058
1059 idrobj = idr_replace(&file_priv->object_idr, NULL, handle);1059 obj = idr_replace(&file_priv->object_idr, NULL, args->handle);
1060 if (idrobj != obj) {1060 if (IS_ERR_OR_NULL(obj)) {
1061 idr_replace(&file_priv->object_idr, idrobj, handle);1061 idr_remove(&file_priv->object_idr, new_handle);
1062 idr_remove(&file_priv->object_idr, args->new_handle);1062 spin_unlock(&file_priv->table_lock);
1063 spin_unlock(&file_priv->table_lock);1063 ret = -ENOENT;
1064 ret = -ENOENT;1064 goto out_unlock;
1065 goto out_unlock;1065 }
1066 }
1067
1068 idr_replace(&file_priv->object_idr, NULL, args->handle);
1069 spin_unlock(&file_priv->table_lock);1066 spin_unlock(&file_priv->table_lock);
1070 1067
1071 if (obj->dma_buf) {1068 if (obj->dma_buf) {
1072 ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf,1069 ret = drm_prime_add_buf_handle(&file_priv->prime, obj->dma_buf,
1073 handle);1070 new_handle);
1074 if (ret < 0) {1071 if (ret < 0) {
1075 spin_lock(&file_priv->table_lock);1072 spin_lock(&file_priv->table_lock);
1076 idr_remove(&file_priv->object_idr, handle);1073 idr_remove(&file_priv->object_idr, new_handle);
1077 idr_replace(&file_priv->object_idr, obj, args->handle);1074 idr_replace(&file_priv->object_idr, obj, args->handle);
1078 spin_unlock(&file_priv->table_lock);1075 spin_unlock(&file_priv->table_lock);
1079 goto out_unlock;1076 goto out_unlock;
@@ -1086,14 +1083,12 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data, · 6 unmodified lines
1086 1083
1087 spin_lock(&file_priv->table_lock);1084 spin_lock(&file_priv->table_lock);
1088 idr_remove(&file_priv->object_idr, args->handle);1085 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);
1090 spin_unlock(&file_priv->table_lock);1087 spin_unlock(&file_priv->table_lock);
1091 WARN_ON(idrobj != NULL);1088 WARN_ON(obj != NULL);
1092 1089
1093out_unlock:1090out_unlock:
1094 mutex_unlock(&file_priv->prime.lock);1091 mutex_unlock(&file_priv->prime.lock);
1095out:
1096 drm_gem_object_put(obj);
1097 1092
1098 return ret;1093 return ret;
1099}1094}
drivers/gpu/drm/drm_ioctl.c+2−1
@@ -660,7 +660,8 @@ static const struct drm_ioctl_desc drm_ioctls[] = { · 659 unmodified lines
660 DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, DRM_RENDER_ALLOW),660 DRM_IOCTL_DEF(DRM_IOCTL_GEM_CLOSE, drm_gem_close_ioctl, DRM_RENDER_ALLOW),
661 DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),661 DRM_IOCTL_DEF(DRM_IOCTL_GEM_FLINK, drm_gem_flink_ioctl, DRM_AUTH),
662 DRM_IOCTL_DEF(DRM_IOCTL_GEM_OPEN, drm_gem_open_ioctl, DRM_AUTH),662 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),
664 665
665 DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),666 DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, 0),
666 667

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