Unverified Commit 0558c1bf authored by Christian Brauner's avatar Christian Brauner
Browse files

capability: handle idmapped mounts

In order to determine whether a caller holds privilege over a given
inode the capability framework exposes the two helpers
privileged_wrt_inode_uidgid() and capable_wrt_inode_uidgid(). The former
verifies that the inode has a mapping in the caller's user namespace and
the latter additionally verifies that the caller has the requested
capability in their current user namespace.
If the inode is accessed through an idmapped mount map it into the
mount's user namespace. Afterwards the checks are identical to
non-idmapped inodes. If the initial user namespace is passed all
operations are a nop so non-idmapped mounts will not see a change in
behavior.

Link: https://lore.kernel.org/r/20210121131959.646623-5-christian.brauner@ubuntu.com


Cc: Christoph Hellwig <hch@lst.de>
Cc: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Reviewed-by: default avatarJames Morris <jamorris@linux.microsoft.com>
Acked-by: default avatarSerge Hallyn <serge@hallyn.com>
Signed-off-by: default avatarChristian Brauner <christian.brauner@ubuntu.com>
parent 02f92b38
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ 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))
	if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
		return true;
	if (uid_eq(inode->i_uid, INVALID_UID) &&
	    ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
@@ -36,7 +36,7 @@ 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))
	if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN))
		return true;
	if (gid_eq(inode->i_gid, INVALID_GID) &&
	    ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
@@ -92,7 +92,7 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr)
		/* Also check the setgid bit! */
		if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid :
				inode->i_gid) &&
		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
		    !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
			attr->ia_mode &= ~S_ISGID;
	}

@@ -193,7 +193,7 @@ void setattr_copy(struct inode *inode, const struct iattr *attr)
		umode_t mode = attr->ia_mode;

		if (!in_group_p(inode->i_gid) &&
		    !capable_wrt_inode_uidgid(inode, CAP_FSETID))
		    !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID))
			mode &= ~S_ISGID;
		inode->i_mode = mode;
	}
+2 −1
Original line number Diff line number Diff line
@@ -1411,7 +1411,8 @@ void would_dump(struct linux_binprm *bprm, struct file *file)
		/* 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, inode))
		       !privileged_wrt_inode_uidgid(user_ns, &init_user_ns,
						    inode))
			user_ns = user_ns->parent;

		if (old != user_ns) {
+2 −1
Original line number Diff line number Diff line
@@ -2146,7 +2146,8 @@ void inode_init_owner(struct inode *inode, const struct inode *dir,
			mode |= S_ISGID;
		else if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP) &&
			 !in_group_p(inode->i_gid) &&
			 !capable_wrt_inode_uidgid(dir, CAP_FSETID))
			 !capable_wrt_inode_uidgid(&init_user_ns, dir,
						   CAP_FSETID))
			mode &= ~S_ISGID;
	} else
		inode->i_gid = current_fsgid();
+8 −5
Original line number Diff line number Diff line
@@ -357,10 +357,11 @@ int generic_permission(struct inode *inode, int mask)
	if (S_ISDIR(inode->i_mode)) {
		/* DACs are overridable for directories */
		if (!(mask & MAY_WRITE))
			if (capable_wrt_inode_uidgid(inode,
			if (capable_wrt_inode_uidgid(&init_user_ns, inode,
						     CAP_DAC_READ_SEARCH))
				return 0;
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
		if (capable_wrt_inode_uidgid(&init_user_ns, inode,
					     CAP_DAC_OVERRIDE))
			return 0;
		return -EACCES;
	}
@@ -370,7 +371,8 @@ int generic_permission(struct inode *inode, int mask)
	 */
	mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
	if (mask == MAY_READ)
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
		if (capable_wrt_inode_uidgid(&init_user_ns, inode,
					     CAP_DAC_READ_SEARCH))
			return 0;
	/*
	 * Read/write DACs are always overridable.
@@ -378,7 +380,8 @@ int generic_permission(struct inode *inode, int mask)
	 * at least one exec bit set.
	 */
	if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
		if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
		if (capable_wrt_inode_uidgid(&init_user_ns, inode,
					     CAP_DAC_OVERRIDE))
			return 0;

	return -EACCES;
@@ -2659,7 +2662,7 @@ int __check_sticky(struct inode *dir, struct inode *inode)
		return 0;
	if (uid_eq(dir->i_uid, fsuid))
		return 0;
	return !capable_wrt_inode_uidgid(inode, CAP_FOWNER);
	return !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FOWNER);
}
EXPORT_SYMBOL(__check_sticky);

+1 −1
Original line number Diff line number Diff line
@@ -1017,7 +1017,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler,
	if (unlikely(inode->i_mode & S_ISGID) &&
	    handler->flags == ACL_TYPE_ACCESS &&
	    !in_group_p(inode->i_gid) &&
	    !capable_wrt_inode_uidgid(inode, CAP_FSETID)) {
	    !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) {
		struct iattr iattr = { .ia_valid = ATTR_KILL_SGID };

		err = ovl_setattr(dentry, &iattr);
Loading