aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390/cio
diff options
context:
space:
mode:
authorPeter Oberparleiter <peter.oberparleiter@de.ibm.com>2011-01-05 06:48:13 -0500
committerMartin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com>2011-01-05 06:47:31 -0500
commitc03017544e3b2e60aa3c8ae451fac01595f1bf11 (patch)
tree4321b487b2c6826aa2c6f6247696d86001b96082 /drivers/s390/cio
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/cio')
-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 97b25d68e3e7..2864581d8ecb 100644
--- a/drivers/s390/cio/ccwgroup.c
+++ b/drivers/s390/cio/ccwgroup.c
@@ -67,6 +67,27 @@ __ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
67} 67}
68 68
69/* 69/*
70 * Remove references from ccw devices to ccw group device and from
71 * ccw group device to ccw devices.
72 */
73static void __ccwgroup_remove_cdev_refs(struct ccwgroup_device *gdev)
74{
75 struct ccw_device *cdev;
76 int i;
77
78 for (i = 0; i < gdev->count; i++) {
79 cdev = gdev->cdev[i];
80 if (!cdev)
81 continue;
82 spin_lock_irq(cdev->ccwlock);
83 dev_set_drvdata(&cdev->dev, NULL);
84 spin_unlock_irq(cdev->ccwlock);
85 gdev->cdev[i] = NULL;
86 put_device(&cdev->dev);
87 }
88}
89
90/*
70 * Provide an 'ungroup' attribute so the user can remove group devices no 91 * Provide an 'ungroup' attribute so the user can remove group devices no
71 * longer needed or accidentially created. Saves memory :) 92 * longer needed or accidentially created. Saves memory :)
72 */ 93 */
@@ -78,6 +99,7 @@ static void ccwgroup_ungroup_callback(struct device *dev)
78 if (device_is_registered(&gdev->dev)) { 99 if (device_is_registered(&gdev->dev)) {
79 __ccwgroup_remove_symlinks(gdev); 100 __ccwgroup_remove_symlinks(gdev);
80 device_unregister(dev); 101 device_unregister(dev);
102 __ccwgroup_remove_cdev_refs(gdev);
81 } 103 }
82 mutex_unlock(&gdev->reg_mutex); 104 mutex_unlock(&gdev->reg_mutex);
83} 105}
@@ -116,21 +138,7 @@ static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
116static void 138static void
117ccwgroup_release (struct device *dev) 139ccwgroup_release (struct device *dev)
118{ 140{
119 struct ccwgroup_device *gdev; 141 kfree(to_ccwgroupdev(dev));
120 int i;
121
122 gdev = to_ccwgroupdev(dev);
123
124 for (i = 0; i < gdev->count; i++) {
125 if (gdev->cdev[i]) {
126 spin_lock_irq(gdev->cdev[i]->ccwlock);
127 if (dev_get_drvdata(&gdev->cdev[i]->dev) == gdev)
128 dev_set_drvdata(&gdev->cdev[i]->dev, NULL);
129 spin_unlock_irq(gdev->cdev[i]->ccwlock);
130 put_device(&gdev->cdev[i]->dev);
131 }
132 }
133 kfree(gdev);
134} 142}
135 143
136static int 144static int
@@ -639,6 +647,7 @@ void ccwgroup_driver_unregister(struct ccwgroup_driver *cdriver)
639 mutex_lock(&gdev->reg_mutex); 647 mutex_lock(&gdev->reg_mutex);
640 __ccwgroup_remove_symlinks(gdev); 648 __ccwgroup_remove_symlinks(gdev);
641 device_unregister(dev); 649 device_unregister(dev);
650 __ccwgroup_remove_cdev_refs(gdev);
642 mutex_unlock(&gdev->reg_mutex); 651 mutex_unlock(&gdev->reg_mutex);
643 put_device(dev); 652 put_device(dev);
644 } 653 }
@@ -660,25 +669,6 @@ int ccwgroup_probe_ccwdev(struct ccw_device *cdev)
660 return 0; 669 return 0;
661} 670}
662 671
663static struct ccwgroup_device *
664__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
665{
666 struct ccwgroup_device *gdev;
667
668 gdev = dev_get_drvdata(&cdev->dev);
669 if (gdev) {
670 if (get_device(&gdev->dev)) {
671 mutex_lock(&gdev->reg_mutex);
672 if (device_is_registered(&gdev->dev))
673 return gdev;
674 mutex_unlock(&gdev->reg_mutex);
675 put_device(&gdev->dev);
676 }
677 return NULL;
678 }
679 return NULL;
680}
681
682/** 672/**
683 * ccwgroup_remove_ccwdev() - remove function for slave devices 673 * ccwgroup_remove_ccwdev() - remove function for slave devices
684 * @cdev: ccw device to be removed 674 * @cdev: ccw device to be removed
@@ -694,13 +684,25 @@ void ccwgroup_remove_ccwdev(struct ccw_device *cdev)
694 /* Ignore offlining errors, device is gone anyway. */ 684 /* Ignore offlining errors, device is gone anyway. */
695 ccw_device_set_offline(cdev); 685 ccw_device_set_offline(cdev);
696 /* If one of its devices is gone, the whole group is done for. */ 686 /* If one of its devices is gone, the whole group is done for. */
697 gdev = __ccwgroup_get_gdev_by_cdev(cdev); 687 spin_lock_irq(cdev->ccwlock);
698 if (gdev) { 688 gdev = dev_get_drvdata(&cdev->dev);
689 if (!gdev) {
690 spin_unlock_irq(cdev->ccwlock);
691 return;
692 }
693 /* Get ccwgroup device reference for local processing. */
694 get_device(&gdev->dev);
695 spin_unlock_irq(cdev->ccwlock);
696 /* Unregister group device. */
697 mutex_lock(&gdev->reg_mutex);
698 if (device_is_registered(&gdev->dev)) {
699 __ccwgroup_remove_symlinks(gdev); 699 __ccwgroup_remove_symlinks(gdev);
700 device_unregister(&gdev->dev); 700 device_unregister(&gdev->dev);
701 mutex_unlock(&gdev->reg_mutex); 701 __ccwgroup_remove_cdev_refs(gdev);
702 put_device(&gdev->dev);
703 } 702 }
703 mutex_unlock(&gdev->reg_mutex);
704 /* Release ccwgroup device reference for local processing. */
705 put_device(&gdev->dev);
704} 706}
705 707
706MODULE_LICENSE("GPL"); 708MODULE_LICENSE("GPL");