Commit 80a9d85b authored by Seth Forshee's avatar Seth Forshee

fs: Allow superblock owner to change ownership of inodes with unmappable ids

In a userns mount some on-disk inodes may have ids which do not
map into s_user_ns, in which case the in-kernel inodes are owned
by invalid users. The superblock owner should be able to change
attributes of these inodes but cannot. However it is unsafe to
grant the superblock owner privileged access to all inodes in the
superblock since proc, sysfs, etc. use DAC to protect files which
may not belong to s_user_ns. The problem is restricted to only
inodes where the owner or group is an invalid user.

We can work around this by allowing users with CAP_CHOWN in
s_user_ns to change an invalid owner or group id, so long as the
other id is either invalid or mappable in s_user_ns. After
changing ownership the user will be privileged towards the inode
and thus able to change other attributes.

As an precaution, checks for invalid ids are added to the proc
and kernfs setattr interfaces. These filesystems are not expected
to have inodes with invalid ids, but if it does happen any
setattr operations will return -EPERM.
Signed-off-by: default avatarSeth Forshee <seth.forshee@canonical.com>
Acked-by: default avatarSerge Hallyn <serge.hallyn@canonical.com>
parent a6dfa2eb
......@@ -16,6 +16,58 @@
#include <linux/evm.h>
#include <linux/ima.h>
static bool chown_ok(const struct inode *inode, kuid_t uid)
{
struct user_namespace *user_ns;
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;
/*
* Inode uids/gids are of type kuid_t/kgid_t. As such, they can be
* a) INVALID_UID/INVALID_GID, b) valid and mappable into
* i_sb->s_user_ns, or c) valid but not mappable into
* i_sb->s_user_ns.
*
* For filesystems on user-supplied media ids will either be (a) or
* (b), so we permit CAP_CHOWN in s_user_ns to change INVALID_UID if
* the gid meets these conditions (and vice versa for INVALID_GID).
*
* For psuedo-filesystems like proc or sysfs ids will be either (b)
* or (c), so these conditions do not permit namespace-root to chown
* in those filesystems.
*/
user_ns = inode->i_sb->s_user_ns;
if (!uid_valid(inode->i_uid) &&
(!gid_valid(inode->i_gid) || kgid_has_mapping(user_ns, inode->i_gid)) &&
ns_capable(user_ns, CAP_CHOWN))
return true;
return false;
}
static bool chgrp_ok(const struct inode *inode, kgid_t gid)
{
struct user_namespace *user_ns;
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;
/* Logic here is the same as in chown_ok(); see comment there. */
user_ns = inode->i_sb->s_user_ns;
if (!gid_valid(inode->i_gid) &&
(!uid_valid(inode->i_uid) || kuid_has_mapping(user_ns, inode->i_uid)) &&
ns_capable(user_ns, CAP_CHOWN))
return true;
return false;
}
/**
* inode_change_ok - check if attribute changes to an inode are allowed
* @inode: inode to check
......@@ -58,17 +110,11 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
return 0;
/* Make sure a caller can chown. */
if ((ia_valid & ATTR_UID) &&
(!uid_eq(current_fsuid(), inode->i_uid) ||
!uid_eq(attr->ia_uid, inode->i_uid)) &&
!capable_wrt_inode_uidgid(inode, CAP_CHOWN))
if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
return -EPERM;
/* Make sure caller can chgrp. */
if ((ia_valid & ATTR_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))
if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid))
return -EPERM;
/* Make sure a caller can chmod. */
......
......@@ -117,6 +117,8 @@ int kernfs_iop_setattr(struct dentry *dentry, struct iattr *iattr)
if (!kn)
return -EINVAL;
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
return -EPERM;
mutex_lock(&kernfs_mutex);
error = inode_change_ok(inode, iattr);
......
......@@ -711,6 +711,8 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr)
if (attr->ia_valid & ATTR_MODE)
return -EPERM;
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
return -EPERM;
error = inode_change_ok(inode, attr);
if (error)
......
......@@ -105,6 +105,9 @@ static int proc_notify_change(struct dentry *dentry, struct iattr *iattr)
struct proc_dir_entry *de = PDE(inode);
int error;
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
return -EPERM;
error = inode_change_ok(inode, iattr);
if (error)
return error;
......
......@@ -752,6 +752,8 @@ static int proc_sys_setattr(struct dentry *dentry, struct iattr *attr)
if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))
return -EPERM;
if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid))
return -EPERM;
error = inode_change_ok(inode, attr);
if (error)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment