s390/dasd: fix panic during offline processing
authorStefan Haberland <sth@linux.vnet.ibm.com>
Tue, 20 Sep 2016 08:42:38 +0000 (10:42 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Mon, 26 Sep 2016 14:45:29 +0000 (16:45 +0200)
A DASD device consists of the device itself and a discipline with a
corresponding private structure. These fields are set up during online
processing right after the device is created and before it is processed by
the state machine and made available for I/O.
During offline processing the discipline pointer and the private data gets
freed within the state machine and without protection of the existing
reference count. This might lead to a kernel panic because a function might
have taken a device reference and accesses the discipline pointer and/or
private data of the device while this is already freed.

Fix by freeing the discipline pointer and the private data after ensuring
that there is no reference to the device left.

Reviewed-by: Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
Signed-off-by: Stefan Haberland <sth@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/block/dasd.c
drivers/s390/block/dasd_devmap.c
drivers/s390/block/dasd_int.h

index 706ae0ac94c971bec9bde617503dd8e3d7ea28c7..1de089019268e10279a4c88be7767cf11f21eb96 100644 (file)
@@ -212,16 +212,6 @@ static int dasd_state_known_to_new(struct dasd_device *device)
 {
        /* Disable extended error reporting for this device. */
        dasd_eer_disable(device);
-       /* Forget the discipline information. */
-       if (device->discipline) {
-               if (device->discipline->uncheck_device)
-                       device->discipline->uncheck_device(device);
-               module_put(device->discipline->owner);
-       }
-       device->discipline = NULL;
-       if (device->base_discipline)
-               module_put(device->base_discipline->owner);
-       device->base_discipline = NULL;
        device->state = DASD_STATE_NEW;
 
        if (device->block)
@@ -3377,6 +3367,22 @@ int dasd_generic_probe(struct ccw_device *cdev,
 }
 EXPORT_SYMBOL_GPL(dasd_generic_probe);
 
+void dasd_generic_free_discipline(struct dasd_device *device)
+{
+       /* Forget the discipline information. */
+       if (device->discipline) {
+               if (device->discipline->uncheck_device)
+                       device->discipline->uncheck_device(device);
+               module_put(device->discipline->owner);
+               device->discipline = NULL;
+       }
+       if (device->base_discipline) {
+               module_put(device->base_discipline->owner);
+               device->base_discipline = NULL;
+       }
+}
+EXPORT_SYMBOL_GPL(dasd_generic_free_discipline);
+
 /*
  * This will one day be called from a global not_oper handler.
  * It is also used by driver_unregister during module unload.
index 3cdbce45e4649f853c85146ea2d3da199f823efc..15a1a70cace90f6eae0dc6a9466970df293e4132 100644 (file)
@@ -617,6 +617,7 @@ dasd_delete_device(struct dasd_device *device)
        /* Wait for reference counter to drop to zero. */
        wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
 
+       dasd_generic_free_discipline(device);
        /* Disconnect dasd_device structure from ccw_device structure. */
        cdev = device->cdev;
        device->cdev = NULL;
index ac7027e6d52b0eb508be47cc0aa90bd161ad0913..87ff6cef872f238b4993333e4c9d416450062ba6 100644 (file)
@@ -725,6 +725,7 @@ void dasd_block_clear_timer(struct dasd_block *);
 int  dasd_cancel_req(struct dasd_ccw_req *);
 int dasd_flush_device_queue(struct dasd_device *);
 int dasd_generic_probe (struct ccw_device *, struct dasd_discipline *);
+void dasd_generic_free_discipline(struct dasd_device *);
 void dasd_generic_remove (struct ccw_device *cdev);
 int dasd_generic_set_online(struct ccw_device *, struct dasd_discipline *);
 int dasd_generic_set_offline (struct ccw_device *cdev);