[PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
flat view
WARM1d
From: Josef Bacik <josef@toxicpanda.com>
Date: 2026-10-23 16:30:37
Also in:
keyrings, linux-fsdevel, lkml
Subsystem:
capabilities, security subsystem, the rest · Maintainers:
Serge Hallyn, Paul Moore, James Morris, Linus Torvalds
Revision v5 of 5 in this series.
Revisions (5)
Commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
keeps a root process without CAP_SETFCAP from creating a user namespace
that maps uid 0, in which it could attach capabilities to a file that
are then honoured outside. But the map doesn't have to be written by
that process. A privileged task can write it from the parent, and the
file capabilities are not checked against the outside at all:
task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
unshare(CLONE_NEWUSER)
ns->setfcap_level = 1
write "0 0 1" to B's uid_map, gid_map
A has CAP_SETFCAP -> allowed
setxattr(file, "security.capability")
rootid = kuid 0
uid 1000 in the initial namespace:
execve(file)
file capabilities apply
The map doesn't even have to show uid 0 of the parent as uid 0, since a
v3 xattr can name any mapped uid as the root user.
Check at the place where it matters, in cap_convert_nscap(). On an
idmapped mount the root user named in the xattr has two identities: the
kuid seen through this mount, and the kuid that is stored and seen
through every other mount of the filesystem. If either of them is the
root user of an ancestor of the caller's namespace too, CAP_SETFCAP has
to reach up to there, which ns->setfcap_level tells.
A v2 xattr skips the conversion when the writer has CAP_SETFCAP over the
filesystem's namespace, and belongs to that namespace's root user. It
gets the same check: if that root user is out of reach, the xattr goes
through the conversion instead, and is refused there. Otherwise a task
that unshared without CAP_SETFCAP could mount a tmpfs in its namespace,
write a v2 xattr on it, and have it honoured through an overlay mounted
from the initial namespace.
Tasks in the initial namespace, tasks in namespaces created by a task
with CAP_SETFCAP, and file capabilities for a root user that only exists
in namespaces within reach are not affected. That includes everything
an unprivileged user does in its own namespaces. The rest gets -EPERM.
Fixes: 8db6c34f1dbc ("Introduce v3 namespaced file capabilities")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
security/commoncap.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 49 insertions(+), 4 deletions(-)
diff --git a/security/commoncap.c b/security/commoncap.c
index 8a9d998b1897..3551ce9b046a 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c@@ -216,6 +216,29 @@ bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns) return !top || cred_setfcap_reaches(cred, ns->parent, top->level); } +/** + * cap_root_level - Find the topmost namespace in which a kuid is the root user + * @kuid: The kuid to look for + * @ns: The user namespace to start from + * + * Return: the lowest ->level among @ns and its ancestors in which @kuid is + * uid 0, which is how far up file capabilities with that root user are + * honoured; INT_MAX if there is no such namespace. + */ +static int cap_root_level(kuid_t kuid, struct user_namespace *ns) +{ + int level = INT_MAX; + + for (;; ns = ns->parent) { + if (from_kuid(ns, kuid) == 0) + level = ns->level; + if (ns == &init_user_ns) + break; + } + + return level; +} + /** * cap_settime - Determine whether the current process may set the system clock * @ts: The time to set
@@ -671,19 +694,41 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry, return -EINVAL; if (!capable_wrt_inode_uidgid(idmap, inode, CAP_SETFCAP)) return -EPERM; - if (size == XATTR_CAPS_SZ_2 && (idmap == &nop_mnt_idmap)) - if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP)) - /* user is privileged, just write the v2 */ - return size; + /* + * A v2 xattr belongs to the root user of the filesystem's namespace, + * which may be the root user of ancestors of our namespace as well. + * Only write it unconverted if that root user is within reach, the + * checks below handle everything else. + */ + if (size == XATTR_CAPS_SZ_2 && idmap == &nop_mnt_idmap && + ns_capable(fs_ns, CAP_SETFCAP) && + cap_root_level(make_kuid(fs_ns, 0), task_ns) >= + task_ns->setfcap_level) + /* user is privileged, just write the v2 */ + return size; vfsrootid = rootid_from_xattr(*ivalue, size, task_ns); if (!vfsuid_valid(vfsrootid)) return -EINVAL; + /* + * The root user may be the root user of ancestors of our namespace as + * well. CAP_SETFCAP that we got for entering it doesn't cover those. + * On an idmapped mount the root user is vfsrootid as seen through + * this mount and rootid as seen through every other mount of the + * filesystem, so both have to stay within reach. + */ + if (cap_root_level(vfsuid_into_kuid(vfsrootid), task_ns) < + task_ns->setfcap_level) + return -EPERM; + rootid = from_vfsuid(idmap, fs_ns, vfsrootid); if (!uid_valid(rootid)) return -EINVAL; + if (cap_root_level(rootid, task_ns) < task_ns->setfcap_level) + return -EPERM; + nsrootid = from_kuid(fs_ns, rootid); if (nsrootid == -1) return -EINVAL;
--
2.55.0