| From 6d2626ece99a02979b58885a883dbb8d286cf645 Mon Sep 17 00:00:00 2001 |
| From: Paolo Bonzini <pbonzini@redhat.com> |
| Date: Thu, 12 Jan 2012 16:01:28 +0100 |
| Subject: [PATCH] block: fail SCSI passthrough ioctls on partition devices |
| |
| commit 0bfc96cb77224736dfa35c3c555d37b3646ef35e upstream. |
| |
| Linux allows executing the SG_IO ioctl on a partition or LVM volume, and |
| will pass the command to the underlying block device. This is |
| well-known, but it is also a large security problem when (via Unix |
| permissions, ACLs, SELinux or a combination thereof) a program or user |
| needs to be granted access only to part of the disk. |
| |
| This patch lets partitions forward a small set of harmless ioctls; |
| others are logged with printk so that we can see which ioctls are |
| actually sent. In my tests only CDROM_GET_CAPABILITY actually occurred. |
| Of course it was being sent to a (partition on a) hard disk, so it would |
| have failed with ENOTTY and the patch isn't changing anything in |
| practice. Still, I'm treating it specially to avoid spamming the logs. |
| |
| In principle, this restriction should include programs running with |
| CAP_SYS_RAWIO. If for example I let a program access /dev/sda2 and |
| /dev/sdb, it still should not be able to read/write outside the |
| boundaries of /dev/sda2 independent of the capabilities. However, for |
| now programs with CAP_SYS_RAWIO will still be allowed to send the |
| ioctls. Their actions will still be logged. |
| |
| This patch does not affect the non-libata IDE driver. That driver |
| however already tests for bd != bd->bd_contains before issuing some |
| ioctl; it could be restricted further to forbid these ioctls even for |
| programs running with CAP_SYS_ADMIN/CAP_SYS_RAWIO. |
| |
| Cc: linux-scsi@vger.kernel.org |
| Cc: Jens Axboe <axboe@kernel.dk> |
| Cc: James Bottomley <JBottomley@parallels.com> |
| Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> |
| [ Make it also print the command name when warning - Linus ] |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| --- |
| block/scsi_ioctl.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ |
| drivers/scsi/sd.c | 11 +++++++++-- |
| include/linux/blkdev.h | 1 + |
| 3 files changed, 55 insertions(+), 2 deletions(-) |
| |
| diff --git a/block/scsi_ioctl.c b/block/scsi_ioctl.c |
| index 57ac93754841..b661f8940ef5 100644 |
| --- a/block/scsi_ioctl.c |
| +++ b/block/scsi_ioctl.c |
| @@ -24,6 +24,7 @@ |
| #include <linux/capability.h> |
| #include <linux/completion.h> |
| #include <linux/cdrom.h> |
| +#include <linux/ratelimit.h> |
| #include <linux/slab.h> |
| #include <linux/times.h> |
| #include <asm/uaccess.h> |
| @@ -691,9 +692,53 @@ int scsi_cmd_ioctl(struct request_queue *q, struct gendisk *bd_disk, fmode_t mod |
| } |
| EXPORT_SYMBOL(scsi_cmd_ioctl); |
| |
| +int scsi_verify_blk_ioctl(struct block_device *bd, unsigned int cmd) |
| +{ |
| + if (bd && bd == bd->bd_contains) |
| + return 0; |
| + |
| + /* Actually none of these is particularly useful on a partition, |
| + * but they are safe. |
| + */ |
| + switch (cmd) { |
| + case SCSI_IOCTL_GET_IDLUN: |
| + case SCSI_IOCTL_GET_BUS_NUMBER: |
| + case SCSI_IOCTL_GET_PCI: |
| + case SCSI_IOCTL_PROBE_HOST: |
| + case SG_GET_VERSION_NUM: |
| + case SG_SET_TIMEOUT: |
| + case SG_GET_TIMEOUT: |
| + case SG_GET_RESERVED_SIZE: |
| + case SG_SET_RESERVED_SIZE: |
| + case SG_EMULATED_HOST: |
| + return 0; |
| + case CDROM_GET_CAPABILITY: |
| + /* Keep this until we remove the printk below. udev sends it |
| + * and we do not want to spam dmesg about it. CD-ROMs do |
| + * not have partitions, so we get here only for disks. |
| + */ |
| + return -ENOIOCTLCMD; |
| + default: |
| + break; |
| + } |
| + |
| + /* In particular, rule out all resets and host-specific ioctls. */ |
| + printk_ratelimited(KERN_WARNING |
| + "%s: sending ioctl %x to a partition!\n", current->comm, cmd); |
| + |
| + return capable(CAP_SYS_RAWIO) ? 0 : -ENOIOCTLCMD; |
| +} |
| +EXPORT_SYMBOL(scsi_verify_blk_ioctl); |
| + |
| int scsi_cmd_blk_ioctl(struct block_device *bd, fmode_t mode, |
| unsigned int cmd, void __user *arg) |
| { |
| + int ret; |
| + |
| + ret = scsi_verify_blk_ioctl(bd, cmd); |
| + if (ret < 0) |
| + return ret; |
| + |
| return scsi_cmd_ioctl(bd->bd_disk->queue, bd->bd_disk, mode, cmd, arg); |
| } |
| EXPORT_SYMBOL(scsi_cmd_blk_ioctl); |
| diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c |
| index 654e2674e7c3..1b543a229487 100644 |
| --- a/drivers/scsi/sd.c |
| +++ b/drivers/scsi/sd.c |
| @@ -886,6 +886,10 @@ static int sd_ioctl(struct block_device *bdev, fmode_t mode, |
| SCSI_LOG_IOCTL(1, printk("sd_ioctl: disk=%s, cmd=0x%x\n", |
| disk->disk_name, cmd)); |
| |
| + error = scsi_verify_blk_ioctl(bdev, cmd); |
| + if (error < 0) |
| + return error; |
| + |
| /* |
| * If we are in the middle of error recovery, don't let anyone |
| * else try and use this device. Also, if error recovery fails, it |
| @@ -1065,6 +1069,11 @@ static int sd_compat_ioctl(struct block_device *bdev, fmode_t mode, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct scsi_device *sdev = scsi_disk(bdev->bd_disk)->device; |
| + int ret; |
| + |
| + ret = scsi_verify_blk_ioctl(bdev, cmd); |
| + if (ret < 0) |
| + return ret; |
| |
| /* |
| * If we are in the middle of error recovery, don't let anyone |
| @@ -1076,8 +1085,6 @@ static int sd_compat_ioctl(struct block_device *bdev, fmode_t mode, |
| return -ENODEV; |
| |
| if (sdev->host->hostt->compat_ioctl) { |
| - int ret; |
| - |
| ret = sdev->host->hostt->compat_ioctl(sdev, cmd, (void __user *)arg); |
| |
| return ret; |
| diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h |
| index ba55e497f7dc..22713e8385f0 100644 |
| --- a/include/linux/blkdev.h |
| +++ b/include/linux/blkdev.h |
| @@ -793,6 +793,7 @@ extern void blk_plug_device(struct request_queue *); |
| extern void blk_plug_device_unlocked(struct request_queue *); |
| extern int blk_remove_plug(struct request_queue *); |
| extern void blk_recount_segments(struct request_queue *, struct bio *); |
| +extern int scsi_verify_blk_ioctl(struct block_device *, unsigned int); |
| extern int scsi_cmd_blk_ioctl(struct block_device *, fmode_t, |
| unsigned int, void __user *); |
| extern int scsi_cmd_ioctl(struct request_queue *, struct gendisk *, fmode_t, |
| -- |
| 1.8.5.2 |
| |