ext4: don't allow any modifications to an immutable file
authorDarrick J. Wong <darrick.wong@oracle.com>
Mon, 10 Jun 2019 01:41:41 +0000 (21:41 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Mon, 10 Jun 2019 01:41:41 +0000 (21:41 -0400)
Don't allow any modifications to a file that's marked immutable, which
means that we have to flush all the writable pages to make the readonly
and we have to check the setattr/setflags parameters more closely.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org
fs/ext4/ioctl.c

index e486e49..7af835a 100644 (file)
@@ -269,6 +269,29 @@ static int uuid_is_zero(__u8 u[16])
 }
 #endif
 
+/*
+ * If immutable is set and we are not clearing it, we're not allowed to change
+ * anything else in the inode.  Don't error out if we're only trying to set
+ * immutable on an immutable file.
+ */
+static int ext4_ioctl_check_immutable(struct inode *inode, __u32 new_projid,
+                                     unsigned int flags)
+{
+       struct ext4_inode_info *ei = EXT4_I(inode);
+       unsigned int oldflags = ei->i_flags;
+
+       if (!(oldflags & EXT4_IMMUTABLE_FL) || !(flags & EXT4_IMMUTABLE_FL))
+               return 0;
+
+       if ((oldflags & ~EXT4_IMMUTABLE_FL) != (flags & ~EXT4_IMMUTABLE_FL))
+               return -EPERM;
+       if (ext4_has_feature_project(inode->i_sb) &&
+           __kprojid_val(ei->i_projid) != new_projid)
+               return -EPERM;
+
+       return 0;
+}
+
 static int ext4_ioctl_setflags(struct inode *inode,
                               unsigned int flags)
 {
@@ -340,6 +363,20 @@ static int ext4_ioctl_setflags(struct inode *inode,
                }
        }
 
+       /*
+        * Wait for all pending directio and then flush all the dirty pages
+        * for this file.  The flush marks all the pages readonly, so any
+        * subsequent attempt to write to the file (particularly mmap pages)
+        * will come through the filesystem and fail.
+        */
+       if (S_ISREG(inode->i_mode) && !IS_IMMUTABLE(inode) &&
+           (flags & EXT4_IMMUTABLE_FL)) {
+               inode_dio_wait(inode);
+               err = filemap_write_and_wait(inode->i_mapping);
+               if (err)
+                       goto flags_out;
+       }
+
        handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
        if (IS_ERR(handle)) {
                err = PTR_ERR(handle);
@@ -769,7 +806,11 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                        return err;
 
                inode_lock(inode);
-               err = ext4_ioctl_setflags(inode, flags);
+               err = ext4_ioctl_check_immutable(inode,
+                               from_kprojid(&init_user_ns, ei->i_projid),
+                               flags);
+               if (!err)
+                       err = ext4_ioctl_setflags(inode, flags);
                inode_unlock(inode);
                mnt_drop_write_file(filp);
                return err;
@@ -1139,6 +1180,9 @@ resizefs_out:
                        goto out;
                flags = (ei->i_flags & ~EXT4_FL_XFLAG_VISIBLE) |
                         (flags & EXT4_FL_XFLAG_VISIBLE);
+               err = ext4_ioctl_check_immutable(inode, fa.fsx_projid, flags);
+               if (err)
+                       goto out;
                err = ext4_ioctl_setflags(inode, flags);
                if (err)
                        goto out;