summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2019-05-21 17:10:02 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2019-06-28 10:57:53 -0700
commit8be6f7aad163030f85ccaade1398c9e221f92b75 (patch)
tree73667daaa4ac71c0364538144ffcee03a5445056
parenta9e9dc22d216c829c3dc20539ad3bf6b58dad316 (diff)
vfs: don't allow most setxattr to immutable filesimmutable-files_2019-06-28
The chattr manpage has this to say about immutable files: "A file with the 'i' attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode." However, we don't actually check the immutable flag in the setattr code, which means that we can update inode flags and project ids and extent size hints on supposedly immutable files. Therefore, reject setflags and fssetxattr calls on an immutable file if the file is immutable and will remain that way. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r--fs/inode.c27
1 files changed, 27 insertions, 0 deletions
diff --git a/fs/inode.c b/fs/inode.c
index cf07378e5731..4261c709e50e 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2215,6 +2215,14 @@ int vfs_ioc_setflags_prepare(struct inode *inode, unsigned int oldflags,
return -EPERM;
/*
+ * We aren't allowed to change any other flags if the immutable flag is
+ * already set and is not being unset.
+ */
+ if ((oldflags & FS_IMMUTABLE_FL) && (flags & FS_IMMUTABLE_FL) &&
+ oldflags != flags)
+ return -EPERM;
+
+ /*
* Now that we're done checking the new flags, flush all pending IO and
* dirty mappings before setting S_IMMUTABLE on an inode via
* FS_IOC_SETFLAGS. If the flush fails we'll clear the flag before
@@ -2284,6 +2292,25 @@ int vfs_ioc_fssetxattr_check(struct inode *inode, const struct fsxattr *old_fa,
!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)))
return -EINVAL;
+ /*
+ * We aren't allowed to change any fields if the immutable flag is
+ * already set and is not being unset.
+ */
+ if ((old_fa->fsx_xflags & FS_XFLAG_IMMUTABLE) &&
+ (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)) {
+ if (old_fa->fsx_xflags != fa->fsx_xflags)
+ return -EPERM;
+ if (old_fa->fsx_projid != fa->fsx_projid)
+ return -EPERM;
+ if ((fa->fsx_xflags & (FS_XFLAG_EXTSIZE |
+ FS_XFLAG_EXTSZINHERIT)) &&
+ old_fa->fsx_extsize != fa->fsx_extsize)
+ return -EPERM;
+ if ((old_fa->fsx_xflags & FS_XFLAG_COWEXTSIZE) &&
+ old_fa->fsx_cowextsize != fa->fsx_cowextsize)
+ return -EPERM;
+ }
+
/* Extent size hints of zero turn off the flags. */
if (fa->fsx_extsize == 0)
fa->fsx_xflags &= ~(FS_XFLAG_EXTSIZE | FS_XFLAG_EXTSZINHERIT);