Commit 0f57c97f authored by Jan Höppner's avatar Jan Höppner Committed by Martin Schwidefsky

s390/dasd: Eliminate race condition in dasd_generic_set_offline()

Before we set a device offline, the open_count for the block device is
checked and certain flags are checked and set as well.
However, this is all done without holding any lock. Potentially, if the
open_count was checked but the DASD_FLAG_OFFLINE wasn't set yet, a
different process might want to increase the open_count depending on
whether DASD_FLAG_OFFLINE is set or not in the meanwhile.

This is quite racy and can lead to the loss of the device for that
process and subsequently lead to a panic.

Fix this by checking the open_count and setting the offline flags while
holding the ccwdev lock.
Reviewed-by: default avatarStefan Haberland <sth@linux.vnet.ibm.com>
Signed-off-by: default avatarJan Höppner <hoeppner@linux.vnet.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent 1081f3a7
...@@ -3517,11 +3517,15 @@ int dasd_generic_set_offline(struct ccw_device *cdev) ...@@ -3517,11 +3517,15 @@ int dasd_generic_set_offline(struct ccw_device *cdev)
struct dasd_device *device; struct dasd_device *device;
struct dasd_block *block; struct dasd_block *block;
int max_count, open_count, rc; int max_count, open_count, rc;
unsigned long flags;
rc = 0; rc = 0;
device = dasd_device_from_cdev(cdev); spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
if (IS_ERR(device)) device = dasd_device_from_cdev_locked(cdev);
if (IS_ERR(device)) {
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
return PTR_ERR(device); return PTR_ERR(device);
}
/* /*
* We must make sure that this device is currently not in use. * We must make sure that this device is currently not in use.
...@@ -3540,8 +3544,7 @@ int dasd_generic_set_offline(struct ccw_device *cdev) ...@@ -3540,8 +3544,7 @@ int dasd_generic_set_offline(struct ccw_device *cdev)
pr_warn("%s: The DASD cannot be set offline while it is in use\n", pr_warn("%s: The DASD cannot be set offline while it is in use\n",
dev_name(&cdev->dev)); dev_name(&cdev->dev));
clear_bit(DASD_FLAG_OFFLINE, &device->flags); clear_bit(DASD_FLAG_OFFLINE, &device->flags);
dasd_put_device(device); goto out_busy;
return -EBUSY;
} }
} }
...@@ -3551,19 +3554,19 @@ int dasd_generic_set_offline(struct ccw_device *cdev) ...@@ -3551,19 +3554,19 @@ int dasd_generic_set_offline(struct ccw_device *cdev)
* could only be called by normal offline so safe_offline flag * could only be called by normal offline so safe_offline flag
* needs to be removed to run normal offline and kill all I/O * needs to be removed to run normal offline and kill all I/O
*/ */
if (test_and_set_bit(DASD_FLAG_OFFLINE, &device->flags)) { if (test_and_set_bit(DASD_FLAG_OFFLINE, &device->flags))
/* Already doing normal offline processing */ /* Already doing normal offline processing */
dasd_put_device(device); goto out_busy;
return -EBUSY; else
} else
clear_bit(DASD_FLAG_SAFE_OFFLINE, &device->flags); clear_bit(DASD_FLAG_SAFE_OFFLINE, &device->flags);
} else {
} else if (test_bit(DASD_FLAG_OFFLINE, &device->flags))
if (test_bit(DASD_FLAG_OFFLINE, &device->flags)) {
/* Already doing offline processing */ /* Already doing offline processing */
dasd_put_device(device); goto out_busy;
return -EBUSY; }
}
set_bit(DASD_FLAG_OFFLINE, &device->flags);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
/* /*
* if safe_offline called set safe_offline_running flag and * if safe_offline called set safe_offline_running flag and
...@@ -3591,7 +3594,6 @@ int dasd_generic_set_offline(struct ccw_device *cdev) ...@@ -3591,7 +3594,6 @@ int dasd_generic_set_offline(struct ccw_device *cdev)
goto interrupted; goto interrupted;
} }
set_bit(DASD_FLAG_OFFLINE, &device->flags);
dasd_set_target_state(device, DASD_STATE_NEW); dasd_set_target_state(device, DASD_STATE_NEW);
/* dasd_delete_device destroys the device reference. */ /* dasd_delete_device destroys the device reference. */
block = device->block; block = device->block;
...@@ -3610,7 +3612,14 @@ int dasd_generic_set_offline(struct ccw_device *cdev) ...@@ -3610,7 +3612,14 @@ int dasd_generic_set_offline(struct ccw_device *cdev)
clear_bit(DASD_FLAG_SAFE_OFFLINE_RUNNING, &device->flags); clear_bit(DASD_FLAG_SAFE_OFFLINE_RUNNING, &device->flags);
clear_bit(DASD_FLAG_OFFLINE, &device->flags); clear_bit(DASD_FLAG_OFFLINE, &device->flags);
dasd_put_device(device); dasd_put_device(device);
return rc; return rc;
out_busy:
dasd_put_device(device);
spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
return -EBUSY;
} }
EXPORT_SYMBOL_GPL(dasd_generic_set_offline); EXPORT_SYMBOL_GPL(dasd_generic_set_offline);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment