driver core: fix shutdown races with probe/remove(v3)
authorMing Lei <ming.lei@canonical.com>
Fri, 22 Jun 2012 10:01:40 +0000 (18:01 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 17 Jul 2012 01:04:25 +0000 (18:04 -0700)
Firstly, .shutdown callback may touch a uninitialized hardware
if dev->driver is set and .probe is not completed.

Secondly, device_shutdown() may dereference a null pointer to cause
oops when dev->driver is cleared after it has been checked in
device_shutdown().

So just hold device lock and its parent lock(if it has) to
fix the races.

Cc: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Ming Lei <ming.lei@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/base/core.c

index 846d12b..65849a9 100644 (file)
@@ -1812,6 +1812,13 @@ void device_shutdown(void)
        while (!list_empty(&devices_kset->list)) {
                dev = list_entry(devices_kset->list.prev, struct device,
                                kobj.entry);
+
+               /*
+                * hold reference count of device's parent to
+                * prevent it from being freed because parent's
+                * lock is to be held
+                */
+               get_device(dev->parent);
                get_device(dev);
                /*
                 * Make sure the device is off the kset list, in the
@@ -1820,6 +1827,11 @@ void device_shutdown(void)
                list_del_init(&dev->kobj.entry);
                spin_unlock(&devices_kset->list_lock);
 
+               /* hold lock to avoid race with probe/release */
+               if (dev->parent)
+                       device_lock(dev->parent);
+               device_lock(dev);
+
                /* Don't allow any more runtime suspends */
                pm_runtime_get_noresume(dev);
                pm_runtime_barrier(dev);
@@ -1831,7 +1843,13 @@ void device_shutdown(void)
                        dev_dbg(dev, "shutdown\n");
                        dev->driver->shutdown(dev);
                }
+
+               device_unlock(dev);
+               if (dev->parent)
+                       device_unlock(dev->parent);
+
                put_device(dev);
+               put_device(dev->parent);
 
                spin_lock(&devices_kset->list_lock);
        }