Skip to content

Commit

Permalink
[S390] cio: fix ccwgroup unregistration race condition
Browse files Browse the repository at this point in the history
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 <[email protected]>
Signed-off-by: Martin Schwidefsky <[email protected]>
  • Loading branch information
oberpar authored and Martin Schwidefsky committed Jan 5, 2011
1 parent f602be6 commit c030175
Showing 1 changed file with 40 additions and 38 deletions.
78 changes: 40 additions & 38 deletions drivers/s390/cio/ccwgroup.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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 :)
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand All @@ -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");
Expand Down

0 comments on commit c030175

Please sign in to comment.