]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - block/ioctl.c
net: disable BRIDGE_NETFILTER by default
[linux.git] / block / ioctl.c
index 5de98b97af2a0c7b4db99ea6ed72c8081edd5cce..127194b9f9bda40ade08d36c89445aae32223cba 100644 (file)
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 #include <linux/capability.h>
+#include <linux/compat.h>
 #include <linux/blkdev.h>
 #include <linux/export.h>
 #include <linux/gfp.h>
 #include <linux/pr.h>
 #include <linux/uaccess.h>
 
-static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user *arg)
+static int blkpg_do_ioctl(struct block_device *bdev,
+                         struct blkpg_partition __user *upart, int op)
 {
        struct block_device *bdevp;
        struct gendisk *disk;
        struct hd_struct *part, *lpart;
-       struct blkpg_ioctl_arg a;
        struct blkpg_partition p;
        struct disk_part_iter piter;
        long long start, length;
@@ -24,9 +25,7 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user
 
        if (!capable(CAP_SYS_ADMIN))
                return -EACCES;
-       if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg)))
-               return -EFAULT;
-       if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition)))
+       if (copy_from_user(&p, upart, sizeof(struct blkpg_partition)))
                return -EFAULT;
        disk = bdev->bd_disk;
        if (bdev != bdev->bd_contains)
@@ -34,7 +33,7 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user
        partno = p.pno;
        if (partno <= 0)
                return -EINVAL;
-       switch (a.op) {
+       switch (op) {
                case BLKPG_ADD_PARTITION:
                        start = p.start >> 9;
                        length = p.length >> 9;
@@ -155,6 +154,39 @@ static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user
        }
 }
 
+static int blkpg_ioctl(struct block_device *bdev,
+                      struct blkpg_ioctl_arg __user *arg)
+{
+       struct blkpg_partition __user *udata;
+       int op;
+
+       if (get_user(op, &arg->op) || get_user(udata, &arg->data))
+               return -EFAULT;
+
+       return blkpg_do_ioctl(bdev, udata, op);
+}
+
+#ifdef CONFIG_COMPAT
+struct compat_blkpg_ioctl_arg {
+       compat_int_t op;
+       compat_int_t flags;
+       compat_int_t datalen;
+       compat_caddr_t data;
+};
+
+static int compat_blkpg_ioctl(struct block_device *bdev,
+                             struct compat_blkpg_ioctl_arg __user *arg)
+{
+       compat_caddr_t udata;
+       int op;
+
+       if (get_user(op, &arg->op) || get_user(udata, &arg->data))
+               return -EFAULT;
+
+       return blkpg_do_ioctl(bdev, compat_ptr(udata), op);
+}
+#endif
+
 static int blkdev_reread_part(struct block_device *bdev)
 {
        int ret;
@@ -238,36 +270,48 @@ static int blk_ioctl_zeroout(struct block_device *bdev, fmode_t mode,
                        BLKDEV_ZERO_NOUNMAP);
 }
 
-static int put_ushort(unsigned long arg, unsigned short val)
+static int put_ushort(unsigned short __user *argp, unsigned short val)
 {
-       return put_user(val, (unsigned short __user *)arg);
+       return put_user(val, argp);
 }
 
-static int put_int(unsigned long arg, int val)
+static int put_int(int __user *argp, int val)
 {
-       return put_user(val, (int __user *)arg);
+       return put_user(val, argp);
 }
 
-static int put_uint(unsigned long arg, unsigned int val)
+static int put_uint(unsigned int __user *argp, unsigned int val)
 {
-       return put_user(val, (unsigned int __user *)arg);
+       return put_user(val, argp);
 }
 
-static int put_long(unsigned long arg, long val)
+static int put_long(long __user *argp, long val)
 {
-       return put_user(val, (long __user *)arg);
+       return put_user(val, argp);
 }
 
-static int put_ulong(unsigned long arg, unsigned long val)
+static int put_ulong(unsigned long __user *argp, unsigned long val)
 {
-       return put_user(val, (unsigned long __user *)arg);
+       return put_user(val, argp);
 }
 
-static int put_u64(unsigned long arg, u64 val)
+static int put_u64(u64 __user *argp, u64 val)
 {
-       return put_user(val, (u64 __user *)arg);
+       return put_user(val, argp);
 }
 
+#ifdef CONFIG_COMPAT
+static int compat_put_long(compat_long_t *argp, long val)
+{
+       return put_user(val, argp);
+}
+
+static int compat_put_ulong(compat_ulong_t *argp, compat_ulong_t val)
+{
+       return put_user(val, argp);
+}
+#endif
+
 int __blkdev_driver_ioctl(struct block_device *bdev, fmode_t mode,
                        unsigned cmd, unsigned long arg)
 {
@@ -285,6 +329,26 @@ int __blkdev_driver_ioctl(struct block_device *bdev, fmode_t mode,
  */
 EXPORT_SYMBOL_GPL(__blkdev_driver_ioctl);
 
+#ifdef CONFIG_COMPAT
+/*
+ * This is the equivalent of compat_ptr_ioctl(), to be used by block
+ * drivers that implement only commands that are completely compatible
+ * between 32-bit and 64-bit user space
+ */
+int blkdev_compat_ptr_ioctl(struct block_device *bdev, fmode_t mode,
+                       unsigned cmd, unsigned long arg)
+{
+       struct gendisk *disk = bdev->bd_disk;
+
+       if (disk->fops->ioctl)
+               return disk->fops->ioctl(bdev, mode, cmd,
+                                        (unsigned long)compat_ptr(arg));
+
+       return -ENOIOCTLCMD;
+}
+EXPORT_SYMBOL(blkdev_compat_ptr_ioctl);
+#endif
+
 static int blkdev_pr_register(struct block_device *bdev,
                struct pr_registration __user *arg)
 {
@@ -455,6 +519,45 @@ static int blkdev_getgeo(struct block_device *bdev,
        return 0;
 }
 
+#ifdef CONFIG_COMPAT
+struct compat_hd_geometry {
+       unsigned char heads;
+       unsigned char sectors;
+       unsigned short cylinders;
+       u32 start;
+};
+
+static int compat_hdio_getgeo(struct block_device *bdev,
+                             struct compat_hd_geometry __user *ugeo)
+{
+       struct gendisk *disk = bdev->bd_disk;
+       struct hd_geometry geo;
+       int ret;
+
+       if (!ugeo)
+               return -EINVAL;
+       if (!disk->fops->getgeo)
+               return -ENOTTY;
+
+       memset(&geo, 0, sizeof(geo));
+       /*
+        * We need to set the startsect first, the driver may
+        * want to override it.
+        */
+       geo.start = get_start_sect(bdev);
+       ret = disk->fops->getgeo(bdev, &geo);
+       if (ret)
+               return ret;
+
+       ret = copy_to_user(ugeo, &geo, 4);
+       ret |= put_user(geo.start, &ugeo->start);
+       if (ret)
+               ret = -EFAULT;
+
+       return ret;
+}
+#endif
+
 /* set the logical block size */
 static int blkdev_bszset(struct block_device *bdev, fmode_t mode,
                int __user *argp)
@@ -481,13 +584,13 @@ static int blkdev_bszset(struct block_device *bdev, fmode_t mode,
 }
 
 /*
- * always keep this in sync with compat_blkdev_ioctl()
+ * Common commands that are handled the same way on native and compat
+ * user space. Note the separate arg/argp parameters that are needed
+ * to deal with the compat_ptr() conversion.
  */
-int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,
-                       unsigned long arg)
+static int blkdev_common_ioctl(struct block_device *bdev, fmode_t mode,
+                               unsigned cmd, unsigned long arg, void __user *argp)
 {
-       void __user *argp = (void __user *)arg;
-       loff_t size;
        unsigned int max_sectors;
 
        switch (cmd) {
@@ -510,60 +613,39 @@ int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,
        case BLKFINISHZONE:
                return blkdev_zone_mgmt_ioctl(bdev, mode, cmd, arg);
        case BLKGETZONESZ:
-               return put_uint(arg, bdev_zone_sectors(bdev));
+               return put_uint(argp, bdev_zone_sectors(bdev));
        case BLKGETNRZONES:
-               return put_uint(arg, blkdev_nr_zones(bdev->bd_disk));
-       case HDIO_GETGEO:
-               return blkdev_getgeo(bdev, argp);
-       case BLKRAGET:
-       case BLKFRAGET:
-               if (!arg)
-                       return -EINVAL;
-               return put_long(arg, (bdev->bd_bdi->ra_pages*PAGE_SIZE) / 512);
+               return put_uint(argp, blkdev_nr_zones(bdev->bd_disk));
        case BLKROGET:
-               return put_int(arg, bdev_read_only(bdev) != 0);
-       case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */
-               return put_int(arg, block_size(bdev));
+               return put_int(argp, bdev_read_only(bdev) != 0);
        case BLKSSZGET: /* get block device logical block size */
-               return put_int(arg, bdev_logical_block_size(bdev));
+               return put_int(argp, bdev_logical_block_size(bdev));
        case BLKPBSZGET: /* get block device physical block size */
-               return put_uint(arg, bdev_physical_block_size(bdev));
+               return put_uint(argp, bdev_physical_block_size(bdev));
        case BLKIOMIN:
-               return put_uint(arg, bdev_io_min(bdev));
+               return put_uint(argp, bdev_io_min(bdev));
        case BLKIOOPT:
-               return put_uint(arg, bdev_io_opt(bdev));
+               return put_uint(argp, bdev_io_opt(bdev));
        case BLKALIGNOFF:
-               return put_int(arg, bdev_alignment_offset(bdev));
+               return put_int(argp, bdev_alignment_offset(bdev));
        case BLKDISCARDZEROES:
-               return put_uint(arg, 0);
+               return put_uint(argp, 0);
        case BLKSECTGET:
                max_sectors = min_t(unsigned int, USHRT_MAX,
                                    queue_max_sectors(bdev_get_queue(bdev)));
-               return put_ushort(arg, max_sectors);
+               return put_ushort(argp, max_sectors);
        case BLKROTATIONAL:
-               return put_ushort(arg, !blk_queue_nonrot(bdev_get_queue(bdev)));
+               return put_ushort(argp, !blk_queue_nonrot(bdev_get_queue(bdev)));
        case BLKRASET:
        case BLKFRASET:
                if(!capable(CAP_SYS_ADMIN))
                        return -EACCES;
                bdev->bd_bdi->ra_pages = (arg * 512) / PAGE_SIZE;
                return 0;
-       case BLKBSZSET:
-               return blkdev_bszset(bdev, mode, argp);
-       case BLKPG:
-               return blkpg_ioctl(bdev, argp);
        case BLKRRPART:
                return blkdev_reread_part(bdev);
-       case BLKGETSIZE:
-               size = i_size_read(bdev->bd_inode);
-               if ((size >> 9) > ~0UL)
-                       return -EFBIG;
-               return put_ulong(arg, size >> 9);
-       case BLKGETSIZE64:
-               return put_u64(arg, i_size_read(bdev->bd_inode));
        case BLKTRACESTART:
        case BLKTRACESTOP:
-       case BLKTRACESETUP:
        case BLKTRACETEARDOWN:
                return blk_trace_ioctl(bdev, cmd, argp);
        case IOC_PR_REGISTER:
@@ -579,7 +661,132 @@ int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,
        case IOC_PR_CLEAR:
                return blkdev_pr_clear(bdev, argp);
        default:
+               return -ENOIOCTLCMD;
+       }
+}
+
+/*
+ * Always keep this in sync with compat_blkdev_ioctl()
+ * to handle all incompatible commands in both functions.
+ *
+ * New commands must be compatible and go into blkdev_common_ioctl
+ */
+int blkdev_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,
+                       unsigned long arg)
+{
+       int ret;
+       loff_t size;
+       void __user *argp = (void __user *)arg;
+
+       switch (cmd) {
+       /* These need separate implementations for the data structure */
+       case HDIO_GETGEO:
+               return blkdev_getgeo(bdev, argp);
+       case BLKPG:
+               return blkpg_ioctl(bdev, argp);
+
+       /* Compat mode returns 32-bit data instead of 'long' */
+       case BLKRAGET:
+       case BLKFRAGET:
+               if (!argp)
+                       return -EINVAL;
+               return put_long(argp, (bdev->bd_bdi->ra_pages*PAGE_SIZE) / 512);
+       case BLKGETSIZE:
+               size = i_size_read(bdev->bd_inode);
+               if ((size >> 9) > ~0UL)
+                       return -EFBIG;
+               return put_ulong(argp, size >> 9);
+
+       /* The data is compatible, but the command number is different */
+       case BLKBSZGET: /* get block device soft block size (cf. BLKSSZGET) */
+               return put_int(argp, block_size(bdev));
+       case BLKBSZSET:
+               return blkdev_bszset(bdev, mode, argp);
+       case BLKGETSIZE64:
+               return put_u64(argp, i_size_read(bdev->bd_inode));
+
+       /* Incompatible alignment on i386 */
+       case BLKTRACESETUP:
+               return blk_trace_ioctl(bdev, cmd, argp);
+       default:
+               break;
+       }
+
+       ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp);
+       if (ret == -ENOIOCTLCMD)
                return __blkdev_driver_ioctl(bdev, mode, cmd, arg);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(blkdev_ioctl); /* for /dev/raw */
+
+#ifdef CONFIG_COMPAT
+
+#define BLKBSZGET_32           _IOR(0x12, 112, int)
+#define BLKBSZSET_32           _IOW(0x12, 113, int)
+#define BLKGETSIZE64_32                _IOR(0x12, 114, int)
+
+/* Most of the generic ioctls are handled in the normal fallback path.
+   This assumes the blkdev's low level compat_ioctl always returns
+   ENOIOCTLCMD for unknown ioctls. */
+long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
+{
+       int ret;
+       void __user *argp = compat_ptr(arg);
+       struct inode *inode = file->f_mapping->host;
+       struct block_device *bdev = inode->i_bdev;
+       struct gendisk *disk = bdev->bd_disk;
+       fmode_t mode = file->f_mode;
+       loff_t size;
+
+       /*
+        * O_NDELAY can be altered using fcntl(.., F_SETFL, ..), so we have
+        * to updated it before every ioctl.
+        */
+       if (file->f_flags & O_NDELAY)
+               mode |= FMODE_NDELAY;
+       else
+               mode &= ~FMODE_NDELAY;
+
+       switch (cmd) {
+       /* These need separate implementations for the data structure */
+       case HDIO_GETGEO:
+               return compat_hdio_getgeo(bdev, argp);
+       case BLKPG:
+               return compat_blkpg_ioctl(bdev, argp);
+
+       /* Compat mode returns 32-bit data instead of 'long' */
+       case BLKRAGET:
+       case BLKFRAGET:
+               if (!argp)
+                       return -EINVAL;
+               return compat_put_long(argp,
+                              (bdev->bd_bdi->ra_pages * PAGE_SIZE) / 512);
+       case BLKGETSIZE:
+               size = i_size_read(bdev->bd_inode);
+               if ((size >> 9) > ~0UL)
+                       return -EFBIG;
+               return compat_put_ulong(argp, size >> 9);
+
+       /* The data is compatible, but the command number is different */
+       case BLKBSZGET_32: /* get the logical block size (cf. BLKSSZGET) */
+               return put_int(argp, bdev_logical_block_size(bdev));
+       case BLKBSZSET_32:
+               return blkdev_bszset(bdev, mode, argp);
+       case BLKGETSIZE64_32:
+               return put_u64(argp, i_size_read(bdev->bd_inode));
+
+       /* Incompatible alignment on i386 */
+       case BLKTRACESETUP32:
+               return blk_trace_ioctl(bdev, cmd, argp);
+       default:
+               break;
        }
+
+       ret = blkdev_common_ioctl(bdev, mode, cmd, arg, argp);
+       if (ret == -ENOIOCTLCMD && disk->fops->compat_ioctl)
+               ret = disk->fops->compat_ioctl(bdev, mode, cmd, arg);
+
+       return ret;
 }
-EXPORT_SYMBOL_GPL(blkdev_ioctl);
+#endif