From: Darrick J. Wong Date: Wed, 21 May 2025 22:42:30 +0000 (-0700) Subject: fuse2fs: fix group membership checking in op_chmod X-Git-Tag: v1.47.3-rc1~34 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3469e6ff606af8938a4ed3332b9cd43e91fa5164;p=thirdparty%2Fe2fsprogs.git fuse2fs: fix group membership checking in op_chmod In the decade or so since I touched fuse2fs, libfuse3 has grown the ability to read the group ids of a process making a chmod request. So now we actually /can/ determine if a file's gid is a in the group list of the process that initiated a fuse request. Let's implement that too. Signed-off-by: Darrick J. Wong Link: https://lore.kernel.org/r/174786678066.1383760.15547379523110361909.stgit@frogsfrogsfrogs Signed-off-by: Theodore Ts'o --- diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c index c65f3126..4fa000ec 100644 --- a/misc/fuse2fs.c +++ b/misc/fuse2fs.c @@ -2066,6 +2066,70 @@ out: return ret; } +#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0) +/* Obtain group ids of the process that sent us a command(?) */ +static int get_req_groups(struct fuse2fs *ff, gid_t **gids, size_t *nr_gids) +{ + ext2_filsys fs = ff->fs; + errcode_t err; + gid_t *array; + int nr = 32; /* nobody has more than 32 groups right? */ + int ret; + + do { + err = ext2fs_get_array(nr, sizeof(gid_t), &array); + if (err) + return translate_error(fs, 0, err); + + ret = fuse_getgroups(nr, array); + if (ret < 0) + return ret; + + if (ret <= nr) { + *gids = array; + *nr_gids = ret; + return 0; + } + + ext2fs_free_mem(&array); + nr = ret; + } while (0); + + /* shut up gcc */ + return -ENOMEM; +} + +/* + * Is this file's group id in the set of groups associated with the process + * that initiated the fuse request? Returns 1 for yes, 0 for no, or a negative + * errno. + */ +static int in_file_group(struct fuse_context *ctxt, + const struct ext2_inode_large *inode) +{ + struct fuse2fs *ff = (struct fuse2fs *)ctxt->private_data; + gid_t *gids = NULL; + size_t i, nr_gids = 0; + gid_t gid = inode_gid(*inode); + int ret; + + ret = get_req_groups(ff, &gids, &nr_gids); + if (ret < 0) + return ret; + + for (i = 0; i < nr_gids; i++) + if (gids[i] == gid) + return 1; + return 0; +} +#else +static int in_file_group(struct fuse_context *ctxt, + const struct ext2_inode_large *inode) +{ + return ctxt->gid == inode_gid(*inode); +} +#endif + static int op_chmod(const char *path, mode_t mode #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0) , struct fuse_file_info *fi EXT2FS_ATTR((unused)) @@ -2112,11 +2176,21 @@ static int op_chmod(const char *path, mode_t mode * of the user's groups, but FUSE only tells us about the primary * group. */ - if (!is_superuser(ff, ctxt) && ctxt->gid != inode_gid(inode)) - mode &= ~S_ISGID; + if (!is_superuser(ff, ctxt)) { + ret = in_file_group(ctxt, &inode); + if (ret < 0) + goto out; + + if (!ret) + mode &= ~S_ISGID; + } inode.i_mode &= ~0xFFF; inode.i_mode |= mode & 0xFFF; + + dbg_printf(ff, "%s: path=%s new_mode=0%o ino=%d\n", __func__, + path, inode.i_mode, ino); + ret = update_ctime(fs, ino, &inode); if (ret) goto out;