btrfs: qgroup: validate btrfs_qgroup_inherit parameter
[linux-2.6-microblaze.git] / fs / btrfs / qgroup.c
index b3bf08f..af241aa 100644 (file)
@@ -3046,6 +3046,57 @@ int btrfs_run_qgroups(struct btrfs_trans_handle *trans)
        return ret;
 }
 
+int btrfs_qgroup_check_inherit(struct btrfs_fs_info *fs_info,
+                              struct btrfs_qgroup_inherit *inherit,
+                              size_t size)
+{
+       if (inherit->flags & ~BTRFS_QGROUP_INHERIT_FLAGS_SUPP)
+               return -EOPNOTSUPP;
+       if (size < sizeof(*inherit) || size > PAGE_SIZE)
+               return -EINVAL;
+
+       /*
+        * In the past we allowed btrfs_qgroup_inherit to specify to copy
+        * rfer/excl numbers directly from other qgroups.  This behavior has
+        * been disabled in userspace for a very long time, but here we should
+        * also disable it in kernel, as this behavior is known to mark qgroup
+        * inconsistent, and a rescan would wipe out the changes anyway.
+        *
+        * Reject any btrfs_qgroup_inherit with num_ref_copies or num_excl_copies.
+        */
+       if (inherit->num_ref_copies > 0 || inherit->num_excl_copies > 0)
+               return -EINVAL;
+
+       if (inherit->num_qgroups > PAGE_SIZE)
+               return -EINVAL;
+
+       if (size != struct_size(inherit, qgroups, inherit->num_qgroups))
+               return -EINVAL;
+
+       /*
+        * Now check all the remaining qgroups, they should all:
+        *
+        * - Exist
+        * - Be higher level qgroups.
+        */
+       for (int i = 0; i < inherit->num_qgroups; i++) {
+               struct btrfs_qgroup *qgroup;
+               u64 qgroupid = inherit->qgroups[i];
+
+               if (btrfs_qgroup_level(qgroupid) == 0)
+                       return -EINVAL;
+
+               spin_lock(&fs_info->qgroup_lock);
+               qgroup = find_qgroup_rb(fs_info, qgroupid);
+               if (!qgroup) {
+                       spin_unlock(&fs_info->qgroup_lock);
+                       return -ENOENT;
+               }
+               spin_unlock(&fs_info->qgroup_lock);
+       }
+       return 0;
+}
+
 static int qgroup_auto_inherit(struct btrfs_fs_info *fs_info,
                               u64 inode_rootid,
                               struct btrfs_qgroup_inherit **inherit)