]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/commitdiff
vfs: create a generic checking and prep function for FS_IOC_SETFLAGS
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 1 Jul 2019 15:25:34 +0000 (08:25 -0700)
committerDarrick J. Wong <darrick.wong@oracle.com>
Mon, 1 Jul 2019 15:25:34 +0000 (08:25 -0700)
Create a generic function to check incoming FS_IOC_SETFLAGS flag values
and later prepare the inode for updates so that we can standardize the
implementations that follow ext4's flag values.

Note that the efivarfs implementation no longer fails a no-op SETFLAGS
without CAP_LINUX_IMMUTABLE since that's the behavior in ext*.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Acked-by: David Sterba <dsterba@suse.com>
Reviewed-by: Bob Peterson <rpeterso@redhat.com>
14 files changed:
fs/btrfs/ioctl.c
fs/efivarfs/file.c
fs/ext2/ioctl.c
fs/ext4/ioctl.c
fs/gfs2/file.c
fs/hfsplus/ioctl.c
fs/inode.c
fs/jfs/ioctl.c
fs/nilfs2/ioctl.c
fs/ocfs2/ioctl.c
fs/orangefs/file.c
fs/reiserfs/ioctl.c
fs/ubifs/ioctl.c
include/linux/fs.h

index 6dafa857bbb9a9bd003fc55aa8a0677a86a22fd5..d3d9b4abb09b0730ed1a749f7bf1fd1aa1594842 100644 (file)
@@ -187,7 +187,7 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
        struct btrfs_inode *binode = BTRFS_I(inode);
        struct btrfs_root *root = binode->root;
        struct btrfs_trans_handle *trans;
-       unsigned int fsflags;
+       unsigned int fsflags, old_fsflags;
        int ret;
        const char *comp = NULL;
        u32 binode_flags = binode->flags;
@@ -212,13 +212,10 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
        inode_lock(inode);
 
        fsflags = btrfs_mask_fsflags_for_type(inode, fsflags);
-       if ((fsflags ^ btrfs_inode_flags_to_fsflags(binode->flags)) &
-           (FS_APPEND_FL | FS_IMMUTABLE_FL)) {
-               if (!capable(CAP_LINUX_IMMUTABLE)) {
-                       ret = -EPERM;
-                       goto out_unlock;
-               }
-       }
+       old_fsflags = btrfs_inode_flags_to_fsflags(binode->flags);
+       ret = vfs_ioc_setflags_prepare(inode, old_fsflags, fsflags);
+       if (ret)
+               goto out_unlock;
 
        if (fsflags & FS_SYNC_FL)
                binode_flags |= BTRFS_INODE_SYNC;
index 8e568428c88be059299b5ada7eb48e7d289a04fe..a3cc10b1bfe1d1e756d6c18ea88023080b5d2e4e 100644 (file)
@@ -110,16 +110,22 @@ out_free:
        return size;
 }
 
-static int
-efivarfs_ioc_getxflags(struct file *file, void __user *arg)
+static inline unsigned int efivarfs_getflags(struct inode *inode)
 {
-       struct inode *inode = file->f_mapping->host;
        unsigned int i_flags;
        unsigned int flags = 0;
 
        i_flags = inode->i_flags;
        if (i_flags & S_IMMUTABLE)
                flags |= FS_IMMUTABLE_FL;
+       return flags;
+}
+
+static int
+efivarfs_ioc_getxflags(struct file *file, void __user *arg)
+{
+       struct inode *inode = file->f_mapping->host;
+       unsigned int flags = efivarfs_getflags(inode);
 
        if (copy_to_user(arg, &flags, sizeof(flags)))
                return -EFAULT;
@@ -132,6 +138,7 @@ efivarfs_ioc_setxflags(struct file *file, void __user *arg)
        struct inode *inode = file->f_mapping->host;
        unsigned int flags;
        unsigned int i_flags = 0;
+       unsigned int oldflags = efivarfs_getflags(inode);
        int error;
 
        if (!inode_owner_or_capable(inode))
@@ -143,9 +150,6 @@ efivarfs_ioc_setxflags(struct file *file, void __user *arg)
        if (flags & ~FS_IMMUTABLE_FL)
                return -EOPNOTSUPP;
 
-       if (!capable(CAP_LINUX_IMMUTABLE))
-               return -EPERM;
-
        if (flags & FS_IMMUTABLE_FL)
                i_flags |= S_IMMUTABLE;
 
@@ -155,12 +159,16 @@ efivarfs_ioc_setxflags(struct file *file, void __user *arg)
                return error;
 
        inode_lock(inode);
+
+       error = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (error)
+               goto out;
+
        inode_set_flags(inode, i_flags, S_IMMUTABLE);
+out:
        inode_unlock(inode);
-
        mnt_drop_write_file(file);
-
-       return 0;
+       return error;
 }
 
 static long
index 0367c0039e68fd7ef25e102d3e0b2ab85f198132..1b853fb0b1639f6fb123954326bc886606a6be01 100644 (file)
@@ -60,18 +60,10 @@ long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                }
                oldflags = ei->i_flags;
 
-               /*
-                * The IMMUTABLE and APPEND_ONLY flags can only be changed by
-                * the relevant capability.
-                *
-                * This test looks nicer. Thanks to Pauline Middelink
-                */
-               if ((flags ^ oldflags) & (EXT2_APPEND_FL | EXT2_IMMUTABLE_FL)) {
-                       if (!capable(CAP_LINUX_IMMUTABLE)) {
-                               inode_unlock(inode);
-                               ret = -EPERM;
-                               goto setflags_out;
-                       }
+               ret = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+               if (ret) {
+                       inode_unlock(inode);
+                       goto setflags_out;
                }
 
                flags = flags & EXT2_FL_USER_MODIFIABLE;
index e486e49b31ed7ac7e0db17f0e4205e51fb67538d..272b6e44191b1451027a30014ff80a727ee38bf5 100644 (file)
@@ -289,16 +289,9 @@ static int ext4_ioctl_setflags(struct inode *inode,
        /* The JOURNAL_DATA flag is modifiable only by root */
        jflag = flags & EXT4_JOURNAL_DATA_FL;
 
-       /*
-        * The IMMUTABLE and APPEND_ONLY flags can only be changed by
-        * the relevant capability.
-        *
-        * This test looks nicer. Thanks to Pauline Middelink
-        */
-       if ((flags ^ oldflags) & (EXT4_APPEND_FL | EXT4_IMMUTABLE_FL)) {
-               if (!capable(CAP_LINUX_IMMUTABLE))
-                       goto flags_out;
-       }
+       err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (err)
+               goto flags_out;
 
        /*
         * The JOURNAL_DATA flag can only be changed by
index d174b1f8fd0883c288d555daa630b0fa674e93dd..1cb0c3afd3dc521c9610cf0f3bc6fe57e66c782f 100644 (file)
@@ -136,27 +136,36 @@ static struct {
        {FS_JOURNAL_DATA_FL, GFS2_DIF_JDATA | GFS2_DIF_INHERIT_JDATA},
 };
 
+static inline u32 gfs2_gfsflags_to_fsflags(struct inode *inode, u32 gfsflags)
+{
+       int i;
+       u32 fsflags = 0;
+
+       if (S_ISDIR(inode->i_mode))
+               gfsflags &= ~GFS2_DIF_JDATA;
+       else
+               gfsflags &= ~GFS2_DIF_INHERIT_JDATA;
+
+       for (i = 0; i < ARRAY_SIZE(fsflag_gfs2flag); i++)
+               if (gfsflags & fsflag_gfs2flag[i].gfsflag)
+                       fsflags |= fsflag_gfs2flag[i].fsflag;
+       return fsflags;
+}
+
 static int gfs2_get_flags(struct file *filp, u32 __user *ptr)
 {
        struct inode *inode = file_inode(filp);
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_holder gh;
-       int i, error;
-       u32 gfsflags, fsflags = 0;
+       int error;
+       u32 fsflags;
 
        gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh);
        error = gfs2_glock_nq(&gh);
        if (error)
                goto out_uninit;
 
-       gfsflags = ip->i_diskflags;
-       if (S_ISDIR(inode->i_mode))
-               gfsflags &= ~GFS2_DIF_JDATA;
-       else
-               gfsflags &= ~GFS2_DIF_INHERIT_JDATA;
-       for (i = 0; i < ARRAY_SIZE(fsflag_gfs2flag); i++)
-               if (gfsflags & fsflag_gfs2flag[i].gfsflag)
-                       fsflags |= fsflag_gfs2flag[i].fsflag;
+       fsflags = gfs2_gfsflags_to_fsflags(inode, ip->i_diskflags);
 
        if (put_user(fsflags, ptr))
                error = -EFAULT;
@@ -200,9 +209,11 @@ void gfs2_set_inode_flags(struct inode *inode)
  * @filp: file pointer
  * @reqflags: The flags to set
  * @mask: Indicates which flags are valid
+ * @fsflags: The FS_* inode flags passed in
  *
  */
-static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask)
+static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask,
+                            const u32 fsflags)
 {
        struct inode *inode = file_inode(filp);
        struct gfs2_inode *ip = GFS2_I(inode);
@@ -210,7 +221,7 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask)
        struct buffer_head *bh;
        struct gfs2_holder gh;
        int error;
-       u32 new_flags, flags;
+       u32 new_flags, flags, oldflags;
 
        error = mnt_want_write_file(filp);
        if (error)
@@ -220,6 +231,11 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask)
        if (error)
                goto out_drop_write;
 
+       oldflags = gfs2_gfsflags_to_fsflags(inode, ip->i_diskflags);
+       error = vfs_ioc_setflags_prepare(inode, oldflags, fsflags);
+       if (error)
+               goto out;
+
        error = -EACCES;
        if (!inode_owner_or_capable(inode))
                goto out;
@@ -308,7 +324,7 @@ static int gfs2_set_flags(struct file *filp, u32 __user *ptr)
                mask &= ~(GFS2_DIF_TOPDIR | GFS2_DIF_INHERIT_JDATA);
        }
 
-       return do_gfs2_set_flags(filp, gfsflags, mask);
+       return do_gfs2_set_flags(filp, gfsflags, mask, fsflags);
 }
 
 static int gfs2_getlabel(struct file *filp, char __user *label)
index 5e6502ef7415426b67338e0ed7210aaecc9b065c..ce15b9496b77e058e331afa0a4a1ea83c9c3011a 100644 (file)
@@ -57,9 +57,8 @@ static int hfsplus_ioctl_bless(struct file *file, int __user *user_flags)
        return 0;
 }
 
-static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)
+static inline unsigned int hfsplus_getflags(struct inode *inode)
 {
-       struct inode *inode = file_inode(file);
        struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
        unsigned int flags = 0;
 
@@ -69,6 +68,13 @@ static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)
                flags |= FS_APPEND_FL;
        if (hip->userflags & HFSPLUS_FLG_NODUMP)
                flags |= FS_NODUMP_FL;
+       return flags;
+}
+
+static int hfsplus_ioctl_getflags(struct file *file, int __user *user_flags)
+{
+       struct inode *inode = file_inode(file);
+       unsigned int flags = hfsplus_getflags(inode);
 
        return put_user(flags, user_flags);
 }
@@ -78,6 +84,7 @@ static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)
        struct inode *inode = file_inode(file);
        struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
        unsigned int flags, new_fl = 0;
+       unsigned int oldflags = hfsplus_getflags(inode);
        int err = 0;
 
        err = mnt_want_write_file(file);
@@ -96,13 +103,9 @@ static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags)
 
        inode_lock(inode);
 
-       if ((flags & (FS_IMMUTABLE_FL|FS_APPEND_FL)) ||
-           inode->i_flags & (S_IMMUTABLE|S_APPEND)) {
-               if (!capable(CAP_LINUX_IMMUTABLE)) {
-                       err = -EPERM;
-                       goto out_unlock_inode;
-               }
-       }
+       err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (err)
+               goto out_unlock_inode;
 
        /* don't silently ignore unsupported ext2 flags */
        if (flags & ~(FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NODUMP_FL)) {
index df6542ec3b88be2b3000ac21262f7d5823303e09..8072a09fd0b9673f5dd59919a71caff33352184c 100644 (file)
@@ -2170,3 +2170,27 @@ struct timespec64 current_time(struct inode *inode)
        return timespec64_trunc(now, inode->i_sb->s_time_gran);
 }
 EXPORT_SYMBOL(current_time);
+
+/*
+ * Generic function to check FS_IOC_SETFLAGS values and reject any invalid
+ * configurations.
+ *
+ * Note: the caller should be holding i_mutex, or else be sure that they have
+ * exclusive access to the inode structure.
+ */
+int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
+                            unsigned int flags)
+{
+       /*
+        * The IMMUTABLE and APPEND_ONLY flags can only be changed by
+        * the relevant capability.
+        *
+        * This test looks nicer. Thanks to Pauline Middelink
+        */
+       if ((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL) &&
+           !capable(CAP_LINUX_IMMUTABLE))
+               return -EPERM;
+
+       return 0;
+}
+EXPORT_SYMBOL(vfs_ioc_setflags_prepare);
index ba34dae8bd9ffd42a615453441f789938011b2fb..10ee0ecca1a82ff8c1a80e8c48cc60f7d63202d1 100644 (file)
@@ -98,24 +98,16 @@ long jfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                /* Lock against other parallel changes of flags */
                inode_lock(inode);
 
-               oldflags = jfs_inode->mode2;
-
-               /*
-                * The IMMUTABLE and APPEND_ONLY flags can only be changed by
-                * the relevant capability.
-                */
-               if ((oldflags & JFS_IMMUTABLE_FL) ||
-                       ((flags ^ oldflags) &
-                       (JFS_APPEND_FL | JFS_IMMUTABLE_FL))) {
-                       if (!capable(CAP_LINUX_IMMUTABLE)) {
-                               inode_unlock(inode);
-                               err = -EPERM;
-                               goto setflags_out;
-                       }
+               oldflags = jfs_map_ext2(jfs_inode->mode2 & JFS_FL_USER_VISIBLE,
+                                       0);
+               err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+               if (err) {
+                       inode_unlock(inode);
+                       goto setflags_out;
                }
 
                flags = flags & JFS_FL_USER_MODIFIABLE;
-               flags |= oldflags & ~JFS_FL_USER_MODIFIABLE;
+               flags |= jfs_inode->mode2 & ~JFS_FL_USER_MODIFIABLE;
                jfs_inode->mode2 = flags;
 
                jfs_set_inode_flags(inode);
index 9b96d79eea6c81247380142fb9db0ef1ad0c51cb..91b9dac6b2cc0d792696407c4aad29ceefc55cce 100644 (file)
@@ -148,13 +148,8 @@ static int nilfs_ioctl_setflags(struct inode *inode, struct file *filp,
 
        oldflags = NILFS_I(inode)->i_flags;
 
-       /*
-        * The IMMUTABLE and APPEND_ONLY flags can only be changed by the
-        * relevant capability.
-        */
-       ret = -EPERM;
-       if (((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL)) &&
-           !capable(CAP_LINUX_IMMUTABLE))
+       ret = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (ret)
                goto out;
 
        ret = nilfs_transaction_begin(inode->i_sb, &ti, 0);
index 994726ada857c701f5fb4cb16949fef86d14cd13..d6f7b299eb236d4b176e653babdb4f1261cb9584 100644 (file)
@@ -106,16 +106,9 @@ static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags,
        flags = flags & mask;
        flags |= oldflags & ~mask;
 
-       /*
-        * The IMMUTABLE and APPEND_ONLY flags can only be changed by
-        * the relevant capability.
-        */
-       status = -EPERM;
-       if ((oldflags & OCFS2_IMMUTABLE_FL) || ((flags ^ oldflags) &
-               (OCFS2_APPEND_FL | OCFS2_IMMUTABLE_FL))) {
-               if (!capable(CAP_LINUX_IMMUTABLE))
-                       goto bail_unlock;
-       }
+       status = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (status)
+               goto bail_unlock;
 
        handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
        if (IS_ERR(handle)) {
index a35c17017210f9ba78cdfde99a44c6cf8e8f044f..679a3c8e4fb39924dae0a1e2f37d4e1c971df810 100644 (file)
@@ -357,11 +357,28 @@ static ssize_t orangefs_file_write_iter(struct kiocb *iocb,
        return ret;
 }
 
+static int orangefs_getflags(struct inode *inode, unsigned long *uval)
+{
+       __u64 val = 0;
+       int ret;
+
+       ret = orangefs_inode_getxattr(inode,
+                                     "user.pvfs2.meta_hint",
+                                     &val, sizeof(val));
+       if (ret < 0 && ret != -ENODATA)
+               return ret;
+       else if (ret == -ENODATA)
+               val = 0;
+       *uval = val;
+       return 0;
+}
+
 /*
  * Perform a miscellaneous operation on a file.
  */
 static long orangefs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 {
+       struct inode *inode = file_inode(file);
        int ret = -ENOTTY;
        __u64 val = 0;
        unsigned long uval;
@@ -375,20 +392,16 @@ static long orangefs_ioctl(struct file *file, unsigned int cmd, unsigned long ar
         * and append flags
         */
        if (cmd == FS_IOC_GETFLAGS) {
-               val = 0;
-               ret = orangefs_inode_getxattr(file_inode(file),
-                                             "user.pvfs2.meta_hint",
-                                             &val, sizeof(val));
-               if (ret < 0 && ret != -ENODATA)
+               ret = orangefs_getflags(inode, &uval);
+               if (ret)
                        return ret;
-               else if (ret == -ENODATA)
-                       val = 0;
-               uval = val;
                gossip_debug(GOSSIP_FILE_DEBUG,
                             "orangefs_ioctl: FS_IOC_GETFLAGS: %llu\n",
                             (unsigned long long)uval);
                return put_user(uval, (int __user *)arg);
        } else if (cmd == FS_IOC_SETFLAGS) {
+               unsigned long old_uval;
+
                ret = 0;
                if (get_user(uval, (int __user *)arg))
                        return -EFAULT;
@@ -404,11 +417,17 @@ static long orangefs_ioctl(struct file *file, unsigned int cmd, unsigned long ar
                        gossip_err("orangefs_ioctl: the FS_IOC_SETFLAGS only supports setting one of FS_IMMUTABLE_FL|FS_APPEND_FL|FS_NOATIME_FL\n");
                        return -EINVAL;
                }
+               ret = orangefs_getflags(inode, &old_uval);
+               if (ret)
+                       return ret;
+               ret = vfs_ioc_setflags_prepare(inode, old_uval, uval);
+               if (ret)
+                       return ret;
                val = uval;
                gossip_debug(GOSSIP_FILE_DEBUG,
                             "orangefs_ioctl: FS_IOC_SETFLAGS: %llu\n",
                             (unsigned long long)val);
-               ret = orangefs_inode_setxattr(file_inode(file),
+               ret = orangefs_inode_setxattr(inode,
                                              "user.pvfs2.meta_hint",
                                              &val, sizeof(val), 0);
        }
index acbbaf7a0bb26be8e25039ca4fee2a64a0db02be..45e1a5d11af39a0b913934ecbefbf01c4f6336f3 100644 (file)
@@ -74,13 +74,11 @@ long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                                err = -EPERM;
                                goto setflags_out;
                        }
-                       if (((flags ^ REISERFS_I(inode)->
-                             i_attrs) & (REISERFS_IMMUTABLE_FL |
-                                         REISERFS_APPEND_FL))
-                           && !capable(CAP_LINUX_IMMUTABLE)) {
-                               err = -EPERM;
+                       err = vfs_ioc_setflags_prepare(inode,
+                                                    REISERFS_I(inode)->i_attrs,
+                                                    flags);
+                       if (err)
                                goto setflags_out;
-                       }
                        if ((flags & REISERFS_NOTAIL_FL) &&
                            S_ISREG(inode->i_mode)) {
                                int result;
index 4f1a397fda6904e1923b21f6436c74d01a93f1dc..034ad14710d14a395ab818389be4cbe5177cec9e 100644 (file)
@@ -107,18 +107,11 @@ static int setflags(struct inode *inode, int flags)
        if (err)
                return err;
 
-       /*
-        * The IMMUTABLE and APPEND_ONLY flags can only be changed by
-        * the relevant capability.
-        */
        mutex_lock(&ui->ui_mutex);
        oldflags = ubifs2ioctl(ui->flags);
-       if ((flags ^ oldflags) & (FS_APPEND_FL | FS_IMMUTABLE_FL)) {
-               if (!capable(CAP_LINUX_IMMUTABLE)) {
-                       err = -EPERM;
-                       goto out_unlock;
-               }
-       }
+       err = vfs_ioc_setflags_prepare(inode, oldflags, flags);
+       if (err)
+               goto out_unlock;
 
        ui->flags = ioctl2ubifs(flags);
        ubifs_set_inode_flags(inode);
index f7fdfe93e25d3e94cc4bc7b053ead12667607cc9..41d5175ffdd7f2156ad1b5e4978df891ee833f32 100644 (file)
@@ -3546,4 +3546,7 @@ static inline struct sock *io_uring_get_socket(struct file *file)
 }
 #endif
 
+int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
+                            unsigned int flags);
+
 #endif /* _LINUX_FS_H */