mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 03:35:32 -04:00
drm: fix race between partial drm_dev_register() failure and ioctl
If drm_dev_register() fails after registering a minor (e.g. render minor
registered, primary minor fails), userspace could have opened the first
minor and entered a drm_dev_enter() critical section. Since the
unplugged flag was never set, the ioctl proceeds while the error path
tears down device resources.
Fix this by introducing drm_dev_synchronize_unplug(), which sets the
unplugged flag and waits for the SRCU barrier, ensuring all in-flight
drm_dev_enter() critical sections complete before cleanup proceeds; call
it on the error path of drm_dev_register().
Fixes: bee330f3d6 ("drm: Use srcu to protect drm_device.unplugged")
Cc: stable@vger.kernel.org
Reported-by: sashiko-bot@kernel.org
Closes: https://lore.kernel.org/all/20260620190648.2E9F61F000E9@smtp.kernel.org/
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Tested-by: Deborah Brouwer <deborah.brouwer@collabora.com>
Link: https://patch.msgid.link/20260628145406.2107056-17-dakr@kernel.org
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
@@ -473,6 +473,22 @@ void drm_dev_exit(int idx)
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dev_exit);
|
||||
|
||||
/*
|
||||
* Mark the device as unplugged and wait for any in-flight drm_dev_enter()
|
||||
* critical sections to complete.
|
||||
*/
|
||||
static void drm_dev_synchronize_unplug(struct drm_device *dev)
|
||||
{
|
||||
/*
|
||||
* After synchronizing any critical read section is guaranteed to see
|
||||
* the new value of ->unplugged, and any critical section which might
|
||||
* still have seen the old value of ->unplugged is guaranteed to have
|
||||
* finished.
|
||||
*/
|
||||
dev->unplugged = true;
|
||||
synchronize_srcu(&drm_unplug_srcu);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_dev_unplug - unplug a DRM device
|
||||
* @dev: DRM device
|
||||
@@ -485,15 +501,7 @@ EXPORT_SYMBOL(drm_dev_exit);
|
||||
*/
|
||||
void drm_dev_unplug(struct drm_device *dev)
|
||||
{
|
||||
/*
|
||||
* After synchronizing any critical read section is guaranteed to see
|
||||
* the new value of ->unplugged, and any critical section which might
|
||||
* still have seen the old value of ->unplugged is guaranteed to have
|
||||
* finished.
|
||||
*/
|
||||
dev->unplugged = true;
|
||||
synchronize_srcu(&drm_unplug_srcu);
|
||||
|
||||
drm_dev_synchronize_unplug(dev);
|
||||
drm_dev_unregister(dev);
|
||||
|
||||
/* Clear all CPU mappings pointing to this device */
|
||||
@@ -1091,6 +1099,7 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
|
||||
goto err_minors;
|
||||
|
||||
dev->registered = true;
|
||||
dev->unplugged = false;
|
||||
|
||||
if (driver->load) {
|
||||
ret = driver->load(dev, flags);
|
||||
@@ -1118,6 +1127,13 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags)
|
||||
if (dev->driver->unload)
|
||||
dev->driver->unload(dev);
|
||||
err_minors:
|
||||
/*
|
||||
* If a minor was registered before the failure, userspace could have
|
||||
* opened it and entered a drm_dev_enter() critical section. Ensure all
|
||||
* such sections complete before we clean up.
|
||||
*/
|
||||
drm_dev_synchronize_unplug(dev);
|
||||
|
||||
remove_compat_control_link(dev);
|
||||
drm_minor_unregister(dev, DRM_MINOR_ACCEL);
|
||||
drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
|
||||
|
||||
Reference in New Issue
Block a user