Skip to content
/ linux Public
forked from torvalds/linux

Commit

Permalink
fs: port privilege checking helpers to mnt_idmap
Browse files Browse the repository at this point in the history
Convert to struct mnt_idmap.

Last cycle we merged the necessary infrastructure in
256c8ae ("fs: introduce dedicated idmap type for mounts").
This is just the conversion to struct mnt_idmap.

Currently we still pass around the plain namespace that was attached to a
mount. This is in general pretty convenient but it makes it easy to
conflate namespaces that are relevant on the filesystem with namespaces
that are relevent on the mount level. Especially for non-vfs developers
without detailed knowledge in this area this can be a potential source for
bugs.

Once the conversion to struct mnt_idmap is done all helpers down to the
really low-level helpers will take a struct mnt_idmap argument instead of
two namespace arguments. This way it becomes impossible to conflate the two
eliminating the possibility of any bugs. All of the vfs and all filesystems
only operate on struct mnt_idmap.

Acked-by: Dave Chinner <[email protected]>
Reviewed-by: Christoph Hellwig <[email protected]>
Signed-off-by: Christian Brauner (Microsoft) <[email protected]>
  • Loading branch information
brauner committed Jan 19, 2023
1 parent 01beba7 commit 9452e93
Show file tree
Hide file tree
Showing 20 changed files with 93 additions and 94 deletions.
53 changes: 29 additions & 24 deletions fs/attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
/**
* setattr_should_drop_sgid - determine whether the setgid bit needs to be
* removed
* @mnt_userns: user namespace of the mount @inode was found from
* @idmap: idmap of the mount @inode was found from
* @inode: inode to check
*
* This function determines whether the setgid bit needs to be removed.
Expand All @@ -33,16 +33,17 @@
*
* Return: ATTR_KILL_SGID if setgid bit needs to be removed, 0 otherwise.
*/
int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
int setattr_should_drop_sgid(struct mnt_idmap *idmap,
const struct inode *inode)
{
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);
umode_t mode = inode->i_mode;

if (!(mode & S_ISGID))
return 0;
if (mode & S_IXGRP)
return ATTR_KILL_SGID;
if (!in_group_or_capable(mnt_userns, inode,
if (!in_group_or_capable(idmap, inode,
i_gid_into_vfsgid(mnt_userns, inode)))
return ATTR_KILL_SGID;
return 0;
Expand All @@ -51,7 +52,7 @@ int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
/**
* setattr_should_drop_suidgid - determine whether the set{g,u}id bit needs to
* be dropped
* @mnt_userns: user namespace of the mount @inode was found from
* @idmap: idmap of the mount @inode was found from
* @inode: inode to check
*
* This function determines whether the set{g,u}id bits need to be removed.
Expand All @@ -63,7 +64,7 @@ int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
* Return: A mask of ATTR_KILL_S{G,U}ID indicating which - if any - setid bits
* to remove, 0 otherwise.
*/
int setattr_should_drop_suidgid(struct user_namespace *mnt_userns,
int setattr_should_drop_suidgid(struct mnt_idmap *idmap,
struct inode *inode)
{
umode_t mode = inode->i_mode;
Expand All @@ -73,7 +74,7 @@ int setattr_should_drop_suidgid(struct user_namespace *mnt_userns,
if (unlikely(mode & S_ISUID))
kill = ATTR_KILL_SUID;

kill |= setattr_should_drop_sgid(mnt_userns, inode);
kill |= setattr_should_drop_sgid(idmap, inode);

if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode)))
return kill;
Expand All @@ -84,24 +85,26 @@ EXPORT_SYMBOL(setattr_should_drop_suidgid);

/**
* chown_ok - verify permissions to chown inode
* @mnt_userns: user namespace of the mount @inode was found from
* @idmap: idmap of the mount @inode was found from
* @inode: inode to check permissions on
* @ia_vfsuid: uid to chown @inode to
*
* If the inode has been found through an idmapped mount the user namespace of
* the vfsmount must be passed through @mnt_userns. This function will then
* take care to map the inode according to @mnt_userns before checking
* If the inode has been found through an idmapped mount the idmap of
* the vfsmount must be passed through @idmap. This function will then
* take care to map the inode according to @idmap before checking
* permissions. On non-idmapped mounts or if permission checking is to be
* performed on the raw inode simply passs init_user_ns.
* performed on the raw inode simply pass @nop_mnt_idmap.
*/
static bool chown_ok(struct user_namespace *mnt_userns,
static bool chown_ok(struct mnt_idmap *idmap,
const struct inode *inode, vfsuid_t ia_vfsuid)
{
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);

vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_userns, inode);
if (vfsuid_eq_kuid(vfsuid, current_fsuid()) &&
vfsuid_eq(ia_vfsuid, vfsuid))
return true;
if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_CHOWN))
if (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))
return true;
if (!vfsuid_valid(vfsuid) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
Expand All @@ -111,19 +114,21 @@ static bool chown_ok(struct user_namespace *mnt_userns,

/**
* chgrp_ok - verify permissions to chgrp inode
* @mnt_userns: user namespace of the mount @inode was found from
* @idmap: idmap of the mount @inode was found from
* @inode: inode to check permissions on
* @ia_vfsgid: gid to chown @inode to
*
* If the inode has been found through an idmapped mount the user namespace of
* the vfsmount must be passed through @mnt_userns. This function will then
* take care to map the inode according to @mnt_userns before checking
* If the inode has been found through an idmapped mount the idmap of
* the vfsmount must be passed through @idmap. This function will then
* take care to map the inode according to @idmap before checking
* permissions. On non-idmapped mounts or if permission checking is to be
* performed on the raw inode simply passs init_user_ns.
* performed on the raw inode simply pass @nop_mnt_idmap.
*/
static bool chgrp_ok(struct user_namespace *mnt_userns,
static bool chgrp_ok(struct mnt_idmap *idmap,
const struct inode *inode, vfsgid_t ia_vfsgid)
{
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);

vfsgid_t vfsgid = i_gid_into_vfsgid(mnt_userns, inode);
vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_userns, inode);
if (vfsuid_eq_kuid(vfsuid, current_fsuid())) {
Expand All @@ -132,7 +137,7 @@ static bool chgrp_ok(struct user_namespace *mnt_userns,
if (vfsgid_in_group_p(ia_vfsgid))
return true;
}
if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_CHOWN))
if (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))
return true;
if (!vfsgid_valid(vfsgid) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
Expand Down Expand Up @@ -184,12 +189,12 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,

/* Make sure a caller can chown. */
if ((ia_valid & ATTR_UID) &&
!chown_ok(mnt_userns, inode, attr->ia_vfsuid))
!chown_ok(idmap, inode, attr->ia_vfsuid))
return -EPERM;

/* Make sure caller can chgrp. */
if ((ia_valid & ATTR_GID) &&
!chgrp_ok(mnt_userns, inode, attr->ia_vfsgid))
!chgrp_ok(idmap, inode, attr->ia_vfsgid))
return -EPERM;

/* Make sure a caller can chmod. */
Expand All @@ -205,7 +210,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
vfsgid = i_gid_into_vfsgid(mnt_userns, inode);

/* Also check the setgid bit! */
if (!in_group_or_capable(mnt_userns, inode, vfsgid))
if (!in_group_or_capable(idmap, inode, vfsgid))
attr->ia_mode &= ~S_ISGID;
}

Expand Down Expand Up @@ -316,7 +321,7 @@ void setattr_copy(struct mnt_idmap *idmap, struct inode *inode,
inode->i_ctime = attr->ia_ctime;
if (ia_valid & ATTR_MODE) {
umode_t mode = attr->ia_mode;
if (!in_group_or_capable(mnt_userns, inode,
if (!in_group_or_capable(idmap, inode,
i_gid_into_vfsgid(mnt_userns, inode)))
mode &= ~S_ISGID;
inode->i_mode = mode;
Expand Down
3 changes: 1 addition & 2 deletions fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,6 @@ static int btrfs_may_delete(struct mnt_idmap *idmap,
struct inode *dir, struct dentry *victim, int isdir)
{
int error;
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);

if (d_really_is_negative(victim))
return -ENOENT;
Expand All @@ -915,7 +914,7 @@ static int btrfs_may_delete(struct mnt_idmap *idmap,
return error;
if (IS_APPEND(dir))
return -EPERM;
if (check_sticky(mnt_userns, dir, d_inode(victim)) ||
if (check_sticky(idmap, dir, d_inode(victim)) ||
IS_APPEND(d_inode(victim)) || IS_IMMUTABLE(d_inode(victim)) ||
IS_SWAPFILE(d_inode(victim)))
return -EPERM;
Expand Down
3 changes: 1 addition & 2 deletions fs/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -1415,15 +1415,14 @@ void would_dump(struct linux_binprm *bprm, struct file *file)
{
struct inode *inode = file_inode(file);
struct mnt_idmap *idmap = file_mnt_idmap(file);
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);
if (inode_permission(idmap, inode, MAY_READ) < 0) {
struct user_namespace *old, *user_ns;
bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP;

/* Ensure mm->user_ns contains the executable */
user_ns = old = bprm->mm->user_ns;
while ((user_ns != &init_user_ns) &&
!privileged_wrt_inode_uidgid(user_ns, mnt_userns, inode))
!privileged_wrt_inode_uidgid(user_ns, idmap, inode))
user_ns = user_ns->parent;

if (old != user_ns) {
Expand Down
12 changes: 6 additions & 6 deletions fs/f2fs/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,12 @@ struct posix_acl *f2fs_get_acl(struct inode *inode, int type, bool rcu)
return __f2fs_get_acl(inode, type, NULL);
}

static int f2fs_acl_update_mode(struct user_namespace *mnt_userns,
static int f2fs_acl_update_mode(struct mnt_idmap *idmap,
struct inode *inode, umode_t *mode_p,
struct posix_acl **acl)
{
umode_t mode = inode->i_mode;
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);
int error;

if (is_inode_flag_set(inode, FI_ACL_MODE))
Expand All @@ -220,13 +221,13 @@ static int f2fs_acl_update_mode(struct user_namespace *mnt_userns,
if (error == 0)
*acl = NULL;
if (!vfsgid_in_group_p(i_gid_into_vfsgid(mnt_userns, inode)) &&
!capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID))
!capable_wrt_inode_uidgid(idmap, inode, CAP_FSETID))
mode &= ~S_ISGID;
*mode_p = mode;
return 0;
}

static int __f2fs_set_acl(struct user_namespace *mnt_userns,
static int __f2fs_set_acl(struct mnt_idmap *idmap,
struct inode *inode, int type,
struct posix_acl *acl, struct page *ipage)
{
Expand All @@ -240,7 +241,7 @@ static int __f2fs_set_acl(struct user_namespace *mnt_userns,
case ACL_TYPE_ACCESS:
name_index = F2FS_XATTR_INDEX_POSIX_ACL_ACCESS;
if (acl && !ipage) {
error = f2fs_acl_update_mode(mnt_userns, inode,
error = f2fs_acl_update_mode(idmap, inode,
&mode, &acl);
if (error)
return error;
Expand Down Expand Up @@ -279,13 +280,12 @@ static int __f2fs_set_acl(struct user_namespace *mnt_userns,
int f2fs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
struct posix_acl *acl, int type)
{
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);
struct inode *inode = d_inode(dentry);

if (unlikely(f2fs_cp_error(F2FS_I_SB(inode))))
return -EIO;

return __f2fs_set_acl(mnt_userns, inode, type, acl, NULL);
return __f2fs_set_acl(idmap, inode, type, acl, NULL);
}

/*
Expand Down
2 changes: 1 addition & 1 deletion fs/f2fs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ static void __setattr_copy(struct mnt_idmap *idmap,
vfsgid_t vfsgid = i_gid_into_vfsgid(mnt_userns, inode);

if (!vfsgid_in_group_p(vfsgid) &&
!capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID))
!capable_wrt_inode_uidgid(idmap, inode, CAP_FSETID))
mode &= ~S_ISGID;
set_acl_inode(inode, mode);
}
Expand Down
2 changes: 1 addition & 1 deletion fs/fuse/acl.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ int fuse_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
}

if (!vfsgid_in_group_p(i_gid_into_vfsgid(&init_user_ns, inode)) &&
!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
!capable_wrt_inode_uidgid(&nop_mnt_idmap, inode, CAP_FSETID))
extra_flags |= FUSE_SETXATTR_ACL_KILL_SGID;

ret = fuse_setxattr(inode, name, value, size, 0, extra_flags);
Expand Down
3 changes: 2 additions & 1 deletion fs/fuse/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,8 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
return err;

if (fc->handle_killpriv_v2 &&
setattr_should_drop_suidgid(&init_user_ns, file_inode(file))) {
setattr_should_drop_suidgid(&nop_mnt_idmap,
file_inode(file))) {
goto writethrough;
}

Expand Down
20 changes: 11 additions & 9 deletions fs/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1953,7 +1953,7 @@ EXPORT_SYMBOL(touch_atime);
* response to write or truncate. Return 0 if nothing has to be changed.
* Negative value on error (change should be denied).
*/
int dentry_needs_remove_privs(struct user_namespace *mnt_userns,
int dentry_needs_remove_privs(struct mnt_idmap *idmap,
struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
Expand All @@ -1963,7 +1963,7 @@ int dentry_needs_remove_privs(struct user_namespace *mnt_userns,
if (IS_NOSEC(inode))
return 0;

mask = setattr_should_drop_suidgid(mnt_userns, inode);
mask = setattr_should_drop_suidgid(idmap, inode);
ret = security_inode_need_killpriv(dentry);
if (ret < 0)
return ret;
Expand Down Expand Up @@ -1995,7 +1995,7 @@ static int __file_remove_privs(struct file *file, unsigned int flags)
if (IS_NOSEC(inode) || !S_ISREG(inode->i_mode))
return 0;

kill = dentry_needs_remove_privs(file_mnt_user_ns(file), dentry);
kill = dentry_needs_remove_privs(file_mnt_idmap(file), dentry);
if (kill < 0)
return kill;

Expand Down Expand Up @@ -2461,7 +2461,7 @@ EXPORT_SYMBOL(current_time);

/**
* in_group_or_capable - check whether caller is CAP_FSETID privileged
* @mnt_userns: user namespace of the mount @inode was found from
* @idmap: idmap of the mount @inode was found from
* @inode: inode to check
* @vfsgid: the new/current vfsgid of @inode
*
Expand All @@ -2471,19 +2471,19 @@ EXPORT_SYMBOL(current_time);
*
* Return: true if the caller is sufficiently privileged, false if not.
*/
bool in_group_or_capable(struct user_namespace *mnt_userns,
bool in_group_or_capable(struct mnt_idmap *idmap,
const struct inode *inode, vfsgid_t vfsgid)
{
if (vfsgid_in_group_p(vfsgid))
return true;
if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID))
if (capable_wrt_inode_uidgid(idmap, inode, CAP_FSETID))
return true;
return false;
}

/**
* mode_strip_sgid - handle the sgid bit for non-directories
* @mnt_userns: User namespace of the mount the inode was created from
* @idmap: idmap of the mount the inode was created from
* @dir: parent directory inode
* @mode: mode of the file to be created in @dir
*
Expand All @@ -2495,14 +2495,16 @@ bool in_group_or_capable(struct user_namespace *mnt_userns,
*
* Return: the new mode to use for the file
*/
umode_t mode_strip_sgid(struct user_namespace *mnt_userns,
umode_t mode_strip_sgid(struct mnt_idmap *idmap,
const struct inode *dir, umode_t mode)
{
struct user_namespace *mnt_userns = mnt_idmap_owner(idmap);

if ((mode & (S_ISGID | S_IXGRP)) != (S_ISGID | S_IXGRP))
return mode;
if (S_ISDIR(mode) || !dir || !(dir->i_mode & S_ISGID))
return mode;
if (in_group_or_capable(mnt_userns, dir,
if (in_group_or_capable(idmap, dir,
i_gid_into_vfsgid(mnt_userns, dir)))
return mode;
return mode & ~S_ISGID;
Expand Down
6 changes: 3 additions & 3 deletions fs/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ extern int vfs_open(const struct path *, struct file *);
* inode.c
*/
extern long prune_icache_sb(struct super_block *sb, struct shrink_control *sc);
int dentry_needs_remove_privs(struct user_namespace *, struct dentry *dentry);
bool in_group_or_capable(struct user_namespace *mnt_userns,
int dentry_needs_remove_privs(struct mnt_idmap *, struct dentry *dentry);
bool in_group_or_capable(struct mnt_idmap *idmap,
const struct inode *inode, vfsgid_t vfsgid);

/*
Expand Down Expand Up @@ -261,5 +261,5 @@ ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *po
/*
* fs/attr.c
*/
int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
int setattr_should_drop_sgid(struct mnt_idmap *idmap,
const struct inode *inode);
Loading

0 comments on commit 9452e93

Please sign in to comment.