From: David Sterba <[email protected]>

commit f37c563bab4297024c300b05c8f48430e323809d upstream.

User Forza reported on IRC that some invalid combinations of file
attributes are accepted by chattr.

The NODATACOW and compression file flags/attributes are mutually
exclusive, but they could be set by 'chattr +c +C' on an empty file. The
nodatacow will be in effect because it's checked first in
btrfs_run_delalloc_range.

Extend the flag validation to catch the following cases:

  - input flags are conflicting
  - old and new flags are conflicting
  - initialize the local variable with inode flags after inode ls locked

Inode attributes take precedence over mount options and are an
independent setting.

Nocompress would be a no-op with nodatacow, but we don't want to mix
any compression-related options with nodatacow.

CC: [email protected] # 4.4+
Signed-off-by: David Sterba <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>

---
 fs/btrfs/ioctl.c |   30 ++++++++++++++++++++++--------
 1 file changed, 22 insertions(+), 8 deletions(-)

--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -167,8 +167,11 @@ static int btrfs_ioctl_getflags(struct f
        return 0;
 }
 
-/* Check if @flags are a supported and valid set of FS_*_FL flags */
-static int check_fsflags(unsigned int flags)
+/*
+ * Check if @flags are a supported and valid set of FS_*_FL flags and that
+ * the old and new flags are not conflicting
+ */
+static int check_fsflags(unsigned int old_flags, unsigned int flags)
 {
        if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | \
                      FS_NOATIME_FL | FS_NODUMP_FL | \
@@ -177,9 +180,19 @@ static int check_fsflags(unsigned int fl
                      FS_NOCOW_FL))
                return -EOPNOTSUPP;
 
+       /* COMPR and NOCOMP on new/old are valid */
        if ((flags & FS_NOCOMP_FL) && (flags & FS_COMPR_FL))
                return -EINVAL;
 
+       if ((flags & FS_COMPR_FL) && (flags & FS_NOCOW_FL))
+               return -EINVAL;
+
+       /* NOCOW and compression options are mutually exclusive */
+       if ((old_flags & FS_NOCOW_FL) && (flags & (FS_COMPR_FL | FS_NOCOMP_FL)))
+               return -EINVAL;
+       if ((flags & FS_NOCOW_FL) && (old_flags & (FS_COMPR_FL | FS_NOCOMP_FL)))
+               return -EINVAL;
+
        return 0;
 }
 
@@ -193,7 +206,7 @@ static int btrfs_ioctl_setflags(struct f
        unsigned int fsflags, old_fsflags;
        int ret;
        const char *comp = NULL;
-       u32 binode_flags = binode->flags;
+       u32 binode_flags;
 
        if (!inode_owner_or_capable(inode))
                return -EPERM;
@@ -204,22 +217,23 @@ static int btrfs_ioctl_setflags(struct f
        if (copy_from_user(&fsflags, arg, sizeof(fsflags)))
                return -EFAULT;
 
-       ret = check_fsflags(fsflags);
-       if (ret)
-               return ret;
-
        ret = mnt_want_write_file(file);
        if (ret)
                return ret;
 
        inode_lock(inode);
-
        fsflags = btrfs_mask_fsflags_for_type(inode, fsflags);
        old_fsflags = btrfs_inode_flags_to_fsflags(binode->flags);
+
        ret = vfs_ioc_setflags_prepare(inode, old_fsflags, fsflags);
        if (ret)
                goto out_unlock;
 
+       ret = check_fsflags(old_fsflags, fsflags);
+       if (ret)
+               goto out_unlock;
+
+       binode_flags = binode->flags;
        if (fsflags & FS_SYNC_FL)
                binode_flags |= BTRFS_INODE_SYNC;
        else


Reply via email to