summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Ott <sebott@linux.vnet.ibm.com>2015-09-07 19:51:39 +0200
committerMartin Schwidefsky <schwidefsky@de.ibm.com>2015-10-14 14:32:02 +0200
commita6ef15652d260f754ead223d0c55434a3a39fe1d (patch)
treeb2a872f3b03d863b648a1e796d41ef4f20e46761
parent1bc6664bdfb949bc69a08113801e7d6acbf6bc3f (diff)
downloadop-kernel-dev-a6ef15652d260f754ead223d0c55434a3a39fe1d.zip
op-kernel-dev-a6ef15652d260f754ead223d0c55434a3a39fe1d.tar.gz
s390/cio: fix use after free in cmb processing
Devices with active channel measurement are included in a list. When a device is removed without deactivating channel measurement first the list_head is freed but still used. Fix this by making sure that channel measurement is deactivated during device deregistration. For devices that we deregister because they are no longer accessible deactivating channel measurement will fail. In this case we can report success because the FW will no longer access the measurement block. In addition to these steps keep an extra device reference while channel measurement is active. Signed-off-by: Sebastian Ott <sebott@linux.vnet.ibm.com> Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
-rw-r--r--drivers/s390/cio/cmf.c27
-rw-r--r--drivers/s390/cio/device.c2
2 files changed, 18 insertions, 11 deletions
diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c
index 5eeb62c..31677c0 100644
--- a/drivers/s390/cio/cmf.c
+++ b/drivers/s390/cio/cmf.c
@@ -186,9 +186,8 @@ static inline void cmf_activate(void *area, unsigned int onoff)
static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
unsigned long address)
{
- struct subchannel *sch;
-
- sch = to_subchannel(cdev->dev.parent);
+ struct subchannel *sch = to_subchannel(cdev->dev.parent);
+ int ret;
sch->config.mme = mme;
sch->config.mbfc = mbfc;
@@ -198,7 +197,15 @@ static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
else
sch->config.mbi = address;
- return cio_commit_config(sch);
+ ret = cio_commit_config(sch);
+ if (!mme && ret == -ENODEV) {
+ /*
+ * The task was to disable measurement block updates but
+ * the subchannel is already gone. Report success.
+ */
+ ret = 0;
+ }
+ return ret;
}
struct set_schib_struct {
@@ -606,12 +613,6 @@ static void free_cmb(struct ccw_device *cdev)
spin_lock_irq(cdev->ccwlock);
priv = cdev->private;
-
- if (list_empty(&priv->cmb_list)) {
- /* already freed */
- goto out;
- }
-
cmb_data = priv->cmb;
priv->cmb = NULL;
if (cmb_data)
@@ -626,7 +627,6 @@ static void free_cmb(struct ccw_device *cdev)
free_pages((unsigned long)cmb_area.mem, get_order(size));
cmb_area.mem = NULL;
}
-out:
spin_unlock_irq(cdev->ccwlock);
spin_unlock(&cmb_area.lock);
}
@@ -1227,6 +1227,7 @@ int enable_cmf(struct ccw_device *cdev)
int ret;
device_lock(&cdev->dev);
+ get_device(&cdev->dev);
ret = cmbops->alloc(cdev);
if (ret)
goto out;
@@ -1242,6 +1243,9 @@ int enable_cmf(struct ccw_device *cdev)
cmbops->free(cdev);
}
out:
+ if (ret)
+ put_device(&cdev->dev);
+
device_unlock(&cdev->dev);
return ret;
}
@@ -1265,6 +1269,7 @@ int __disable_cmf(struct ccw_device *cdev)
sysfs_remove_group(&cdev->dev.kobj, cmbops->attr_group);
cmbops->free(cdev);
+ put_device(&cdev->dev);
return ret;
}
diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c
index 20b92c7..6aae684 100644
--- a/drivers/s390/cio/device.c
+++ b/drivers/s390/cio/device.c
@@ -1787,6 +1787,8 @@ static int ccw_device_remove(struct device *dev)
cdev->drv = NULL;
cdev->private->int_class = IRQIO_CIO;
spin_unlock_irq(cdev->ccwlock);
+ __disable_cmf(cdev);
+
return 0;
}
OpenPOWER on IntegriCloud