diff options
author | Stefan Weinhuber <wein@de.ibm.com> | 2011-04-20 10:15:30 +0200 |
---|---|---|
committer | Martin Schwidefsky <sky@mschwide.boeblingen.de.ibm.com> | 2011-04-20 10:15:43 +0200 |
commit | 65f8da475995f667af5298c644707dbd9d646ca6 (patch) | |
tree | bca8597443060e79f09a8843903bbc55b2dece52 /drivers/s390/block/dasd_ioctl.c | |
parent | 2f666bcf757cb72549f360ef6da02f03620a48b6 (diff) | |
download | linux-65f8da475995f667af5298c644707dbd9d646ca6.tar.gz |
[S390] dasd: fix race between open and offline
The dasd_open function uses the private_data pointer of the gendisk to
find the dasd_block structure that matches the gendisk. When a DASD
device is set offline, we set the private_data pointer of the gendisk
to NULL and later remove the dasd_block structure, but there is still
a small race window, in which dasd_open could first read a pointer
from the private_data field and then try to use it, after the structure
has already been freed.
To close this race window, we will store a pointer to the dasd_devmap
structure of the base device in the private_data field. The devmap
entries are not deleted, and we already have proper locking and
reference counting in place, so that we can safely get from a devmap
pointer to the dasd_device and dasd_block structures of the device.
Signed-off-by: Stefan Weinhuber <wein@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Diffstat (limited to 'drivers/s390/block/dasd_ioctl.c')
-rw-r--r-- | drivers/s390/block/dasd_ioctl.c | 128 |
1 files changed, 87 insertions, 41 deletions
diff --git a/drivers/s390/block/dasd_ioctl.c b/drivers/s390/block/dasd_ioctl.c index 26075e95b1ba..72261e4c516d 100644 --- a/drivers/s390/block/dasd_ioctl.c +++ b/drivers/s390/block/dasd_ioctl.c @@ -42,16 +42,22 @@ dasd_ioctl_api_version(void __user *argp) static int dasd_ioctl_enable(struct block_device *bdev) { - struct dasd_block *block = bdev->bd_disk->private_data; + struct dasd_device *base; if (!capable(CAP_SYS_ADMIN)) return -EACCES; - dasd_enable_device(block->base); + base = dasd_device_from_gendisk(bdev->bd_disk); + if (!base) + return -ENODEV; + + dasd_enable_device(base); /* Formatting the dasd device can change the capacity. */ mutex_lock(&bdev->bd_mutex); - i_size_write(bdev->bd_inode, (loff_t)get_capacity(block->gdp) << 9); + i_size_write(bdev->bd_inode, + (loff_t)get_capacity(base->block->gdp) << 9); mutex_unlock(&bdev->bd_mutex); + dasd_put_device(base); return 0; } @@ -62,11 +68,14 @@ dasd_ioctl_enable(struct block_device *bdev) static int dasd_ioctl_disable(struct block_device *bdev) { - struct dasd_block *block = bdev->bd_disk->private_data; + struct dasd_device *base; if (!capable(CAP_SYS_ADMIN)) return -EACCES; + base = dasd_device_from_gendisk(bdev->bd_disk); + if (!base) + return -ENODEV; /* * Man this is sick. We don't do a real disable but only downgrade * the device to DASD_STATE_BASIC. The reason is that dasdfmt uses @@ -75,7 +84,7 @@ dasd_ioctl_disable(struct block_device *bdev) * using the BIODASDFMT ioctl. Therefore the correct state for the * device is DASD_STATE_BASIC that allows to do basic i/o. */ - dasd_set_target_state(block->base, DASD_STATE_BASIC); + dasd_set_target_state(base, DASD_STATE_BASIC); /* * Set i_size to zero, since read, write, etc. check against this * value. @@ -83,6 +92,7 @@ dasd_ioctl_disable(struct block_device *bdev) mutex_lock(&bdev->bd_mutex); i_size_write(bdev->bd_inode, 0); mutex_unlock(&bdev->bd_mutex); + dasd_put_device(base); return 0; } @@ -191,26 +201,36 @@ static int dasd_format(struct dasd_block *block, struct format_data_t *fdata) static int dasd_ioctl_format(struct block_device *bdev, void __user *argp) { - struct dasd_block *block = bdev->bd_disk->private_data; + struct dasd_device *base; struct format_data_t fdata; + int rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; if (!argp) return -EINVAL; - - if (block->base->features & DASD_FEATURE_READONLY || - test_bit(DASD_FLAG_DEVICE_RO, &block->base->flags)) + base = dasd_device_from_gendisk(bdev->bd_disk); + if (!base) + return -ENODEV; + if (base->features & DASD_FEATURE_READONLY || + test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) { + dasd_put_device(base); return -EROFS; - if (copy_from_user(&fdata, argp, sizeof(struct format_data_t))) + } + if (copy_from_user(&fdata, argp, sizeof(struct format_data_t))) { + dasd_put_device(base); return -EFAULT; + } if (bdev != bdev->bd_contains) { pr_warning("%s: The specified DASD is a partition and cannot " "be formatted\n", - dev_name(&block->base->cdev->dev)); + dev_name(&base->cdev->dev)); + dasd_put_device(base); return -EINVAL; } - return dasd_format(block, &fdata); + rc = dasd_format(base->block, &fdata); + dasd_put_device(base); + return rc; } #ifdef CONFIG_DASD_PROFILE @@ -340,8 +360,8 @@ static int dasd_ioctl_information(struct dasd_block *block, static int dasd_ioctl_set_ro(struct block_device *bdev, void __user *argp) { - struct dasd_block *block = bdev->bd_disk->private_data; - int intval; + struct dasd_device *base; + int intval, rc; if (!capable(CAP_SYS_ADMIN)) return -EACCES; @@ -350,10 +370,17 @@ dasd_ioctl_set_ro(struct block_device *bdev, void __user *argp) return -EINVAL; if (get_user(intval, (int __user *)argp)) return -EFAULT; - if (!intval && test_bit(DASD_FLAG_DEVICE_RO, &block->base->flags)) + base = dasd_device_from_gendisk(bdev->bd_disk); + if (!base) + return -ENODEV; + if (!intval && test_bit(DASD_FLAG_DEVICE_RO, &base->flags)) { + dasd_put_device(base); return -EROFS; + } set_disk_ro(bdev->bd_disk, intval); - return dasd_set_feature(block->base->cdev, DASD_FEATURE_READONLY, intval); + rc = dasd_set_feature(base->cdev, DASD_FEATURE_READONLY, intval); + dasd_put_device(base); + return rc; } static int dasd_ioctl_readall_cmb(struct dasd_block *block, unsigned int cmd, @@ -372,59 +399,78 @@ static int dasd_ioctl_readall_cmb(struct dasd_block *block, unsigned int cmd, int dasd_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { - struct dasd_block *block = bdev->bd_disk->private_data; + struct dasd_block *block; + struct dasd_device *base; void __user *argp; + int rc; if (is_compat_task()) argp = compat_ptr(arg); else argp = (void __user *)arg; - if (!block) - return -ENODEV; - if ((_IOC_DIR(cmd) != _IOC_NONE) && !arg) { PRINT_DEBUG("empty data ptr"); return -EINVAL; } + base = dasd_device_from_gendisk(bdev->bd_disk); + if (!base) + return -ENODEV; + block = base->block; + rc = 0; switch (cmd) { case BIODASDDISABLE: - return dasd_ioctl_disable(bdev); + rc = dasd_ioctl_disable(bdev); + break; case BIODASDENABLE: - return dasd_ioctl_enable(bdev); + rc = dasd_ioctl_enable(bdev); + break; case BIODASDQUIESCE: - return dasd_ioctl_quiesce(block); + rc = dasd_ioctl_quiesce(block); + break; case BIODASDRESUME: - return dasd_ioctl_resume(block); + rc = dasd_ioctl_resume(block); + break; case BIODASDFMT: - return dasd_ioctl_format(bdev, argp); + rc = dasd_ioctl_format(bdev, argp); + break; case BIODASDINFO: - return dasd_ioctl_information(block, cmd, argp); + rc = dasd_ioctl_information(block, cmd, argp); + break; case BIODASDINFO2: - return dasd_ioctl_information(block, cmd, argp); + rc = dasd_ioctl_information(block, cmd, argp); + break; case BIODASDPRRD: - return dasd_ioctl_read_profile(block, argp); + rc = dasd_ioctl_read_profile(block, argp); + break; case BIODASDPRRST: - return dasd_ioctl_reset_profile(block); + rc = dasd_ioctl_reset_profile(block); + break; case BLKROSET: - return dasd_ioctl_set_ro(bdev, argp); + rc = dasd_ioctl_set_ro(bdev, argp); + break; case DASDAPIVER: - return dasd_ioctl_api_version(argp); + rc = dasd_ioctl_api_version(argp); + break; case BIODASDCMFENABLE: - return enable_cmf(block->base->cdev); + rc = enable_cmf(base->cdev); + break; case BIODASDCMFDISABLE: - return disable_cmf(block->base->cdev); + rc = disable_cmf(base->cdev); + break; case BIODASDREADALLCMB: - return dasd_ioctl_readall_cmb(block, cmd, argp); + rc = dasd_ioctl_readall_cmb(block, cmd, argp); + break; default: /* if the discipline has an ioctl method try it. */ - if (block->base->discipline->ioctl) { - int rval = block->base->discipline->ioctl(block, cmd, argp); - if (rval != -ENOIOCTLCMD) - return rval; - } - - return -EINVAL; + if (base->discipline->ioctl) { + rc = base->discipline->ioctl(block, cmd, argp); + if (rc == -ENOIOCTLCMD) + rc = -EINVAL; + } else + rc = -EINVAL; } + dasd_put_device(base); + return rc; } |