Whamcloud - gitweb
fuse2fs: check the immutable flag in more places
authorDarrick J. Wong <djwong@kernel.org>
Wed, 21 May 2025 22:39:52 +0000 (15:39 -0700)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 23 May 2025 13:41:20 +0000 (09:41 -0400)
We need to check the immutable flag in a few more places that try to
modify files.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Link: https://lore.kernel.org/r/174786677887.1383760.16795876049190712906.stgit@frogsfrogsfrogs
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
misc/fuse2fs.c

index 17c4d16..45fde73 100644 (file)
@@ -529,6 +529,30 @@ static inline int want_check_owner(struct fuse2fs *ff,
        return !is_superuser(ff, ctxt);
 }
 
+static int check_iflags_access(struct fuse2fs *ff, ext2_ino_t ino,
+                              const struct ext2_inode *inode, int mask)
+{
+       ext2_filsys fs = ff->fs;
+
+       /* no writing to read-only or broken fs */
+       if ((mask & W_OK) && !fs_writeable(fs))
+               return -EROFS;
+
+       dbg_printf(ff, "access ino=%d mask=e%s%s%s iflags=0x%x\n",
+                  ino,
+                  (mask & R_OK ? "r" : ""),
+                  (mask & W_OK ? "w" : ""),
+                  (mask & X_OK ? "x" : ""),
+                  inode->i_flags);
+
+       /* is immutable? */
+       if ((mask & W_OK) &&
+           (inode->i_flags & EXT2_IMMUTABLE_FL))
+               return -EPERM;
+
+       return 0;
+}
+
 static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
 {
        struct fuse_context *ctxt = fuse_get_context();
@@ -536,6 +560,7 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
        struct ext2_inode inode;
        mode_t perms;
        errcode_t err;
+       int ret;
 
        /* no writing to read-only or broken fs */
        if ((mask & W_OK) && !fs_writeable(fs))
@@ -559,10 +584,9 @@ static int check_inum_access(struct fuse2fs *ff, ext2_ino_t ino, int mask)
        if (mask == 0)
                return 0;
 
-       /* is immutable? */
-       if ((mask & W_OK) &&
-           (inode.i_flags & EXT2_IMMUTABLE_FL))
-               return -EPERM;
+       ret = check_iflags_access(ff, ino, &inode, mask);
+       if (ret)
+               return ret;
 
        /* If kernel is responsible for mode and acl checks, we're done. */
        if (ff->kernel)
@@ -1249,6 +1273,10 @@ static int __op_unlink(struct fuse2fs *ff, const char *path)
                goto out;
        }
 
+       ret = check_inum_access(ff, ino, W_OK);
+       if (ret)
+               goto out;
+
        ret = unlink_file_by_name(ff, path);
        if (ret)
                goto out;
@@ -1317,6 +1345,10 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
        }
        dbg_printf(ff, "%s: rmdir path=%s ino=%d\n", __func__, path, child);
 
+       ret = check_inum_access(ff, child, W_OK);
+       if (ret)
+               goto out;
+
        rds.parent = 0;
        rds.empty = 1;
 
@@ -1326,6 +1358,16 @@ static int __op_rmdir(struct fuse2fs *ff, const char *path)
                goto out;
        }
 
+       /* the kernel checks parent permissions before emptiness */
+       if (rds.parent == 0) {
+               ret = translate_error(fs, child, EXT2_ET_FILESYSTEM_CORRUPTED);
+               goto out;
+       }
+
+       ret = check_inum_access(ff, rds.parent, W_OK);
+       if (ret)
+               goto out;
+
        if (rds.empty == 0) {
                ret = -ENOTEMPTY;
                goto out;
@@ -1561,6 +1603,16 @@ static int op_rename(const char *from, const char *to
                goto out;
        }
 
+       ret = check_inum_access(ff, from_ino, W_OK);
+       if (ret)
+               goto out;
+
+       if (to_ino) {
+               ret = check_inum_access(ff, to_ino, W_OK);
+               if (ret)
+                       goto out;
+       }
+
        temp_to = strdup(to);
        if (!temp_to) {
                ret = -ENOMEM;
@@ -1790,7 +1842,6 @@ static int op_link(const char *src, const char *dest)
        if (ret)
                goto out2;
 
-
        err = ext2fs_namei(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, src, &ino);
        if (err || ino == 0) {
                ret = translate_error(fs, 0, err);
@@ -1805,6 +1856,10 @@ static int op_link(const char *src, const char *dest)
                goto out2;
        }
 
+       ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+       if (ret)
+               goto out2;
+
        inode.i_links_count++;
        ret = update_ctime(fs, ino, &inode);
        if (ret)
@@ -1879,6 +1934,10 @@ static int op_chmod(const char *path, mode_t mode
                goto out;
        }
 
+       ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+       if (ret)
+               goto out;
+
        if (want_check_owner(ff, ctxt) && ctxt->uid != inode_uid(inode)) {
                ret = -EPERM;
                goto out;
@@ -1943,6 +2002,10 @@ static int op_chown(const char *path, uid_t owner, gid_t group
                goto out;
        }
 
+       ret = check_iflags_access(ff, ino, EXT2_INODE(&inode), W_OK);
+       if (ret)
+               goto out;
+
        /* FUSE seems to feed us ~0 to mean "don't change" */
        if (owner != (uid_t) ~0) {
                /* Only root gets to change UID. */