summaryrefslogtreecommitdiffstats
path: root/drivers/s390
diff options
context:
space:
mode:
authorPeter Oberparleiter <peter.oberparleiter@de.ibm.com>2011-01-05 12:48:13 +0100
committerMartin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com>2011-01-05 12:47:31 +0100
commitc03017544e3b2e60aa3c8ae451fac01595f1bf11 (patch)
tree4321b487b2c6826aa2c6f6247696d86001b96082 /drivers/s390
parentf602be639e97024a77062368e123008c94b3109a (diff)
[S390] cio: fix ccwgroup unregistration race condition
A race condition exists in the ccwgroup device unregistration code which can cause a kernel panic due to a use-after-free bug. This race condition might be triggered when all ccw devices associated with a ccwgroup device are removed at the same time (e.g. because the corresponding channel path becomes no longer available). Fix this race condition by clearing the references from the associated ccw devices to the ccw group device during unregistration of the ccw group device. Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390')
-rw-r--r--drivers/s390/cio/ccwgroup.c78
1 files changed, 40 insertions, 38 deletions
diff --git a/drivers/s390/cio/ccwgroup.c b/drivers/s390/cio/ccwgroup.c
index 97b25d68e3e..2864581d8ec 100644
--- a/drivers/s390/cio/ccwgroup.c
+++ b/drivers/s390/cio/ccwgroup.c
@@ -67,6 +67,27 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
}
/*
+ * Remove references from ccw devices to ccw group device and from
+ * ccw group device to ccw devices.
+ */
+static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev)
+{
+ struct ccw_device *cdev;
+ int i;
+
+ for (i = 0; i < gdev->count; i++) {
+ cdev = gdev->cdev[i];
+ if (!cdev)
+ continue;
+ spin_lock_irq(cdev->ccwlock);
+ dev_set_drvdata(&cdev->dev, NULL);
+ spin_unlock_irq(cdev->ccwlock);
+ gdev->cdev[i] = NULL;
+ put_device(&cdev->dev);
+ }
+}
+
+/*
* Provide an 'ungroup' attribute so the user can remove group devices no
* longer needed or accidentially created. Saves memory :)
*/
@@ -78,6 +99,7 @@ static void ccwgroup_ungroup_callback(struct device *dev)
if (device_is_registered(&gdev->dev)) {
__ccwgroup_remove_symlinks(gdev);
device_unregister(dev);
+ __ccwgroup_remove_cdev_refs(gdev);
}
mutex_unlock(&gdev->reg_mutex);
}
@@ -116,21 +138,7 @@ static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
static void
ccwgroup_release (struct device *dev)
{
- struct ccwgroup_device *gdev;
- int i;
-
- gdev = to_ccwgroupdev(dev);
-
- for (i = 0; i < gdev->count; i++) {
- if (gdev->cdev[i]) {
- spin_lock_irq(gdev->cdev[i]->ccwlock);
- if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
- dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
- spin_unlock_irq(gdev->cdev[i]->ccwlock);
- put_device(&gdev->cdev[i]->dev);
- }
- }
- kfree(gdev);
+ kfree(to_ccwgroupdev(dev));
}
static int
@@ -639,6 +647,7 @@ void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)
mutex_lock(&gdev->reg_mutex);
__ccwgroup_remove_symlinks(gdev);
device_unregister(dev);
+ __ccwgroup_remove_cdev_refs(gdev);
mutex_unlock(&gdev->reg_mutex);
put_device(dev);
}
@@ -660,25 +669,6 @@ int ccwgroup_probe_ccwdev(struct ccw_device *cdev)
return 0;
}
-static struct ccwgroup_device *
-__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
-{
- struct ccwgroup_device *gdev;
-
- gdev = dev_get_drvdata(&cdev->dev);
- if (gdev) {
- if (get_device(&gdev->dev)) {
- mutex_lock(&gdev->reg_mutex);
- if (device_is_registered(&gdev->dev))
- return gdev;
- mutex_unlock(&gdev->reg_mutex);
- put_device(&gdev->dev);
- }
- return NULL;
- }
- return NULL;
-}
-
/**
* ccwgroup_remove_ccwdev() - remove function for slave devices
* @cdev: ccw device to be removed
@@ -694,13 +684,25 @@ void ccwgroup_remove_ccwdev(struct ccw_device *cdev)
/* Ignore offlining errors, device is gone anyway. */
ccw_device_set_offline(cdev);
/* If one of its devices is gone, the whole group is done for. */
- gdev = __ccwgroup_get_gdev_by_cdev(cdev);
- if (gdev) {
+ spin_lock_irq(cdev->ccwlock);
+ gdev = dev_get_drvdata(&cdev->dev);
+ if (!gdev) {
+ spin_unlock_irq(cdev->ccwlock);
+ return;
+ }
+ /* Get ccwgroup device reference for local processing. */
+ get_device(&gdev->dev);
+ spin_unlock_irq(cdev->ccwlock);
+ /* Unregister group device. */
+ mutex_lock(&gdev->reg_mutex);
+ if (device_is_registered(&gdev->dev)) {
__ccwgroup_remove_symlinks(gdev);
device_unregister(&gdev->dev);
- mutex_unlock(&gdev->reg_mutex);
- put_device(&gdev->dev);
+ __ccwgroup_remove_cdev_refs(gdev);
}
+ mutex_unlock(&gdev->reg_mutex);
+ /* Release ccwgroup device reference for local processing. */
+ put_device(&gdev->dev);
}
MODULE_LICENSE("GPL");