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: bee330f3d672 ("drm: Use srcu to protect drm_device.unplugged")
Cc: [email protected]
Reported-by: [email protected]
Closes: https://lore.kernel.org/all/[email protected]/
Signed-off-by: Danilo Krummrich <[email protected]>
---
 drivers/gpu/drm/drm_drv.c | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index 675675480da4..e890052061f3 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -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);
-- 
2.54.0

Reply via email to