Hi all,
The chattr(1) manpage has this to say about the immutable bit that
system administrators can set on 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."
Given the clause about how the file 'cannot be modified', it is
surprising that programs holding writable file descriptors can continue
to write to and truncate files after the immutable flag has been set,
but they cannot call other things such as utimes, fallocate, unlink,
link, setxattr, or reflink.
Since the immutable flag is only settable by administrators, resolve
this inconsistent behavior in favor of the documented behavior -- once
the flag is set, the file cannot be modified, period.
If you're going to start using this mess, you probably ought to just
pull from my git trees, which are linked below.
This has been lightly tested with fstests. Enjoy!
Comments and questions are, as always, welcome.
--D
kernel git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfs-linux.git/log/?h=immutable-files
From: Darrick J. Wong <[email protected]>
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."
Once the flag is set, it is enforced for quite a few file operations,
such as fallocate, fpunch, fzero, rm, touch, open, etc. However, we
don't check for immutability when doing a write(), a PROT_WRITE mmap(),
a truncate(), or a write to a previously established mmap.
If a program has an open write fd to a file that the administrator
subsequently marks immutable, the program still can change the file
contents. Weird!
The ability to write to an immutable file does not follow the manpage
promise that immutable files cannot be modified. Worse yet it's
inconsistent with the behavior of other syscalls which don't allow
modifications of immutable files.
Therefore, add the necessary checks to make the write, mmap, and
truncate behavior consistent with what the manpage says and consistent
with other syscalls on filesystems which support IMMUTABLE.
Signed-off-by: Darrick J. Wong <[email protected]>
---
fs/attr.c | 13 ++++++-------
mm/filemap.c | 3 +++
mm/memory.c | 3 +++
mm/mmap.c | 3 +++
4 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/fs/attr.c b/fs/attr.c
index d22e8187477f..1fcfdcc5b367 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -233,19 +233,18 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
WARN_ON_ONCE(!inode_is_locked(inode));
- if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_TIMES_SET)) {
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
- return -EPERM;
- }
+ if (IS_IMMUTABLE(inode))
+ return -EPERM;
+
+ if ((ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | ATTR_TIMES_SET)) &&
+ IS_APPEND(inode))
+ return -EPERM;
/*
* If utimes(2) and friends are called with times == NULL (or both
* times are UTIME_NOW), then we need to check for write permission
*/
if (ia_valid & ATTR_TOUCH) {
- if (IS_IMMUTABLE(inode))
- return -EPERM;
-
if (!inode_owner_or_capable(inode)) {
error = inode_permission(inode, MAY_WRITE);
if (error)
diff --git a/mm/filemap.c b/mm/filemap.c
index d78f577baef2..9fed698f4c63 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3033,6 +3033,9 @@ inline ssize_t generic_write_checks(struct kiocb *iocb, struct iov_iter *from)
loff_t count;
int ret;
+ if (IS_IMMUTABLE(inode))
+ return -EPERM;
+
if (!iov_iter_count(from))
return 0;
diff --git a/mm/memory.c b/mm/memory.c
index 47fe250307c7..c493db22413a 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2148,6 +2148,9 @@ static vm_fault_t do_page_mkwrite(struct vm_fault *vmf)
vmf->flags = FAULT_FLAG_WRITE|FAULT_FLAG_MKWRITE;
+ if (vmf->vma->vm_file && IS_IMMUTABLE(file_inode(vmf->vma->vm_file)))
+ return VM_FAULT_SIGBUS;
+
ret = vmf->vma->vm_ops->page_mkwrite(vmf);
/* Restore original flags so that caller is not surprised */
vmf->flags = old_flags;
diff --git a/mm/mmap.c b/mm/mmap.c
index 41eb48d9b527..e49dcbeda461 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -1394,6 +1394,9 @@ unsigned long do_mmap(struct file *file, unsigned long addr,
if (!len)
return -EINVAL;
+ if (file && IS_IMMUTABLE(file_inode(file)))
+ return -EPERM;
+
/*
* Does the application expect PROT_READ to imply PROT_EXEC?
*
From: Darrick J. Wong <[email protected]>
We passed an inode into xfs_ioctl_setattr_get_trans with join_flags
indicating which locks are held on that inode. If we can't allocate a
transaction then we need to unlock the inode before we bail out.
Signed-off-by: Darrick J. Wong <[email protected]>
---
fs/xfs/xfs_ioctl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 6ecdbb3af7de..91938c4f3c67 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -1142,7 +1142,7 @@ xfs_ioctl_setattr_get_trans(
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
if (error)
- return ERR_PTR(error);
+ goto out_unlock;
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL | join_flags);
From: Darrick J. Wong <[email protected]>
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."
This means that we need to flush the page cache when setting the
immutable flag so that all mappings will become read-only again and
therefore programs cannot continue to write to writable mappings.
Signed-off-by: Darrick J. Wong <[email protected]>
---
fs/xfs/xfs_ioctl.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 7 deletions(-)
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 91938c4f3c67..5a1b96dad901 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -998,6 +998,31 @@ xfs_diflags_to_linux(
#endif
}
+/*
+ * Lock the inode against file io and page faults, then flush all dirty pages
+ * and wait for writeback and direct IO operations to finish. Returns with
+ * the relevant inode lock flags set in @join_flags. Caller is responsible for
+ * unlocking even on error return.
+ */
+static int
+xfs_ioctl_setattr_flush(
+ struct xfs_inode *ip,
+ int *join_flags)
+{
+ struct inode *inode = VFS_I(ip);
+
+ /* Already locked the inode from IO? Assume we're done. */
+ if (((*join_flags) & (XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL)) ==
+ (XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL))
+ return 0;
+
+ /* Lock and flush all mappings and IO in preparation for flag change */
+ *join_flags = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
+ xfs_ilock(ip, *join_flags);
+ inode_dio_wait(inode);
+ return filemap_write_and_wait(inode->i_mapping);
+}
+
static int
xfs_ioctl_setattr_xflags(
struct xfs_trans *tp,
@@ -1092,25 +1117,22 @@ xfs_ioctl_setattr_dax_invalidate(
if (!(fa->fsx_xflags & FS_XFLAG_DAX) && !IS_DAX(inode))
return 0;
- if (S_ISDIR(inode->i_mode))
+ if (!S_ISREG(inode->i_mode))
return 0;
/* lock, flush and invalidate mapping in preparation for flag change */
- xfs_ilock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
- error = filemap_write_and_wait(inode->i_mapping);
+ error = xfs_ioctl_setattr_flush(ip, join_flags);
if (error)
goto out_unlock;
error = invalidate_inode_pages2(inode->i_mapping);
if (error)
goto out_unlock;
-
- *join_flags = XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL;
return 0;
out_unlock:
- xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
+ xfs_iunlock(ip, *join_flags);
+ *join_flags = 0;
return error;
-
}
/*
@@ -1356,6 +1378,21 @@ xfs_ioctl_setattr(
if (code)
goto error_free_dquots;
+ /*
+ * If we are trying to set immutable on a file then flush everything to
+ * disk to force all writable memory mappings back through the
+ * pagefault handler.
+ */
+ if (S_ISREG(VFS_I(ip)->i_mode) && !IS_IMMUTABLE(VFS_I(ip)) &&
+ (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)) {
+ code = xfs_ioctl_setattr_flush(ip, &join_flags);
+ if (code) {
+ xfs_iunlock(ip, join_flags);
+ join_flags = 0;
+ goto error_free_dquots;
+ }
+ }
+
tp = xfs_ioctl_setattr_get_trans(ip, join_flags);
if (IS_ERR(tp)) {
code = PTR_ERR(tp);
From: Darrick J. Wong <[email protected]>
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 project ids and extent size hints on
supposedly immutable files. Therefore, reject a setattr call on an
immutable file except for the case where we're trying to unset
IMMUTABLE.
Signed-off-by: Darrick J. Wong <[email protected]>
---
fs/xfs/xfs_ioctl.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 5a1b96dad901..1215713d7814 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -1061,6 +1061,14 @@ xfs_ioctl_setattr_xflags(
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
+ /*
+ * If immutable is set and we are not clearing it, we're not allowed
+ * to change anything else in the inode.
+ */
+ if ((ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) &&
+ (fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
+ return -EPERM;
+
/* diflags2 only valid for v3 inodes. */
di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
if (di_flags2 && ip->i_d.di_version < 3)
Looks ok:
Reviewed-by: Allison Henderson <[email protected]>
On 4/7/19 1:27 PM, Darrick J. Wong wrote:
> From: Darrick J. Wong <[email protected]>
>
> We passed an inode into xfs_ioctl_setattr_get_trans with join_flags
> indicating which locks are held on that inode. If we can't allocate a
> transaction then we need to unlock the inode before we bail out.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
> fs/xfs/xfs_ioctl.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
>
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index 6ecdbb3af7de..91938c4f3c67 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
> @@ -1142,7 +1142,7 @@ xfs_ioctl_setattr_get_trans(
>
> error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
> if (error)
> - return ERR_PTR(error);
> + goto out_unlock;
>
> xfs_ilock(ip, XFS_ILOCK_EXCL);
> xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL | join_flags);
>
On 4/7/19 1:27 PM, Darrick J. Wong wrote:
> From: Darrick J. Wong <[email protected]>
>
> 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."
>
> This means that we need to flush the page cache when setting the
> immutable flag so that all mappings will become read-only again and
> therefore programs cannot continue to write to writable mappings.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
> fs/xfs/xfs_ioctl.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 44 insertions(+), 7 deletions(-)
>
>
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index 91938c4f3c67..5a1b96dad901 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
> @@ -998,6 +998,31 @@ xfs_diflags_to_linux(
> #endif
> }
>
> +/*
> + * Lock the inode against file io and page faults, then flush all dirty pages
> + * and wait for writeback and direct IO operations to finish. Returns with
> + * the relevant inode lock flags set in @join_flags. Caller is responsible for
> + * unlocking even on error return.
> + */
> +static int
> +xfs_ioctl_setattr_flush(
> + struct xfs_inode *ip,
> + int *join_flags)
> +{
> + struct inode *inode = VFS_I(ip);
> +
> + /* Already locked the inode from IO? Assume we're done. */
> + if (((*join_flags) & (XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL)) ==
> + (XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL))
> + return 0;
> +
> + /* Lock and flush all mappings and IO in preparation for flag change */
> + *join_flags = XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL;
Did you mean |= here? It looks like this code came from
xfs_ioctl_setattr_dax_invalidate, but now calling from
xfs_ioctl_setattr, we may be over writing flags where we previously had
not, so that may not be expected.
Allison
> + xfs_ilock(ip, *join_flags);
> + inode_dio_wait(inode);
> + return filemap_write_and_wait(inode->i_mapping);
> +}
> +
> static int
> xfs_ioctl_setattr_xflags(
> struct xfs_trans *tp,
> @@ -1092,25 +1117,22 @@ xfs_ioctl_setattr_dax_invalidate(
> if (!(fa->fsx_xflags & FS_XFLAG_DAX) && !IS_DAX(inode))
> return 0;
>
> - if (S_ISDIR(inode->i_mode))
> + if (!S_ISREG(inode->i_mode))
> return 0;
>
> /* lock, flush and invalidate mapping in preparation for flag change */
> - xfs_ilock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
> - error = filemap_write_and_wait(inode->i_mapping);
> + error = xfs_ioctl_setattr_flush(ip, join_flags);
> if (error)
> goto out_unlock;
> error = invalidate_inode_pages2(inode->i_mapping);
> if (error)
> goto out_unlock;
> -
> - *join_flags = XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL;
> return 0;
>
> out_unlock:
> - xfs_iunlock(ip, XFS_MMAPLOCK_EXCL | XFS_IOLOCK_EXCL);
> + xfs_iunlock(ip, *join_flags);
> + *join_flags = 0;
> return error;
> -
> }
>
> /*
> @@ -1356,6 +1378,21 @@ xfs_ioctl_setattr(
> if (code)
> goto error_free_dquots;
>
> + /*
> + * If we are trying to set immutable on a file then flush everything to
> + * disk to force all writable memory mappings back through the
> + * pagefault handler.
> + */
> + if (S_ISREG(VFS_I(ip)->i_mode) && !IS_IMMUTABLE(VFS_I(ip)) &&
> + (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)) {
> + code = xfs_ioctl_setattr_flush(ip, &join_flags);
> + if (code) {
> + xfs_iunlock(ip, join_flags);
> + join_flags = 0;
> + goto error_free_dquots;
> + }
> + }
> +
> tp = xfs_ioctl_setattr_get_trans(ip, join_flags);
> if (IS_ERR(tp)) {
> code = PTR_ERR(tp);
>
Looks ok:
Reviewed-by: Allison Henderson <[email protected]>
On 4/7/19 1:27 PM, Darrick J. Wong wrote:
> From: Darrick J. Wong <[email protected]>
>
> 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 project ids and extent size hints on
> supposedly immutable files. Therefore, reject a setattr call on an
> immutable file except for the case where we're trying to unset
> IMMUTABLE.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
> fs/xfs/xfs_ioctl.c | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
>
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index 5a1b96dad901..1215713d7814 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
> @@ -1061,6 +1061,14 @@ xfs_ioctl_setattr_xflags(
> !capable(CAP_LINUX_IMMUTABLE))
> return -EPERM;
>
> + /*
> + * If immutable is set and we are not clearing it, we're not allowed
> + * to change anything else in the inode.
> + */
> + if ((ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) &&
> + (fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
> + return -EPERM;
> +
> /* diflags2 only valid for v3 inodes. */
> di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
> if (di_flags2 && ip->i_d.di_version < 3)
>
On Sun, Apr 7, 2019 at 11:28 PM Darrick J. Wong <[email protected]> wrote:
>
> From: Darrick J. Wong <[email protected]>
>
> 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 project ids and extent size hints on
> supposedly immutable files. Therefore, reject a setattr call on an
> immutable file except for the case where we're trying to unset
> IMMUTABLE.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
Did you miss my comment on v1, or do you not think this use case
is going to hurt any application that is not a rootkit?
chattr +i foo => OK
chattr +i foo => -EPERM
Thanks,
Amir.
On Mon, Apr 08, 2019 at 09:20:47AM +0300, Amir Goldstein wrote:
> On Sun, Apr 7, 2019 at 11:28 PM Darrick J. Wong <[email protected]> wrote:
> >
> > From: Darrick J. Wong <[email protected]>
> >
> > 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 project ids and extent size hints on
> > supposedly immutable files. Therefore, reject a setattr call on an
> > immutable file except for the case where we're trying to unset
> > IMMUTABLE.
> >
> > Signed-off-by: Darrick J. Wong <[email protected]>
>
> Did you miss my comment on v1, or do you not think this use case
> is going to hurt any application that is not a rootkit?
>
> chattr +i foo => OK
> chattr +i foo => -EPERM
Nah, I plain forgot to update the patch. :(
Will send v2 where you're allowed to +i multiple times so long as that's
the only thing you're changing.
--D
> Thanks,
> Amir.
From: Darrick J. Wong <[email protected]>
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 project ids and extent size hints on
supposedly immutable files. Therefore, reject a setattr call on an
immutable file except for the case where we're trying to unset
IMMUTABLE.
Signed-off-by: Darrick J. Wong <[email protected]>
---
fs/xfs/xfs_ioctl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index 5a1b96dad901..67d12027f563 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -1023,6 +1023,40 @@ xfs_ioctl_setattr_flush(
return filemap_write_and_wait(inode->i_mapping);
}
+/*
+ * 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
+xfs_ioctl_setattr_immutable(
+ struct xfs_inode *ip,
+ struct fsxattr *fa,
+ uint16_t di_flags,
+ uint64_t di_flags2)
+{
+ struct xfs_mount *mp = ip->i_mount;
+
+ if (!(ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) ||
+ !(fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
+ return 0;
+
+ if ((ip->i_d.di_flags & ~XFS_DIFLAG_IMMUTABLE) !=
+ (di_flags & ~XFS_DIFLAG_IMMUTABLE))
+ return -EPERM;
+ if (ip->i_d.di_version >= 3 && ip->i_d.di_flags2 != di_flags2)
+ return -EPERM;
+ if (xfs_get_projid(ip) != fa->fsx_projid)
+ return -EPERM;
+ if (ip->i_d.di_extsize != fa->fsx_extsize >> mp->m_sb.sb_blocklog)
+ return -EPERM;
+ if (ip->i_d.di_version >= 3 && (di_flags2 & XFS_DIFLAG2_COWEXTSIZE) &&
+ ip->i_d.di_cowextsize != fa->fsx_cowextsize >> mp->m_sb.sb_blocklog)
+ return -EPERM;
+
+ return 0;
+}
+
static int
xfs_ioctl_setattr_xflags(
struct xfs_trans *tp,
@@ -1030,7 +1064,9 @@ xfs_ioctl_setattr_xflags(
struct fsxattr *fa)
{
struct xfs_mount *mp = ip->i_mount;
+ uint16_t di_flags;
uint64_t di_flags2;
+ int error;
/* Can't change realtime flag if any extents are allocated. */
if ((ip->i_d.di_nextents || ip->i_delayed_blks) &&
@@ -1061,12 +1097,18 @@ xfs_ioctl_setattr_xflags(
!capable(CAP_LINUX_IMMUTABLE))
return -EPERM;
- /* diflags2 only valid for v3 inodes. */
+ /* Don't allow changes to an immutable inode. */
+ di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
+ error = xfs_ioctl_setattr_immutable(ip, fa, di_flags, di_flags2);
+ if (error)
+ return error;
+
+ /* diflags2 only valid for v3 inodes. */
if (di_flags2 && ip->i_d.di_version < 3)
return -EINVAL;
- ip->i_d.di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
+ ip->i_d.di_flags = di_flags;
ip->i_d.di_flags2 = di_flags2;
xfs_diflags_to_linux(ip);
On Tue, Apr 9, 2019 at 6:19 AM Darrick J. Wong <[email protected]> wrote:
>
> From: Darrick J. Wong <[email protected]>
>
> 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 project ids and extent size hints on
> supposedly immutable files. Therefore, reject a setattr call on an
> immutable file except for the case where we're trying to unset
> IMMUTABLE.
>
> Signed-off-by: Darrick J. Wong <[email protected]>
> ---
> fs/xfs/xfs_ioctl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 44 insertions(+), 2 deletions(-)
>
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index 5a1b96dad901..67d12027f563 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
> @@ -1023,6 +1023,40 @@ xfs_ioctl_setattr_flush(
> return filemap_write_and_wait(inode->i_mapping);
> }
>
> +/*
> + * If immutable is set and we are not clearing it, we're not allowed to change
> + * anything else in the inode.
This looks correct, but FYI, neither xfs_io nor chattr clears 'immutable'
and sets projid/*extsize in one ioctl/xfsctl, so there is no justification to
making an extra effort to support that use case. You could do with
checking 'immutable' inside xfs_ioctl_setattr_check_projid/*extsize()
and leave only the di_flags check here.
Some would say that will be cleaner code.
Its a matter of taste and its your subsystem, so feel free to dismiss
this comments.
Thanks,
Amir.
> Don't error out if we're only trying to set
> + * immutable on an immutable file.
> + */
> +static int
> +xfs_ioctl_setattr_immutable(
> + struct xfs_inode *ip,
> + struct fsxattr *fa,
> + uint16_t di_flags,
> + uint64_t di_flags2)
> +{
> + struct xfs_mount *mp = ip->i_mount;
> +
> + if (!(ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) ||
> + !(fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
> + return 0;
> +
> + if ((ip->i_d.di_flags & ~XFS_DIFLAG_IMMUTABLE) !=
> + (di_flags & ~XFS_DIFLAG_IMMUTABLE))
> + return -EPERM;
> + if (ip->i_d.di_version >= 3 && ip->i_d.di_flags2 != di_flags2)
> + return -EPERM;
> + if (xfs_get_projid(ip) != fa->fsx_projid)
> + return -EPERM;
> + if (ip->i_d.di_extsize != fa->fsx_extsize >> mp->m_sb.sb_blocklog)
> + return -EPERM;
> + if (ip->i_d.di_version >= 3 && (di_flags2 & XFS_DIFLAG2_COWEXTSIZE) &&
> + ip->i_d.di_cowextsize != fa->fsx_cowextsize >> mp->m_sb.sb_blocklog)
> + return -EPERM;
> +
> + return 0;
> +}
> +
> static int
> xfs_ioctl_setattr_xflags(
> struct xfs_trans *tp,
> @@ -1030,7 +1064,9 @@ xfs_ioctl_setattr_xflags(
> struct fsxattr *fa)
> {
> struct xfs_mount *mp = ip->i_mount;
> + uint16_t di_flags;
> uint64_t di_flags2;
> + int error;
>
> /* Can't change realtime flag if any extents are allocated. */
> if ((ip->i_d.di_nextents || ip->i_delayed_blks) &&
> @@ -1061,12 +1097,18 @@ xfs_ioctl_setattr_xflags(
> !capable(CAP_LINUX_IMMUTABLE))
> return -EPERM;
>
> - /* diflags2 only valid for v3 inodes. */
> + /* Don't allow changes to an immutable inode. */
> + di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
> di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
> + error = xfs_ioctl_setattr_immutable(ip, fa, di_flags, di_flags2);
> + if (error)
> + return error;
> +
> + /* diflags2 only valid for v3 inodes. */
> if (di_flags2 && ip->i_d.di_version < 3)
> return -EINVAL;
>
> - ip->i_d.di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
> + ip->i_d.di_flags = di_flags;
> ip->i_d.di_flags2 = di_flags2;
>
> xfs_diflags_to_linux(ip);
On Tue, Apr 09, 2019 at 11:24:10AM +0300, Amir Goldstein wrote:
> On Tue, Apr 9, 2019 at 6:19 AM Darrick J. Wong <[email protected]> wrote:
> >
> > From: Darrick J. Wong <[email protected]>
> >
> > 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 project ids and extent size hints on
> > supposedly immutable files. Therefore, reject a setattr call on an
> > immutable file except for the case where we're trying to unset
> > IMMUTABLE.
> >
> > Signed-off-by: Darrick J. Wong <[email protected]>
> > ---
> > fs/xfs/xfs_ioctl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++--
> > 1 file changed, 44 insertions(+), 2 deletions(-)
> >
> > diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> > index 5a1b96dad901..67d12027f563 100644
> > --- a/fs/xfs/xfs_ioctl.c
> > +++ b/fs/xfs/xfs_ioctl.c
> > @@ -1023,6 +1023,40 @@ xfs_ioctl_setattr_flush(
> > return filemap_write_and_wait(inode->i_mapping);
> > }
> >
> > +/*
> > + * If immutable is set and we are not clearing it, we're not allowed to change
> > + * anything else in the inode.
>
> This looks correct, but FYI, neither xfs_io nor chattr clears 'immutable'
> and sets projid/*extsize in one ioctl/xfsctl, so there is no justification to
> making an extra effort to support that use case. You could do with
> checking 'immutable' inside xfs_ioctl_setattr_check_projid/*extsize()
> and leave only the di_flags check here.
However, the API does allow callers to clear immutable and set other
fields in one go, so just because xfs_io won't do it doesn't mean we can
ignore it.
Then again I guess the manpage doesn't explicitly say what the behavior
is supposed to be, so I guess we'll just ... argh, fine I'll go fix the
manpage to document the behavior.
--D
> Some would say that will be cleaner code.
> Its a matter of taste and its your subsystem, so feel free to dismiss
> this comments.
>
> Thanks,
> Amir.
>
> > Don't error out if we're only trying to set
> > + * immutable on an immutable file.
> > + */
> > +static int
> > +xfs_ioctl_setattr_immutable(
> > + struct xfs_inode *ip,
> > + struct fsxattr *fa,
> > + uint16_t di_flags,
> > + uint64_t di_flags2)
> > +{
> > + struct xfs_mount *mp = ip->i_mount;
> > +
> > + if (!(ip->i_d.di_flags & XFS_DIFLAG_IMMUTABLE) ||
> > + !(fa->fsx_xflags & FS_XFLAG_IMMUTABLE))
> > + return 0;
> > +
> > + if ((ip->i_d.di_flags & ~XFS_DIFLAG_IMMUTABLE) !=
> > + (di_flags & ~XFS_DIFLAG_IMMUTABLE))
> > + return -EPERM;
> > + if (ip->i_d.di_version >= 3 && ip->i_d.di_flags2 != di_flags2)
> > + return -EPERM;
> > + if (xfs_get_projid(ip) != fa->fsx_projid)
> > + return -EPERM;
> > + if (ip->i_d.di_extsize != fa->fsx_extsize >> mp->m_sb.sb_blocklog)
> > + return -EPERM;
> > + if (ip->i_d.di_version >= 3 && (di_flags2 & XFS_DIFLAG2_COWEXTSIZE) &&
> > + ip->i_d.di_cowextsize != fa->fsx_cowextsize >> mp->m_sb.sb_blocklog)
> > + return -EPERM;
> > +
> > + return 0;
> > +}
> > +
> > static int
> > xfs_ioctl_setattr_xflags(
> > struct xfs_trans *tp,
> > @@ -1030,7 +1064,9 @@ xfs_ioctl_setattr_xflags(
> > struct fsxattr *fa)
> > {
> > struct xfs_mount *mp = ip->i_mount;
> > + uint16_t di_flags;
> > uint64_t di_flags2;
> > + int error;
> >
> > /* Can't change realtime flag if any extents are allocated. */
> > if ((ip->i_d.di_nextents || ip->i_delayed_blks) &&
> > @@ -1061,12 +1097,18 @@ xfs_ioctl_setattr_xflags(
> > !capable(CAP_LINUX_IMMUTABLE))
> > return -EPERM;
> >
> > - /* diflags2 only valid for v3 inodes. */
> > + /* Don't allow changes to an immutable inode. */
> > + di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
> > di_flags2 = xfs_flags2diflags2(ip, fa->fsx_xflags);
> > + error = xfs_ioctl_setattr_immutable(ip, fa, di_flags, di_flags2);
> > + if (error)
> > + return error;
> > +
> > + /* diflags2 only valid for v3 inodes. */
> > if (di_flags2 && ip->i_d.di_version < 3)
> > return -EINVAL;
> >
> > - ip->i_d.di_flags = xfs_flags2diflags(ip, fa->fsx_xflags);
> > + ip->i_d.di_flags = di_flags;
> > ip->i_d.di_flags2 = di_flags2;
> >
> > xfs_diflags_to_linux(ip);