aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/s390
diff options
context:
space:
mode:
authorSebastian Ott <sebott@linux.vnet.ibm.com>2015-09-07 19:52:06 +0200
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2015-10-14 14:32:03 +0200
commit616503d1d8940049841e5b6f2ab5157f37072ed9 (patch)
tree0b4c551fe84e3bb391ae578f8a2a2d07bb336ef9 /drivers/s390
parents390/cio: fix use after free in cmb processing (diff)
downloadlinux-dev-616503d1d8940049841e5b6f2ab5157f37072ed9.tar.xz
linux-dev-616503d1d8940049841e5b6f2ab5157f37072ed9.zip
s390/cio: improve locking during cmbe allocation
During allocation of extended measurement blocks we check if the device is already active for channel measurement and add the device to a list of devices with active channel measurement. The check is done under ccwlock protection and the list modification is guarded by a different lock. To guarantee that both states are in sync make sure that both locks are held during the allocation process (like it's already done for the "normal" measurement block allocation). Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com> Reviewed-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390')
-rw-r--r--drivers/s390/cio/cmf.c43
1 files changed, 23 insertions, 20 deletions
diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c
index 31677c075a8e..59b1ac24f992 100644
--- a/drivers/s390/cio/cmf.c
+++ b/drivers/s390/cio/cmf.c
@@ -821,42 +821,45 @@ static inline struct cmbe *cmbe_align(struct cmbe *c)
static int alloc_cmbe(struct ccw_device *cdev)
{
- struct cmbe *cmbe;
struct cmb_data *cmb_data;
- int ret;
+ struct cmbe *cmbe;
+ int ret = -ENOMEM;
cmbe = kzalloc (sizeof (*cmbe) * 2, GFP_KERNEL);
if (!cmbe)
- return -ENOMEM;
+ return ret;
+
cmb_data = kzalloc(sizeof(struct cmb_data), GFP_KERNEL);
- if (!cmb_data) {
- ret = -ENOMEM;
+ if (!cmb_data)
goto out_free;
- }
+
cmb_data->last_block = kzalloc(sizeof(struct cmbe), GFP_KERNEL);
- if (!cmb_data->last_block) {
- ret = -ENOMEM;
+ if (!cmb_data->last_block)
goto out_free;
- }
+
cmb_data->size = sizeof(struct cmbe);
- spin_lock_irq(cdev->ccwlock);
- if (cdev->private->cmb) {
- spin_unlock_irq(cdev->ccwlock);
- ret = -EBUSY;
- goto out_free;
- }
cmb_data->hw_block = cmbe;
+
+ spin_lock(&cmb_area.lock);
+ spin_lock_irq(cdev->ccwlock);
+ if (cdev->private->cmb)
+ goto out_unlock;
+
cdev->private->cmb = cmb_data;
- spin_unlock_irq(cdev->ccwlock);
/* activate global measurement if this is the first channel */
- spin_lock(&cmb_area.lock);
if (list_empty(&cmb_area.list))
cmf_activate(NULL, 1);
list_add_tail(&cdev->private->cmb_list, &cmb_area.list);
- spin_unlock(&cmb_area.lock);
+ spin_unlock_irq(cdev->ccwlock);
+ spin_unlock(&cmb_area.lock);
return 0;
+
+out_unlock:
+ spin_unlock_irq(cdev->ccwlock);
+ spin_unlock(&cmb_area.lock);
+ ret = -EBUSY;
out_free:
if (cmb_data)
kfree(cmb_data->last_block);
@@ -869,19 +872,19 @@ static void free_cmbe(struct ccw_device *cdev)
{
struct cmb_data *cmb_data;
+ spin_lock(&cmb_area.lock);
spin_lock_irq(cdev->ccwlock);
cmb_data = cdev->private->cmb;
cdev->private->cmb = NULL;
if (cmb_data)
kfree(cmb_data->last_block);
kfree(cmb_data);
- spin_unlock_irq(cdev->ccwlock);
/* deactivate global measurement if this is the last channel */
- spin_lock(&cmb_area.lock);
list_del_init(&cdev->private->cmb_list);
if (list_empty(&cmb_area.list))
cmf_activate(NULL, 0);
+ spin_unlock_irq(cdev->ccwlock);
spin_unlock(&cmb_area.lock);
}