mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 15:12:13 +02:00
Merge branch 'fs.ovl.setgid' into for-next
* fs.ovl.setgid: ovl: remove privs in ovl_fallocate() ovl: remove privs in ovl_copyfile() attr: use consistent sgid stripping checks attr: add setattr_should_drop_sgid() fs: move should_remove_suid() attr: add in_group_or_capable()
This commit is contained in:
commit
b4dd412d4a
|
|
@ -2940,7 +2940,7 @@ Produces::
|
|||
bash-1994 [000] .... 4342.324898: ima_get_action <-process_measurement
|
||||
bash-1994 [000] .... 4342.324898: ima_match_policy <-ima_get_action
|
||||
bash-1994 [000] .... 4342.324899: do_truncate <-do_last
|
||||
bash-1994 [000] .... 4342.324899: should_remove_suid <-do_truncate
|
||||
bash-1994 [000] .... 4342.324899: setattr_should_drop_suidgid <-do_truncate
|
||||
bash-1994 [000] .... 4342.324899: notify_change <-do_truncate
|
||||
bash-1994 [000] .... 4342.324900: current_fs_time <-notify_change
|
||||
bash-1994 [000] .... 4342.324900: current_kernel_time <-current_fs_time
|
||||
|
|
|
|||
74
fs/attr.c
74
fs/attr.c
|
|
@ -18,6 +18,70 @@
|
|||
#include <linux/evm.h>
|
||||
#include <linux/ima.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
/**
|
||||
* setattr_should_drop_sgid - determine whether the setgid bit needs to be
|
||||
* removed
|
||||
* @mnt_userns: user namespace of the mount @inode was found from
|
||||
* @inode: inode to check
|
||||
*
|
||||
* This function determines whether the setgid bit needs to be removed.
|
||||
* We retain backwards compatibility and require setgid bit to be removed
|
||||
* unconditionally if S_IXGRP is set. Otherwise we have the exact same
|
||||
* requirements as setattr_prepare() and setattr_copy().
|
||||
*
|
||||
* Return: ATTR_KILL_SGID if setgid bit needs to be removed, 0 otherwise.
|
||||
*/
|
||||
int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
|
||||
const struct inode *inode)
|
||||
{
|
||||
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,
|
||||
i_gid_into_vfsgid(mnt_userns, inode)))
|
||||
return ATTR_KILL_SGID;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @inode: inode to check
|
||||
*
|
||||
* This function determines whether the set{g,u}id bits need to be removed.
|
||||
* If the setuid bit needs to be removed ATTR_KILL_SUID is returned. If the
|
||||
* setgid bit needs to be removed ATTR_KILL_SGID is returned. If both
|
||||
* set{g,u}id bits need to be removed the corresponding mask of both flags is
|
||||
* returned.
|
||||
*
|
||||
* 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,
|
||||
struct inode *inode)
|
||||
{
|
||||
umode_t mode = inode->i_mode;
|
||||
int kill = 0;
|
||||
|
||||
/* suid always must be killed */
|
||||
if (unlikely(mode & S_ISUID))
|
||||
kill = ATTR_KILL_SUID;
|
||||
|
||||
kill |= setattr_should_drop_sgid(mnt_userns, inode);
|
||||
|
||||
if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode)))
|
||||
return kill;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(setattr_should_drop_suidgid);
|
||||
|
||||
/**
|
||||
* chown_ok - verify permissions to chown inode
|
||||
* @mnt_userns: user namespace of the mount @inode was found from
|
||||
|
|
@ -140,8 +204,7 @@ int setattr_prepare(struct user_namespace *mnt_userns, struct dentry *dentry,
|
|||
vfsgid = i_gid_into_vfsgid(mnt_userns, inode);
|
||||
|
||||
/* Also check the setgid bit! */
|
||||
if (!vfsgid_in_group_p(vfsgid) &&
|
||||
!capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID))
|
||||
if (!in_group_or_capable(mnt_userns, inode, vfsgid))
|
||||
attr->ia_mode &= ~S_ISGID;
|
||||
}
|
||||
|
||||
|
|
@ -251,9 +314,8 @@ void setattr_copy(struct user_namespace *mnt_userns, struct inode *inode,
|
|||
inode->i_ctime = attr->ia_ctime;
|
||||
if (ia_valid & ATTR_MODE) {
|
||||
umode_t mode = attr->ia_mode;
|
||||
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))
|
||||
if (!in_group_or_capable(mnt_userns, inode,
|
||||
i_gid_into_vfsgid(mnt_userns, inode)))
|
||||
mode &= ~S_ISGID;
|
||||
inode->i_mode = mode;
|
||||
}
|
||||
|
|
@ -375,7 +437,7 @@ int notify_change(struct user_namespace *mnt_userns, struct dentry *dentry,
|
|||
}
|
||||
}
|
||||
if (ia_valid & ATTR_KILL_SGID) {
|
||||
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
|
||||
if (mode & S_ISGID) {
|
||||
if (!(ia_valid & ATTR_MODE)) {
|
||||
ia_valid = attr->ia_valid |= ATTR_MODE;
|
||||
attr->ia_mode = inode->i_mode;
|
||||
|
|
|
|||
|
|
@ -1313,7 +1313,7 @@ static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
|||
return err;
|
||||
|
||||
if (fc->handle_killpriv_v2 &&
|
||||
should_remove_suid(file_dentry(file))) {
|
||||
setattr_should_drop_suidgid(&init_user_ns, file_inode(file))) {
|
||||
goto writethrough;
|
||||
}
|
||||
|
||||
|
|
|
|||
64
fs/inode.c
64
fs/inode.c
|
|
@ -1948,41 +1948,13 @@ void touch_atime(const struct path *path)
|
|||
}
|
||||
EXPORT_SYMBOL(touch_atime);
|
||||
|
||||
/*
|
||||
* The logic we want is
|
||||
*
|
||||
* if suid or (sgid and xgrp)
|
||||
* remove privs
|
||||
*/
|
||||
int should_remove_suid(struct dentry *dentry)
|
||||
{
|
||||
umode_t mode = d_inode(dentry)->i_mode;
|
||||
int kill = 0;
|
||||
|
||||
/* suid always must be killed */
|
||||
if (unlikely(mode & S_ISUID))
|
||||
kill = ATTR_KILL_SUID;
|
||||
|
||||
/*
|
||||
* sgid without any exec bits is just a mandatory locking mark; leave
|
||||
* it alone. If some exec bits are set, it's a real sgid; kill it.
|
||||
*/
|
||||
if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
|
||||
kill |= ATTR_KILL_SGID;
|
||||
|
||||
if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode)))
|
||||
return kill;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(should_remove_suid);
|
||||
|
||||
/*
|
||||
* Return mask of changes for notify_change() that need to be done as a
|
||||
* 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 dentry *dentry)
|
||||
int dentry_needs_remove_privs(struct user_namespace *mnt_userns,
|
||||
struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
int mask = 0;
|
||||
|
|
@ -1991,7 +1963,7 @@ int dentry_needs_remove_privs(struct dentry *dentry)
|
|||
if (IS_NOSEC(inode))
|
||||
return 0;
|
||||
|
||||
mask = should_remove_suid(dentry);
|
||||
mask = setattr_should_drop_suidgid(mnt_userns, inode);
|
||||
ret = security_inode_need_killpriv(dentry);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
|
@ -2023,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(dentry);
|
||||
kill = dentry_needs_remove_privs(file_mnt_user_ns(file), dentry);
|
||||
if (kill < 0)
|
||||
return kill;
|
||||
|
||||
|
|
@ -2487,6 +2459,28 @@ struct timespec64 current_time(struct inode *inode)
|
|||
}
|
||||
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
|
||||
* @inode: inode to check
|
||||
* @vfsgid: the new/current vfsgid of @inode
|
||||
*
|
||||
* Check wether @vfsgid is in the caller's group list or if the caller is
|
||||
* privileged with CAP_FSETID over @inode. This can be used to determine
|
||||
* whether the setgid bit can be kept or must be dropped.
|
||||
*
|
||||
* Return: true if the caller is sufficiently privileged, false if not.
|
||||
*/
|
||||
bool in_group_or_capable(struct user_namespace *mnt_userns,
|
||||
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))
|
||||
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
|
||||
|
|
@ -2508,11 +2502,9 @@ umode_t mode_strip_sgid(struct user_namespace *mnt_userns,
|
|||
return mode;
|
||||
if (S_ISDIR(mode) || !dir || !(dir->i_mode & S_ISGID))
|
||||
return mode;
|
||||
if (in_group_p(i_gid_into_mnt(mnt_userns, dir)))
|
||||
if (in_group_or_capable(mnt_userns, dir,
|
||||
i_gid_into_vfsgid(mnt_userns, dir)))
|
||||
return mode;
|
||||
if (capable_wrt_inode_uidgid(mnt_userns, dir, CAP_FSETID))
|
||||
return mode;
|
||||
|
||||
return mode & ~S_ISGID;
|
||||
}
|
||||
EXPORT_SYMBOL(mode_strip_sgid);
|
||||
|
|
|
|||
|
|
@ -150,7 +150,9 @@ extern int vfs_open(const struct path *, struct file *);
|
|||
* inode.c
|
||||
*/
|
||||
extern long prune_icache_sb(struct super_block *sb, struct shrink_control *sc);
|
||||
extern int dentry_needs_remove_privs(struct dentry *dentry);
|
||||
int dentry_needs_remove_privs(struct user_namespace *, struct dentry *dentry);
|
||||
bool in_group_or_capable(struct user_namespace *mnt_userns,
|
||||
const struct inode *inode, vfsgid_t vfsgid);
|
||||
|
||||
/*
|
||||
* fs-writeback.c
|
||||
|
|
@ -234,3 +236,9 @@ int do_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry,
|
|||
struct xattr_ctx *ctx);
|
||||
|
||||
ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *pos);
|
||||
|
||||
/*
|
||||
* fs/attr.c
|
||||
*/
|
||||
int setattr_should_drop_sgid(struct user_namespace *mnt_userns,
|
||||
const struct inode *inode);
|
||||
|
|
|
|||
|
|
@ -1991,7 +1991,7 @@ static int __ocfs2_change_file_space(struct file *file, struct inode *inode,
|
|||
}
|
||||
}
|
||||
|
||||
if (file && should_remove_suid(file->f_path.dentry)) {
|
||||
if (file && setattr_should_drop_suidgid(&init_user_ns, file_inode(file))) {
|
||||
ret = __ocfs2_write_remove_suid(inode, di_bh);
|
||||
if (ret) {
|
||||
mlog_errno(ret);
|
||||
|
|
@ -2279,7 +2279,7 @@ static int ocfs2_prepare_inode_for_write(struct file *file,
|
|||
* inode. There's also the dinode i_size state which
|
||||
* can be lost via setattr during extending writes (we
|
||||
* set inode->i_size at the end of a write. */
|
||||
if (should_remove_suid(dentry)) {
|
||||
if (setattr_should_drop_suidgid(&init_user_ns, inode)) {
|
||||
if (meta_level == 0) {
|
||||
ocfs2_inode_unlock_for_extent_tree(inode,
|
||||
&di_bh,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry,
|
|||
}
|
||||
|
||||
/* Remove suid, sgid, and file capabilities on truncate too */
|
||||
ret = dentry_needs_remove_privs(dentry);
|
||||
ret = dentry_needs_remove_privs(mnt_userns, dentry);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret)
|
||||
|
|
@ -723,10 +723,10 @@ int chown_common(const struct path *path, uid_t user, gid_t group)
|
|||
return -EINVAL;
|
||||
if ((group != (gid_t)-1) && !setattr_vfsgid(&newattrs, gid))
|
||||
return -EINVAL;
|
||||
if (!S_ISDIR(inode->i_mode))
|
||||
newattrs.ia_valid |=
|
||||
ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV;
|
||||
inode_lock(inode);
|
||||
if (!S_ISDIR(inode->i_mode))
|
||||
newattrs.ia_valid |= ATTR_KILL_SUID | ATTR_KILL_PRIV |
|
||||
setattr_should_drop_sgid(mnt_userns, inode);
|
||||
/* Continue to send actual fs values, not the mount values. */
|
||||
error = security_path_chown(
|
||||
path,
|
||||
|
|
|
|||
|
|
@ -517,9 +517,16 @@ static long ovl_fallocate(struct file *file, int mode, loff_t offset, loff_t len
|
|||
const struct cred *old_cred;
|
||||
int ret;
|
||||
|
||||
inode_lock(inode);
|
||||
/* Update mode */
|
||||
ovl_copyattr(inode);
|
||||
ret = file_remove_privs(file);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
|
||||
ret = ovl_real_fdget(file, &real);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto out_unlock;
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file)->i_sb);
|
||||
ret = vfs_fallocate(real.file, mode, offset, len);
|
||||
|
|
@ -530,6 +537,9 @@ static long ovl_fallocate(struct file *file, int mode, loff_t offset, loff_t len
|
|||
|
||||
fdput(real);
|
||||
|
||||
out_unlock:
|
||||
inode_unlock(inode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -567,14 +577,23 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in,
|
|||
const struct cred *old_cred;
|
||||
loff_t ret;
|
||||
|
||||
inode_lock(inode_out);
|
||||
if (op != OVL_DEDUPE) {
|
||||
/* Update mode */
|
||||
ovl_copyattr(inode_out);
|
||||
ret = file_remove_privs(file_out);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
ret = ovl_real_fdget(file_out, &real_out);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto out_unlock;
|
||||
|
||||
ret = ovl_real_fdget(file_in, &real_in);
|
||||
if (ret) {
|
||||
fdput(real_out);
|
||||
return ret;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
old_cred = ovl_override_creds(file_inode(file_out)->i_sb);
|
||||
|
|
@ -603,6 +622,9 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in,
|
|||
fdput(real_in);
|
||||
fdput(real_out);
|
||||
|
||||
out_unlock:
|
||||
inode_unlock(inode_out);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3104,7 +3104,7 @@ extern void __destroy_inode(struct inode *);
|
|||
extern struct inode *new_inode_pseudo(struct super_block *sb);
|
||||
extern struct inode *new_inode(struct super_block *sb);
|
||||
extern void free_inode_nonrcu(struct inode *inode);
|
||||
extern int should_remove_suid(struct dentry *);
|
||||
extern int setattr_should_drop_suidgid(struct user_namespace *, struct inode *);
|
||||
extern int file_remove_privs(struct file *);
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user