Merge tag 'for-6.5/dm-changes' of git://git.kernel.org/pub/scm/linux/kernel/git/devic...
[platform/kernel/linux-rpi.git] / drivers / md / dm-ioctl.c
index 6d30101..f5ed729 100644 (file)
@@ -767,7 +767,14 @@ static int get_target_version(struct file *filp, struct dm_ioctl *param, size_t
 static int check_name(const char *name)
 {
        if (strchr(name, '/')) {
-               DMERR("invalid device name");
+               DMERR("device name cannot contain '/'");
+               return -EINVAL;
+       }
+
+       if (strcmp(name, DM_CONTROL_NODE) == 0 ||
+           strcmp(name, ".") == 0 ||
+           strcmp(name, "..") == 0) {
+               DMERR("device name cannot be \"%s\", \".\", or \"..\"", DM_CONTROL_NODE);
                return -EINVAL;
        }
 
@@ -1388,16 +1395,38 @@ static inline blk_mode_t get_mode(struct dm_ioctl *param)
        return mode;
 }
 
-static int next_target(struct dm_target_spec *last, uint32_t next, void *end,
+static int next_target(struct dm_target_spec *last, uint32_t next, const char *end,
                       struct dm_target_spec **spec, char **target_params)
 {
-       *spec = (struct dm_target_spec *) ((unsigned char *) last + next);
-       *target_params = (char *) (*spec + 1);
+       static_assert(__alignof__(struct dm_target_spec) <= 8,
+               "struct dm_target_spec must not require more than 8-byte alignment");
+
+       /*
+        * Number of bytes remaining, starting with last. This is always
+        * sizeof(struct dm_target_spec) or more, as otherwise *last was
+        * out of bounds already.
+        */
+       size_t remaining = end - (char *)last;
+
+       /*
+        * There must be room for both the next target spec and the
+        * NUL-terminator of the target itself.
+        */
+       if (remaining - sizeof(struct dm_target_spec) <= next) {
+               DMERR("Target spec extends beyond end of parameters");
+               return -EINVAL;
+       }
 
-       if (*spec < (last + 1))
+       if (next % __alignof__(struct dm_target_spec)) {
+               DMERR("Next dm_target_spec (offset %u) is not %zu-byte aligned",
+                     next, __alignof__(struct dm_target_spec));
                return -EINVAL;
+       }
+
+       *spec = (struct dm_target_spec *) ((unsigned char *) last + next);
+       *target_params = (char *) (*spec + 1);
 
-       return invalid_str(*target_params, end);
+       return 0;
 }
 
 static int populate_table(struct dm_table *table,
@@ -1407,8 +1436,9 @@ static int populate_table(struct dm_table *table,
        unsigned int i = 0;
        struct dm_target_spec *spec = (struct dm_target_spec *) param;
        uint32_t next = param->data_start;
-       void *end = (void *) param + param_size;
+       const char *const end = (const char *) param + param_size;
        char *target_params;
+       size_t min_size = sizeof(struct dm_ioctl);
 
        if (!param->target_count) {
                DMERR("%s: no targets specified", __func__);
@@ -1416,6 +1446,13 @@ static int populate_table(struct dm_table *table,
        }
 
        for (i = 0; i < param->target_count; i++) {
+               const char *nul_terminator;
+
+               if (next < min_size) {
+                       DMERR("%s: next target spec (offset %u) overlaps %s",
+                             __func__, next, i ? "previous target" : "'struct dm_ioctl'");
+                       return -EINVAL;
+               }
 
                r = next_target(spec, next, end, &spec, &target_params);
                if (r) {
@@ -1423,6 +1460,15 @@ static int populate_table(struct dm_table *table,
                        return r;
                }
 
+               nul_terminator = memchr(target_params, 0, (size_t)(end - target_params));
+               if (nul_terminator == NULL) {
+                       DMERR("%s: target parameters not NUL-terminated", __func__);
+                       return -EINVAL;
+               }
+
+               /* Add 1 for NUL terminator */
+               min_size = (size_t)(nul_terminator - (const char *)spec) + 1;
+
                r = dm_table_add_target(table, spec->target_type,
                                        (sector_t) spec->sector_start,
                                        (sector_t) spec->length,
@@ -1830,30 +1876,36 @@ static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags)
  * As well as checking the version compatibility this always
  * copies the kernel interface version out.
  */
-static int check_version(unsigned int cmd, struct dm_ioctl __user *user)
+static int check_version(unsigned int cmd, struct dm_ioctl __user *user,
+                        struct dm_ioctl *kernel_params)
 {
-       uint32_t version[3];
        int r = 0;
 
-       if (copy_from_user(version, user->version, sizeof(version)))
+       /* Make certain version is first member of dm_ioctl struct */
+       BUILD_BUG_ON(offsetof(struct dm_ioctl, version) != 0);
+
+       if (copy_from_user(kernel_params->version, user->version, sizeof(kernel_params->version)))
                return -EFAULT;
 
-       if ((version[0] != DM_VERSION_MAJOR) ||
-           (version[1] > DM_VERSION_MINOR)) {
+       if ((kernel_params->version[0] != DM_VERSION_MAJOR) ||
+           (kernel_params->version[1] > DM_VERSION_MINOR)) {
                DMERR("ioctl interface mismatch: kernel(%u.%u.%u), user(%u.%u.%u), cmd(%d)",
                      DM_VERSION_MAJOR, DM_VERSION_MINOR,
                      DM_VERSION_PATCHLEVEL,
-                     version[0], version[1], version[2], cmd);
+                     kernel_params->version[0],
+                     kernel_params->version[1],
+                     kernel_params->version[2],
+                     cmd);
                r = -EINVAL;
        }
 
        /*
         * Fill in the kernel version.
         */
-       version[0] = DM_VERSION_MAJOR;
-       version[1] = DM_VERSION_MINOR;
-       version[2] = DM_VERSION_PATCHLEVEL;
-       if (copy_to_user(user->version, version, sizeof(version)))
+       kernel_params->version[0] = DM_VERSION_MAJOR;
+       kernel_params->version[1] = DM_VERSION_MINOR;
+       kernel_params->version[2] = DM_VERSION_PATCHLEVEL;
+       if (copy_to_user(user->version, kernel_params->version, sizeof(kernel_params->version)))
                return -EFAULT;
 
        return r;
@@ -1877,9 +1929,11 @@ static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kern
        struct dm_ioctl *dmi;
        int secure_data;
        const size_t minimum_data_size = offsetof(struct dm_ioctl, data);
-       unsigned int noio_flag;
 
-       if (copy_from_user(param_kernel, user, minimum_data_size))
+       /* check_version() already copied version from userspace, avoid TOCTOU */
+       if (copy_from_user((char *)param_kernel + sizeof(param_kernel->version),
+                          (char __user *)user + sizeof(param_kernel->version),
+                          minimum_data_size - sizeof(param_kernel->version)))
                return -EFAULT;
 
        if (param_kernel->data_size < minimum_data_size) {
@@ -1904,9 +1958,7 @@ static int copy_params(struct dm_ioctl __user *user, struct dm_ioctl *param_kern
         * Use kmalloc() rather than vmalloc() when we can.
         */
        dmi = NULL;
-       noio_flag = memalloc_noio_save();
-       dmi = kvmalloc(param_kernel->data_size, GFP_KERNEL | __GFP_HIGH);
-       memalloc_noio_restore(noio_flag);
+       dmi = kvmalloc(param_kernel->data_size, GFP_NOIO | __GFP_HIGH);
 
        if (!dmi) {
                if (secure_data && clear_user(user, param_kernel->data_size))
@@ -1991,7 +2043,7 @@ static int ctl_ioctl(struct file *file, uint command, struct dm_ioctl __user *us
         * Check the interface version passed in.  This also
         * writes out the kernel's interface version.
         */
-       r = check_version(cmd, user);
+       r = check_version(cmd, user, &param_kernel);
        if (r)
                return r;