s390/dasd: fix double free in dasd_eckd_read_conf
authorStefan Haberland <stefan.haberland@de.ibm.com>
Thu, 15 Oct 2015 09:10:08 +0000 (11:10 +0200)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Tue, 3 Nov 2015 13:40:44 +0000 (14:40 +0100)
The configuration data is stored per path and also the first valid
configuration data per device. When dasd_eckd_read_conf is called
again after a path got lost the device configuration data is cleared
but possibly not the per path configuration data. This might lead to a
double free when the lost path gets operational again.

Fix by clearing all per path configuration data when the first valid
configuration data is received and stored.

Reviewed-by: Sebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: Stefan Haberland <stefan.haberland@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
drivers/s390/block/dasd_eckd.c

index 30c2a80c004b47ee34b5f2bf2ec666fddc89d569..e53b895a4d0f86ace95c4c20185e65fe0ff42aed 100644 (file)
@@ -1032,6 +1032,21 @@ static unsigned char dasd_eckd_path_access(void *conf_data, int conf_len)
                return 0;
 }
 
+static void dasd_eckd_clear_conf_data(struct dasd_device *device)
+{
+       struct dasd_eckd_private *private;
+       int i;
+
+       private = (struct dasd_eckd_private *) device->private;
+       private->conf_data = NULL;
+       private->conf_len = 0;
+       for (i = 0; i < 8; i++) {
+               kfree(private->path_conf_data[i]);
+               private->path_conf_data[i] = NULL;
+       }
+}
+
+
 static int dasd_eckd_read_conf(struct dasd_device *device)
 {
        void *conf_data;
@@ -1068,19 +1083,10 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
                        path_data->opm |= lpm;
                        continue;       /* no error */
                }
-               pos = pathmask_to_pos(lpm);
-               kfree(private->path_conf_data[pos]);
-               if ((__u8 *)private->path_conf_data[pos] ==
-                   private->conf_data) {
-                       private->conf_data = NULL;
-                       private->conf_len = 0;
-                       conf_data_saved = 0;
-               }
-               private->path_conf_data[pos] =
-                       (struct dasd_conf_data *) conf_data;
                /* save first valid configuration data */
                if (!conf_data_saved) {
-                       kfree(private->conf_data);
+                       /* initially clear previously stored conf_data */
+                       dasd_eckd_clear_conf_data(device);
                        private->conf_data = conf_data;
                        private->conf_len = conf_len;
                        if (dasd_eckd_identify_conf_parts(private)) {
@@ -1089,6 +1095,10 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
                                kfree(conf_data);
                                continue;
                        }
+                       pos = pathmask_to_pos(lpm);
+                       /* store per path conf_data */
+                       private->path_conf_data[pos] =
+                               (struct dasd_conf_data *) conf_data;
                        /*
                         * build device UID that other path data
                         * can be compared to it
@@ -1146,7 +1156,10 @@ static int dasd_eckd_read_conf(struct dasd_device *device)
                                path_data->cablepm |= lpm;
                                continue;
                        }
-
+                       pos = pathmask_to_pos(lpm);
+                       /* store per path conf_data */
+                       private->path_conf_data[pos] =
+                               (struct dasd_conf_data *) conf_data;
                        path_private.conf_data = NULL;
                        path_private.conf_len = 0;
                }