Merge branch 'userns-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace
Pull userns updates from Eric Biederman: "This is the last couple of vfs bits to enable root in a user namespace to mount and manipulate a filesystem with backing store (AKA not a virtual filesystem like proc, but a filesystem where the unprivileged user controls the content). The target filesystem for this work is fuse, and Miklos should be sending you the pull request for the fuse bits this merge window. The two key patches are "evm: Don't update hmacs in user ns mounts" and "vfs: Don't allow changing the link count of an inode with an invalid uid or gid". Those close small gaps in the vfs that would be a problem if an unprivileged fuse filesystem is mounted. The rest of the changes are things that are now safe to allow a root user in a user namespace to do with a filesystem they have mounted. The most interesting development is that remount is now safe" * 'userns-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace: fs: Allow CAP_SYS_ADMIN in s_user_ns to freeze and thaw filesystems capabilities: Allow privileged user in s_user_ns to set security.* xattrs fs: Allow superblock owner to access do_remount_sb() fs: Allow superblock owner to replace invalid owners of inodes vfs: Allow userns root to call mknod on owned filesystems. vfs: Don't allow changing the link count of an inode with an invalid uid or gid evm: Don't update hmacs in user ns mounts
This commit is contained in:
commit
d8aed8415b
36
fs/attr.c
36
fs/attr.c
|
@ -18,6 +18,32 @@
|
||||||
#include <linux/evm.h>
|
#include <linux/evm.h>
|
||||||
#include <linux/ima.h>
|
#include <linux/ima.h>
|
||||||
|
|
||||||
|
static bool chown_ok(const struct inode *inode, kuid_t uid)
|
||||||
|
{
|
||||||
|
if (uid_eq(current_fsuid(), inode->i_uid) &&
|
||||||
|
uid_eq(uid, inode->i_uid))
|
||||||
|
return true;
|
||||||
|
if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
|
||||||
|
return true;
|
||||||
|
if (uid_eq(inode->i_uid, INVALID_UID) &&
|
||||||
|
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool chgrp_ok(const struct inode *inode, kgid_t gid)
|
||||||
|
{
|
||||||
|
if (uid_eq(current_fsuid(), inode->i_uid) &&
|
||||||
|
(in_group_p(gid) || gid_eq(gid, inode->i_gid)))
|
||||||
|
return true;
|
||||||
|
if (capable_wrt_inode_uidgid(inode, CAP_CHOWN))
|
||||||
|
return true;
|
||||||
|
if (gid_eq(inode->i_gid, INVALID_GID) &&
|
||||||
|
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setattr_prepare - check if attribute changes to a dentry are allowed
|
* setattr_prepare - check if attribute changes to a dentry are allowed
|
||||||
* @dentry: dentry to check
|
* @dentry: dentry to check
|
||||||
|
@ -52,17 +78,11 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr)
|
||||||
goto kill_priv;
|
goto kill_priv;
|
||||||
|
|
||||||
/* Make sure a caller can chown. */
|
/* Make sure a caller can chown. */
|
||||||
if ((ia_valid & ATTR_UID) &&
|
if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
|
||||||
(!uid_eq(current_fsuid(), inode->i_uid) ||
|
|
||||||
!uid_eq(attr->ia_uid, inode->i_uid)) &&
|
|
||||||
!capable_wrt_inode_uidgid(inode, CAP_CHOWN))
|
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
/* Make sure caller can chgrp. */
|
/* Make sure caller can chgrp. */
|
||||||
if ((ia_valid & ATTR_GID) &&
|
if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid))
|
||||||
(!uid_eq(current_fsuid(), inode->i_uid) ||
|
|
||||||
(!in_group_p(attr->ia_gid) && !gid_eq(attr->ia_gid, inode->i_gid))) &&
|
|
||||||
!capable_wrt_inode_uidgid(inode, CAP_CHOWN))
|
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
/* Make sure a caller can chmod. */
|
/* Make sure a caller can chmod. */
|
||||||
|
|
|
@ -549,7 +549,7 @@ static int ioctl_fsfreeze(struct file *filp)
|
||||||
{
|
{
|
||||||
struct super_block *sb = file_inode(filp)->i_sb;
|
struct super_block *sb = file_inode(filp)->i_sb;
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
/* If filesystem doesn't support freeze feature, return. */
|
/* If filesystem doesn't support freeze feature, return. */
|
||||||
|
@ -566,7 +566,7 @@ static int ioctl_fsthaw(struct file *filp)
|
||||||
{
|
{
|
||||||
struct super_block *sb = file_inode(filp)->i_sb;
|
struct super_block *sb = file_inode(filp)->i_sb;
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
/* Thaw */
|
/* Thaw */
|
||||||
|
|
16
fs/namei.c
16
fs/namei.c
|
@ -984,13 +984,15 @@ static bool safe_hardlink_source(struct inode *inode)
|
||||||
*/
|
*/
|
||||||
static int may_linkat(struct path *link)
|
static int may_linkat(struct path *link)
|
||||||
{
|
{
|
||||||
struct inode *inode;
|
struct inode *inode = link->dentry->d_inode;
|
||||||
|
|
||||||
|
/* Inode writeback is not safe when the uid or gid are invalid. */
|
||||||
|
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
|
||||||
|
return -EOVERFLOW;
|
||||||
|
|
||||||
if (!sysctl_protected_hardlinks)
|
if (!sysctl_protected_hardlinks)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
inode = link->dentry->d_inode;
|
|
||||||
|
|
||||||
/* Source inode owner (or CAP_FOWNER) can hardlink all they like,
|
/* Source inode owner (or CAP_FOWNER) can hardlink all they like,
|
||||||
* otherwise, it must be a safe source.
|
* otherwise, it must be a safe source.
|
||||||
*/
|
*/
|
||||||
|
@ -2747,6 +2749,11 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
|
||||||
BUG_ON(!inode);
|
BUG_ON(!inode);
|
||||||
|
|
||||||
BUG_ON(victim->d_parent->d_inode != dir);
|
BUG_ON(victim->d_parent->d_inode != dir);
|
||||||
|
|
||||||
|
/* Inode writeback is not safe when the uid or gid are invalid. */
|
||||||
|
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
|
||||||
|
return -EOVERFLOW;
|
||||||
|
|
||||||
audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
|
audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
|
||||||
|
|
||||||
error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
||||||
|
@ -3675,7 +3682,8 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if ((S_ISCHR(mode) || S_ISBLK(mode)) && !capable(CAP_MKNOD))
|
if ((S_ISCHR(mode) || S_ISBLK(mode)) &&
|
||||||
|
!ns_capable(dentry->d_sb->s_user_ns, CAP_MKNOD))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
if (!dir->i_op->mknod)
|
if (!dir->i_op->mknod)
|
||||||
|
|
|
@ -1590,7 +1590,7 @@ static int do_umount(struct mount *mnt, int flags)
|
||||||
* Special case for "unmounting" root ...
|
* Special case for "unmounting" root ...
|
||||||
* we just try to remount it readonly.
|
* we just try to remount it readonly.
|
||||||
*/
|
*/
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
down_write(&sb->s_umount);
|
down_write(&sb->s_umount);
|
||||||
if (!sb_rdonly(sb))
|
if (!sb_rdonly(sb))
|
||||||
|
@ -2333,7 +2333,7 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
|
||||||
down_write(&sb->s_umount);
|
down_write(&sb->s_umount);
|
||||||
if (ms_flags & MS_BIND)
|
if (ms_flags & MS_BIND)
|
||||||
err = change_mount_flags(path->mnt, ms_flags);
|
err = change_mount_flags(path->mnt, ms_flags);
|
||||||
else if (!capable(CAP_SYS_ADMIN))
|
else if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
|
||||||
err = -EPERM;
|
err = -EPERM;
|
||||||
else
|
else
|
||||||
err = do_remount_sb(sb, sb_flags, data, 0);
|
err = do_remount_sb(sb, sb_flags, data, 0);
|
||||||
|
|
|
@ -919,6 +919,8 @@ int cap_bprm_set_creds(struct linux_binprm *bprm)
|
||||||
int cap_inode_setxattr(struct dentry *dentry, const char *name,
|
int cap_inode_setxattr(struct dentry *dentry, const char *name,
|
||||||
const void *value, size_t size, int flags)
|
const void *value, size_t size, int flags)
|
||||||
{
|
{
|
||||||
|
struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
|
||||||
|
|
||||||
/* Ignore non-security xattrs */
|
/* Ignore non-security xattrs */
|
||||||
if (strncmp(name, XATTR_SECURITY_PREFIX,
|
if (strncmp(name, XATTR_SECURITY_PREFIX,
|
||||||
sizeof(XATTR_SECURITY_PREFIX) - 1) != 0)
|
sizeof(XATTR_SECURITY_PREFIX) - 1) != 0)
|
||||||
|
@ -931,7 +933,7 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name,
|
||||||
if (strcmp(name, XATTR_NAME_CAPS) == 0)
|
if (strcmp(name, XATTR_NAME_CAPS) == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -949,6 +951,8 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name,
|
||||||
*/
|
*/
|
||||||
int cap_inode_removexattr(struct dentry *dentry, const char *name)
|
int cap_inode_removexattr(struct dentry *dentry, const char *name)
|
||||||
{
|
{
|
||||||
|
struct user_namespace *user_ns = dentry->d_sb->s_user_ns;
|
||||||
|
|
||||||
/* Ignore non-security xattrs */
|
/* Ignore non-security xattrs */
|
||||||
if (strncmp(name, XATTR_SECURITY_PREFIX,
|
if (strncmp(name, XATTR_SECURITY_PREFIX,
|
||||||
sizeof(XATTR_SECURITY_PREFIX) - 1) != 0)
|
sizeof(XATTR_SECURITY_PREFIX) - 1) != 0)
|
||||||
|
@ -964,7 +968,7 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,7 +200,8 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry,
|
||||||
int size;
|
int size;
|
||||||
bool ima_present = false;
|
bool ima_present = false;
|
||||||
|
|
||||||
if (!(inode->i_opflags & IOP_XATTR))
|
if (!(inode->i_opflags & IOP_XATTR) ||
|
||||||
|
inode->i_sb->s_user_ns != &init_user_ns)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
desc = init_desc(type);
|
desc = init_desc(type);
|
||||||
|
|
Loading…
Reference in New Issue