VYPR
Unrated severityNVD Advisory· Published May 28, 2026

CVE-2026-46215

CVE-2026-46215

Description

In the Linux kernel, the following vulnerability has been resolved:

drm: Set old handle to NULL before prime swap in change_handle

There was a potential race condition in change_handle. The ioctl briefly had a single object with two idr entries; a concurrent gem_close could delete the object and remove one of the handles while leaving the other one dangling, which could subsequently be dereferenced for a use-after-free.

To fix this, do the same dance that gem_close itself does. (f6cd7daecff5 drm: Release driver references to handle before making it available again) First idr_replace the old handle to NULL. Later, if the prime operations are successful, actually close it.

create_tail required a similar dance to avoid a similar problem. (bd46cece51a3 drm/gem: Fix race in drm_gem_handle_create_tail()) It idr_allocs the new handle with NULL, then swaps in the correct object later to avoid races. We don't need to do that here, since the only operations that could race are drm_prime, and change_handle holds the prime lock for the entire duration.

v2: cleanups of error paths

Affected products

2

Patches

6
672464dd5323

drm: Set old handle to NULL before prime swap in change_handle

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git"Francis, David"Apr 28, 2026Fixed in 6.18.32via kernel-cna
1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index 11e7141c1524b8..6d0d27aca518b2 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -969,7 +969,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -992,8 +992,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1005,6 +1026,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    
61bd96d3e547

drm: Set old handle to NULL before prime swap in change_handle

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git"Francis, David"Apr 28, 2026Fixed in 7.0.9via kernel-cna
1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index 891c3bff5ae009..ebf21b403b11ef 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -1001,7 +1001,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -1024,8 +1024,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1037,6 +1058,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    
5e28b7b94408

drm: Set old handle to NULL before prime swap in change_handle

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git"Francis, David"Apr 28, 2026Fixed in 7.1-rc3via kernel-cna
1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index d6424267260bdb..51a887cc7fd744 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -1019,7 +1019,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -1042,8 +1042,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1055,6 +1076,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    
5e28b7b94408

drm: Set old handle to NULL before prime swap in change_handle

1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index d6424267260bdb..51a887cc7fd744 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -1019,7 +1019,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -1042,8 +1042,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1055,6 +1076,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    
61bd96d3e547

drm: Set old handle to NULL before prime swap in change_handle

1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index 891c3bff5ae009..ebf21b403b11ef 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -1001,7 +1001,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -1024,8 +1024,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1037,6 +1058,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    
672464dd5323

drm: Set old handle to NULL before prime swap in change_handle

1 file changed · +24 2
  • drivers/gpu/drm/drm_gem.c+24 2 modified
    diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
    index 11e7141c1524b8..6d0d27aca518b2 100644
    --- a/drivers/gpu/drm/drm_gem.c
    +++ b/drivers/gpu/drm/drm_gem.c
    @@ -969,7 +969,7 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     				struct drm_file *file_priv)
     {
     	struct drm_gem_change_handle *args = data;
    -	struct drm_gem_object *obj;
    +	struct drm_gem_object *obj, *idrobj;
     	int handle, ret;
     
     	if (!drm_core_check_feature(dev, DRIVER_GEM))
    @@ -992,8 +992,29 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     	mutex_lock(&file_priv->prime.lock);
     
     	spin_lock(&file_priv->table_lock);
    +
    +       /* When create_tail allocs an obj idr, it needs to first alloc as NULL,
    +	* then later replace with the correct object. This is not necessary
    +	* here, because the only operations that could race are drm_prime
    +	* bookkeeping, and we hold the prime lock.
    +	*/
     	ret = idr_alloc(&file_priv->object_idr, obj, handle, handle + 1,
     			GFP_NOWAIT);
    +
    +       if (ret < 0) {
    +	       spin_unlock(&file_priv->table_lock);
    +	       goto out_unlock;
    +       }
    +
    +       idrobj = idr_replace(&file_priv->object_idr, NULL, handle);
    +       if (idrobj != obj) {
    +	       idr_replace(&file_priv->object_idr, idrobj, handle);
    +	       idr_remove(&file_priv->object_idr, args->new_handle);
    +	       spin_unlock(&file_priv->table_lock);
    +	       ret = -ENOENT;
    +	       goto out_unlock;
    +       }
    +
     	spin_unlock(&file_priv->table_lock);
     
     	if (ret < 0)
    @@ -1005,6 +1026,8 @@ int drm_gem_change_handle_ioctl(struct drm_device *dev, void *data,
     		if (ret < 0) {
     			spin_lock(&file_priv->table_lock);
     			idr_remove(&file_priv->object_idr, handle);
    +			idrobj = idr_replace(&file_priv->object_idr, obj, handle);
    +			WARN_ON(idrobj != NULL);
     			spin_unlock(&file_priv->table_lock);
     			goto out_unlock;
     		}
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Race condition in drm_gem_change_handle_ioctl where a single GEM object briefly has two IDR entries, allowing a concurrent gem_close to delete the object while leaving a dangling handle that can be dereferenced for use-after-free."

Attack vector

An attacker with access to a DRM device can call the `DRM_IOCTL_GEM_CHANGE_HANDLE` ioctl to reassign a GEM object handle. During the ioctl execution, the kernel briefly creates a second IDR entry pointing to the same GEM object before removing the original handle. A concurrent `gem_close` on the same file descriptor can race with this window, deleting the object and removing one handle while the other handle remains dangling. The dangling handle can later be dereferenced, leading to a use-after-free [patch_id=2897688].

Affected code

The vulnerability is in the `drm_gem_change_handle_ioctl` function in `drivers/gpu/drm/drm_gem.c`. The function handles the DRM_IOCTL_GEM_CHANGE_HANDLE operation, which reassigns a GEM object from one handle to another via the DRM prime interface.

What the fix does

The patch follows the same pattern used by `gem_close` (commit f6cd7daecff5) and `drm_gem_handle_create_tail` (commit bd46cece51a3). After allocating the new handle via `idr_alloc`, the old handle is immediately replaced with NULL via `idr_replace` so that no IDR entry points to the object during the prime swap. If the prime operations succeed, the old handle is properly closed. On error, the old handle is restored from NULL back to the object pointer. This eliminates the window where two handles reference the same object concurrently [patch_id=2897688].

Preconditions

  • authAttacker must have access to a DRM device and be able to open a DRM file descriptor.
  • inputAttacker must be able to issue DRM_IOCTL_GEM_CHANGE_HANDLE ioctls and concurrently trigger gem_close on the same file descriptor.

Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.