* [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0
@ 2026-10-23 16:30 Josef Bacik
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
` (4 more replies)
0 siblings, 5 replies; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Bradley Morgan, Josef Bacik
v1: https://lore.kernel.org/all/20261006-b4-setfcap-userns-v1-0-f47e7ed66072@toxicpanda.com/
v2: https://lore.kernel.org/all/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com/
v3: https://lore.kernel.org/all/20261021-b4-setfcap-userns-v3-0-97c7ed4e43e1@toxicpanda.com/
v4: https://lore.kernel.org/all/20261022-b4-setfcap-userns-v4-0-88aa0ffaa08f@toxicpanda.com/
v4->v5:
- 3/5: the uid_map check only looked for an extent at the parent's uid
0. The root user of an ancestor can sit at any uid in the parent, so
a task without CAP_SETFCAP could map "0 1000 1" under a "1000 0 1"
and get host uid 0 as its uid 0 unchecked. It now finds every
ancestor root user the map contains. 4/5 already refused the file
capabilities after such a map, so this is the map rule itself
(Sashiko). Dropped Bradley's Reviewed-by on 3/5, which changed.
v3->v4:
- 4/5: a v2 security.capability xattr skipped the new checks, it was
written as is whenever the writer had CAP_SETFCAP over the
filesystem's namespace. It now only does that when the filesystem's
root user is within reach, and goes through the v3 conversion and its
checks otherwise (Sashiko).
v2->v3:
- The setns() check in 2/5 and the ptrace check in 5/5 only apply to
namespaces that map the root user of an ancestor within the creator's
CAP_SETFCAP, or that have no uid_map yet. Root users stored through
an idmapped mount are left to whoever sets up the mount, so a root
task without CAP_SETFCAP can join an ordinary "0 100000" container
again (Serge, Christian).
- 2/5 adds cred_may_setfcap_userns() for those two checks and
cred_setfcap_reaches() for the uid_map check; nothing outside
create_user_ns() calls cap_setfcap_level() directly (Serge).
- Fixed the cap_setfcap_level() kernel-doc, @ns doesn't have to be the
cred's namespace or below; cap_capable() is compared with != 0
(Serge).
- Picked up Bradley's Reviewed-by on 1/5 and 3/5, not on 2/5, which
changed.
- Rebased onto current vfs.all.
v1->v2:
- The state lives on the user namespace now: ns->setfcap_level replaces
ns->parent_could_setfcap, and cred->setfcap_level is gone (Serge,
Christian).
- New 2/5: setns() refuses a task whose CAP_SETFCAP doesn't reach as far
up as the namespace's setfcap_level. It applies to every user
namespace, not only to those that map the parent's uid 0, because of
idmapped mounts (Serge, Christian).
- 5/5 asks a tracer for what setns() asks for, also when the namespace
doesn't map an ancestor's root user, again because of idmapped mounts.
- Dropped "userns: don't let setns() lend the right to map uid 0", the
setns() check covers it. Its nested case moved into 3/5, which checks
the opener and the writer.
- set_cred_user_ns() isn't touched anymore, so the put-before-use in
userns_install() is gone (Sashiko).
- Rebased onto current vfs.all.
What it does now:
Every user namespace gets a setfcap_level when it's created, which is
how far up the creator's CAP_SETFCAP counted. If its tasks can write
file capabilities for the root user of an ancestor, because the
namespace maps that root user or can still map it, setns() only lets in
tasks whose own CAP_SETFCAP reaches that far, and PTRACE_ATTACH asks the
tracer for the same. The checks on the way to file capabilities then go
by the namespace instead of the cred: a uid_map write that maps the
parent's uid 0 (opener and writer), and setting security.capability.
The visible difference from v1 is the setns() restriction. A task
without CAP_SETFCAP can't join, or attach to tasks in, a namespace that
maps the root user of an ancestor, such as host uid 0, and was created
by a task that had it. A task that joins takes on the namespace's
reach. Containers that don't map host uid 0, unprivileged users and
anything with CAP_SETFCAP aren't affected.
Testing: the v1 flows plus the setns() and ptrace cases, 41 in all, on
vfs.all with and without the series. For v4 also the v2 xattr route: a
task that unshared without CAP_SETFCAP mounts a tmpfs, writes a v2 xattr
on it, and an overlay mounted from the initial namespace makes a uid
1000 user run the file with CAP_SYS_ADMIN. That works without the
series and with v3, and gets -EPERM with v4. For v5 the "0 1000 1"
under "1000 0 1" map: allowed without the series and with v4, -EPERM
with v5. Every flow gives what's expected, and the only ones that
changed from v2 are the five idmapped mount and "0 100000" container
cases, which are allowed again. The pidfd, namespaces, mount_setattr
and ptrace selftests give identical results with and without. The TLA+
model, which has no idmapped mounts, finds no violation with the
narrower checks, and still finds the setns() bypass when the check is
taken out.
--- Original email (v1) ---
Hello,
Commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
stops a root task that has given up CAP_SETFCAP from creating a user
namespace that maps uid 0 and then writing file capabilities in it that
the initial namespace honours. The check only looks at the task that
creates the namespace, so if somebody who did have CAP_SETFCAP created
one, there are still several ways around it:
- setns() into their namespace and write uid_map from inside (patch 2)
- write "0 0 1" through a uid_map fd that they opened (patch 3)
- setns() into a namespace of theirs that already maps uid 0 and set
security.capability there, no map write needed (patch 4)
- ptrace one of their tasks and have it do any of the above (patch 5)
On an unmodified kernel we took a uid 0 task with CAP_SETFCAP dropped
from its permitted, effective and bounding sets and, through each of
these, ended up with a file that a uid 1000 user execs with
CAP_SYS_ADMIN in its effective set.
Patch 1 adds cred->setfcap_level, which records how far up the
namespace tree a task's CAP_SETFCAP reached when it entered its
namespace, and patches 2-5 check it. Patch 4 is the check that closes
the class, the map patches make the uid 0 map rule mean what
db2e718a4798 meant it to, and patch 5 keeps ptrace from borrowing what
the target is entitled to.
This does change behaviour. Everything new is -EPERM:
- a task that entered a namespace without CAP_SETFCAP outside can't map
uid 0 of the outside or write fscaps for that root user anymore
- a uid_map fd opened by a task with CAP_SETFCAP can't be used by a
task without it to map uid 0
- if a privileged task maps "0 0 1" from the parent for a namespace
created by a task without CAP_SETFCAP, that namespace can no longer
write fscaps honoured outside
- PTRACE_ATTACH and PTRACE_TRACEME fail when the tracer gave up
CAP_SETFCAP and CAP_SYS_PTRACE, the target didn't, and they share a
root user
Rootless containers, privileged runtimes writing the map from the
parent, nested unprivileged namespaces and containers that don't map
host uid 0 aren't affected. The ptrace check is one compare for
targets in the initial namespace and in namespaces entered without
the capability.
Testing: a set of flows run on the base and patched kernels, every
bypass route above gets -EPERM with the series and the 16 legitimate
flows behave the same. The capabilities, namespaces, ptrace, pidfd and
proc selftests give the same results before and after. Thanks,
Josef
---
Changes in v5:
- Link to v4: https://patch.msgid.link/20261022-b4-setfcap-userns-v4-0-88aa0ffaa08f@toxicpanda.com
Changes in v4:
- Link to v3: https://patch.msgid.link/20261021-b4-setfcap-userns-v3-0-97c7ed4e43e1@toxicpanda.com
Changes in v3:
- Link to v2: https://patch.msgid.link/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com
Changes in v2:
- Link to v1: https://patch.msgid.link/20261006-b4-setfcap-userns-v1-0-f47e7ed66072@toxicpanda.com
---
Josef Bacik (5):
userns: record how far up CAP_SETFCAP reaches
userns: don't let setns() lend CAP_SETFCAP
userns: check the opener and the writer before mapping uid 0
capabilities: limit fscaps to where CAP_SETFCAP reaches
capabilities: don't let ptrace borrow CAP_SETFCAP
include/linux/capability.h | 6 ++
include/linux/user_namespace.h | 7 +-
kernel/user_namespace.c | 90 ++++++++++++++------
security/commoncap.c | 183 +++++++++++++++++++++++++++++++++++++++--
4 files changed, 252 insertions(+), 34 deletions(-)
---
base-commit: 2c3ba1f1cb416b81d617b92a24a4bcaa48d574e3
change-id: 20261006-b4-setfcap-userns-d63185a31ce3
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
@ 2026-10-23 16:30 ` Josef Bacik
2026-10-23 16:44 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
` (3 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Bradley Morgan, Josef Bacik
create_user_ns() records in ns->parent_could_setfcap whether the creator
had CAP_SETFCAP in its effective set, and verify_root_map() trusts that
when a task in the new namespace maps the parent's uid 0. A bool only
describes the parent, though. File capabilities belong to the kuid of a
root user, and a namespace that maps the parent's uid 0 shares that kuid
with the parent and possibly with ancestors further up, so what matters
is how far up the creator's CAP_SETFCAP counted.
Replace the bool with ns->setfcap_level: CAP_SETFCAP held in the
namespace counts there and in its ancestors up to the one at that
->level. cap_setfcap_level() computes it from the creator's cred. A
creator that holds CAP_SETFCAP passes on the setfcap_level of its own
namespace, one that doesn't gets the new namespace's own level, and the
initial namespace has 0.
verify_root_map() keeps checking the same condition: the bool was false
exactly when the level is the namespace's own.
No functional change.
Reviewed-by: Bradley Morgan <brads@mainlining.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/capability.h | 3 +++
include/linux/user_namespace.h | 7 ++++---
kernel/user_namespace.c | 4 ++--
security/commoncap.c | 27 +++++++++++++++++++++++++++
4 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/include/linux/capability.h b/include/linux/capability.h
index 622137f66f09..026974a5111b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -34,6 +34,7 @@ struct cpu_vfs_cap_data {
#define _USER_CAP_HEADER_SIZE (sizeof(struct __user_cap_header_struct))
#define _KERNEL_CAP_T_SIZE (sizeof(kernel_cap_t))
+struct cred;
struct file;
struct inode;
struct dentry;
@@ -222,4 +223,6 @@ int get_vfs_caps_from_disk(const struct mnt_idmap *idmap,
int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
const void **ivalue, size_t size);
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns);
+
#endif /* !_LINUX_CAPABILITY_H */
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 91232053775d..f3753b6b6d29 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -86,9 +86,10 @@ struct user_namespace {
kgid_t group;
struct ns_common ns;
unsigned long flags;
- /* parent_could_setfcap: true if the creator if this ns had CAP_SETFCAP
- * in its effective capability set at the child ns creation time. */
- bool parent_could_setfcap;
+ /* setfcap_level: CAP_SETFCAP held in this ns counts here and in the
+ * ancestors up to the one at this ->level, see cap_setfcap_level().
+ */
+ int setfcap_level;
#ifdef CONFIG_KEYS
/* List of joinable keyrings in this namespace. Modification access of
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 50c374ed4687..a5a94367600d 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -125,7 +125,7 @@ int create_user_ns(struct cred *new)
if (!ns)
goto fail_dec;
- ns->parent_could_setfcap = cap_raised(new->cap_effective, CAP_SETFCAP);
+ ns->setfcap_level = cap_setfcap_level(new, parent_ns);
ret = ns_common_init(ns);
if (ret)
@@ -921,7 +921,7 @@ static bool verify_root_map(const struct file *file,
* the new namespace. Verify that the parent had CAP_SETFCAP
* when it unshared.
* */
- if (!file_ns->parent_could_setfcap)
+ if (file_ns->setfcap_level >= file_ns->level)
return false;
} else {
/* Process p1 is writing to uid_map of p2, who is in a child
diff --git a/security/commoncap.c b/security/commoncap.c
index d47ab3022343..4f49dba65426 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -131,6 +131,33 @@ int cap_capable(const struct cred *cred, struct user_namespace *target_ns,
return ret;
}
+/**
+ * cap_setfcap_level - Determine how far up CAP_SETFCAP of a cred reaches
+ * @cred: The credentials to use
+ * @ns: The user namespace to look from
+ *
+ * File capabilities belong to the kuid of a namespace's root user, and the
+ * same kuid can be the root user of ancestors of that namespace. Every task
+ * gets CAP_SETFCAP when it enters a user namespace, so having it there says
+ * nothing about those ancestors. user_ns->setfcap_level does: it is taken
+ * from the creator when the namespace is created.
+ *
+ * Return: the ->level of the topmost namespace, from @ns upwards, for which
+ * CAP_SETFCAP of @cred counts; @ns->level + 1 if it doesn't even count over
+ * @ns, which includes an @ns that isn't @cred's namespace or a descendant.
+ */
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
+{
+ if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT) != 0)
+ return ns->level + 1;
+
+ if (cap_raised(cred->cap_effective, CAP_SETFCAP))
+ return cred->user_ns->setfcap_level;
+
+ /* All we have is that we own a child of our namespace. */
+ return cred->user_ns->level + 1;
+}
+
/**
* cap_settime - Determine whether the current process may set the system clock
* @ts: The time to set
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-23 16:30 ` Josef Bacik
2026-10-23 16:46 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
` (2 subsequent siblings)
4 siblings, 1 reply; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Josef Bacik
Mapping uid 0 of the parent into a user namespace requires CAP_SETFCAP,
because a task in such a namespace can write file capabilities that are
honoured in the parent. A task that is already in the namespace has a
full capability set there and can't be asked any more, so
verify_root_map() goes by what the creator had, now ns->setfcap_level.
But setns() lets other tasks in, and they get the full set as well:
task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
unshare(CLONE_NEWUSER)
setns(A's user ns)
allowed, euid == ns->owner
write "0 0 1" to /proc/self/uid_map
ns->setfcap_level is A's -> allowed
or, if A's namespace maps uid 0 already, B sets security.capability on a
file. Either way B ends up with file capabilities that are honoured in
the initial namespace, which it could not have written there, nor in a
namespace it created itself.
Refuse setns() into a user namespace whose tasks can write file
capabilities for the root user of an ancestor that the joiner's
CAP_SETFCAP doesn't reach. That is the case when the namespace maps
such a root user, or has no uid map yet and its parent maps one.
cred_may_setfcap_userns() checks it, and cred_setfcap_reaches() is the
comparison it and the later checks use. Then every task in such a
namespace is entitled to what its setfcap_level says, and the checks on
the way to file capabilities can go by the namespace.
An idmapped mount can store a root user that the namespace doesn't map,
for example a "0 100000" container whose rootfs maps 100000 to host uid
0. That is a deliberate setup and up to whoever creates the mount, so
it is not considered here.
The change in behaviour is that a task without CAP_SETFCAP gets -EPERM
when it tries to setns() into a user namespace that maps the root user
of an ancestor, and was created by a task whose CAP_SETFCAP reached that
far. Containers that don't map host uid 0, tasks with CAP_SETFCAP, and
unprivileged users joining their own namespaces are not affected.
Link: https://lore.kernel.org/all/20261021-bioenergie-allheilmittel-undurchdringlichen-35666eb62cff@brauner/
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
include/linux/capability.h | 3 +++
kernel/user_namespace.c | 6 +++++
security/commoncap.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+)
diff --git a/include/linux/capability.h b/include/linux/capability.h
index 026974a5111b..e4199b03668b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -224,5 +224,8 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
const void **ivalue, size_t size);
int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns);
+bool cred_setfcap_reaches(const struct cred *cred, struct user_namespace *ns,
+ int level);
+bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns);
#endif /* !_LINUX_CAPABILITY_H */
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index a5a94367600d..7bd3692c2f3e 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -1390,6 +1390,12 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
return -EPERM;
+ /* Tasks in the namespace may have CAP_SETFCAP that counts outside of
+ * it, so only let in tasks whose own counts as far.
+ */
+ if (!cred_may_setfcap_userns(current_cred(), user_ns))
+ return -EPERM;
+
cred = nsset_cred(nsset);
if (!cred)
return -EINVAL;
diff --git a/security/commoncap.c b/security/commoncap.c
index 4f49dba65426..8a9d998b1897 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -158,6 +158,64 @@ int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
return cred->user_ns->level + 1;
}
+/**
+ * cred_setfcap_reaches - Check how far up CAP_SETFCAP of a cred reaches
+ * @cred: The credentials to use
+ * @ns: The user namespace to look from
+ * @level: The ->level of the ancestor of @ns it has to count in
+ *
+ * Return: true if CAP_SETFCAP of @cred counts in @ns and in its ancestors
+ * up to the one at @level.
+ */
+bool cred_setfcap_reaches(const struct cred *cred, struct user_namespace *ns,
+ int level)
+{
+ return cap_setfcap_level(cred, ns) <= level;
+}
+
+/*
+ * The topmost ancestor of @ns whose root user tasks in @ns can write file
+ * capabilities for: within @ns->setfcap_level, and mapped into @ns, or into
+ * its parent while @ns has no uid_map yet. NULL if there is none. An
+ * idmapped mount can store other root users, but that is up to whoever sets
+ * up the mount.
+ */
+static struct user_namespace *setfcap_top(struct user_namespace *ns)
+{
+ struct user_namespace *seen, *p, *top = NULL;
+
+ if (ns->setfcap_level >= ns->level)
+ return NULL;
+
+ seen = READ_ONCE(ns->uid_map.nr_extents) ? ns : ns->parent;
+ for (p = ns->parent; p; p = p->parent) {
+ if (p->level < ns->setfcap_level)
+ break;
+ if (kuid_has_mapping(seen, make_kuid(p, 0)))
+ top = p;
+ }
+
+ return top;
+}
+
+/**
+ * cred_may_setfcap_userns - Check whether a cred covers what a userns can do
+ * @cred: The credentials to use
+ * @ns: The user namespace
+ *
+ * Tasks in @ns can have CAP_SETFCAP that counts in ancestors of @ns, see
+ * cap_setfcap_level(). Check that CAP_SETFCAP of @cred reaches as far up as
+ * tasks in @ns can make use of theirs.
+ *
+ * Return: true if it does.
+ */
+bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns)
+{
+ struct user_namespace *top = setfcap_top(ns);
+
+ return !top || cred_setfcap_reaches(cred, ns->parent, top->level);
+}
+
/**
* cap_settime - Determine whether the current process may set the system clock
* @ts: The time to set
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:30 ` [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-23 16:30 ` Josef Bacik
2026-10-23 17:03 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:30 ` [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
4 siblings, 1 reply; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Josef Bacik
verify_root_map() looks at the cred that opened the map file: it asks
whether it has CAP_SETFCAP over the parent, or, for an opener inside the
namespace, whether the creator had. Two things are missing.
The writer isn't checked. For every other privileged mapping
new_idmap_permitted() wants the capability from the opener and from the
task that calls write(), but a map that consists of the single line
"0 0 1" takes the unprivileged branch, where only the opener's euid
counts. So an open uid_map file is a token for mapping uid 0:
task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
fd = open("/proc/<pid>/uid_map")
B gets fd by inheritance
or SCM_RIGHTS
write(fd, "0 0 1")
opener had CAP_SETFCAP -> allowed
And the check only happens for a map with an extent that starts at the
parent's uid 0, and only asks for CAP_SETFCAP over the parent. But the
root user of an ancestor can be mapped into the parent at any uid, and
the parent's own root user can be the root user of ancestors further up.
A task without CAP_SETFCAP that unshared a namespace, which a privileged
task then mapped "1000 0 1" for, can unshare again and map "0 1000 1":
host uid 0 is uid 0 of the new namespace, and nothing was checked.
Find every ancestor whose root user the new map contains, at whatever uid
it has in the parent, and require that the opener and the writer both
reach the topmost of them: by ns->setfcap_level for a task in the
namespace that is being mapped, by cap_setfcap_level() for one outside
it.
This only affects maps that contain the root user of the parent or of
one of its ancestors. A write by a task without CAP_SETFCAP to a file
opened by one with it, and a map by a task whose CAP_SETFCAP stops short
of the root user it passes on, now fail with -EPERM.
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
kernel/user_namespace.c | 82 ++++++++++++++++++++++++++++++++++++-------------
1 file changed, 60 insertions(+), 22 deletions(-)
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 7bd3692c2f3e..661d7e9b72d5 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -881,15 +881,55 @@ VISIBLE_IF_KUNIT int uid_gid_map_sort(struct uid_gid_map *map)
}
EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort);
+static bool map_has_id(struct uid_gid_map *map, u32 id)
+{
+ struct uid_gid_extent *extent;
+ int idx;
+
+ for (idx = 0; idx < map->nr_extents; idx++) {
+ if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
+ extent = &map->extent[idx];
+ else
+ extent = &map->forward[idx];
+ if (id >= extent->lower_first &&
+ id - extent->lower_first < extent->count)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * The lowest ->level among @parent and its ancestors whose root user @new_map
+ * maps, whatever uid that root user has in @parent; INT_MAX if there is none.
+ */
+static int map_root_level(struct user_namespace *parent,
+ struct uid_gid_map *new_map)
+{
+ struct user_namespace *p;
+ int level = INT_MAX;
+ uid_t id;
+
+ for (p = parent; p; p = p->parent) {
+ id = from_kuid(parent, make_kuid(p, 0));
+ if (id != (uid_t)-1 && map_has_id(new_map, id))
+ level = p->level;
+ }
+
+ return level;
+}
+
/**
* verify_root_map() - check the uid 0 mapping
* @file: idmapping file
* @map_ns: user namespace of the target process
* @new_map: requested idmap
*
- * If a process requests mapping parent uid 0 into the new ns, verify that the
- * process writing the map had the CAP_SETFCAP capability as the target process
- * will be able to write fscaps that are valid in ancestor user namespaces.
+ * If a process requests mapping the root user of the parent ns, or of one of
+ * its ancestors, into the new ns, verify that the process that opened the map
+ * file and the process writing the map had the CAP_SETFCAP capability as the
+ * target process will be able to write fscaps that are valid in ancestor user
+ * namespaces.
*
* Return: true if the mapping is allowed, false if not.
*/
@@ -897,42 +937,40 @@ static bool verify_root_map(const struct file *file,
struct user_namespace *map_ns,
struct uid_gid_map *new_map)
{
- int idx;
const struct user_namespace *file_ns = file->f_cred->user_ns;
- struct uid_gid_extent *extent0 = NULL;
-
- for (idx = 0; idx < new_map->nr_extents; idx++) {
- if (new_map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
- extent0 = &new_map->extent[idx];
- else
- extent0 = &new_map->forward[idx];
- if (extent0->lower_first == 0)
- break;
-
- extent0 = NULL;
- }
+ int level;
- if (!extent0)
+ level = map_root_level(map_ns->parent, new_map);
+ if (level == INT_MAX)
return true;
if (map_ns == file_ns) {
/* The process unshared its ns and is writing to its own
* /proc/self/uid_map. User already has full capabilites in
* the new namespace. Verify that the parent had CAP_SETFCAP
- * when it unshared.
+ * when it unshared, as far up as those root users go.
* */
- if (file_ns->setfcap_level >= file_ns->level)
+ if (file_ns->setfcap_level > level)
return false;
} else {
/* Process p1 is writing to uid_map of p2, who is in a child
* user namespace to p1's. Verify that the opener of the map
* file has CAP_SETFCAP against the parent of the new map
- * namespace */
- if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP))
+ * namespace, and as far up as those root users go.
+ */
+ if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
+ !cred_setfcap_reaches(file->f_cred, map_ns->parent, level))
return false;
}
- return true;
+ /* The file may have been handed to someone else since it was opened,
+ * so the same goes for the process that is doing the write.
+ */
+ if (map_ns == current_user_ns())
+ return map_ns->setfcap_level <= level;
+
+ return ns_capable(map_ns->parent, CAP_SETFCAP) &&
+ cred_setfcap_reaches(current_cred(), map_ns->parent, level);
}
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
` (2 preceding siblings ...)
2026-10-23 16:30 ` [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-23 16:30 ` Josef Bacik
2026-10-23 16:56 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
4 siblings, 1 reply; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Josef Bacik
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
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
` (3 preceding siblings ...)
2026-10-23 16:30 ` [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-23 16:30 ` Josef Bacik
2026-10-23 16:47 ` sashiko-bot
4 siblings, 1 reply; 12+ messages in thread
From: Josef Bacik @ 2026-10-23 16:30 UTC (permalink / raw)
To: Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, linux-kernel, keyrings,
linux-fsdevel, Josef Bacik
Tasks in a user namespace created by a task with CAP_SETFCAP may map uid
0 of the parent and write file capabilities that are honoured there.
That is the one privilege they keep outside of their namespace. setns()
no longer lets anybody in who doesn't have it, but the ptrace checks
don't know about it: for a target in another user namespace
cap_ptrace_access_check() is satisfied with CAP_SYS_PTRACE over the
target's namespace, which the owner rule hands to any task with the
right euid.
task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
unshare(CLONE_NEWUSER)
ptrace(PTRACE_ATTACH, A)
uids match, euid == ns->owner
make A write "0 0 1" to its uid_map,
or set file capabilities
A is entitled -> allowed
The same goes for /proc/<pid>/mem, process_vm_writev() and pidfd_getfd().
Require for PTRACE_MODE_ATTACH and PTRACE_TRACEME what setns() requires
to get into the target's namespace: CAP_SETFCAP of the tracer has to
reach the root users of ancestors that tasks in the namespace can write
file capabilities for, or the tracer has CAP_SYS_PTRACE over the topmost
of those ancestors, which gives control of tasks there that have
CAP_SETFCAP anyway. As in setns(), root users stored through an
idmapped mount are not considered. A tracer in the target's namespace
got in with as much and always passes.
Targets in the initial namespace, in namespaces created without
CAP_SETFCAP (what unprivileged users create) and in namespaces that
don't map the root user of an ancestor (the usual container) are not
affected. What remains is a tracer that gave up CAP_SETFCAP and
CAP_SYS_PTRACE and a target in a namespace that maps the root user of
an ancestor and was created by a task that didn't give it up, which the
tracer can't setns() into either. PTRACE_MODE_READ is unchanged.
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
security/commoncap.c | 45 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 41 insertions(+), 4 deletions(-)
diff --git a/security/commoncap.c b/security/commoncap.c
index 3551ce9b046a..bb126aba8736 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -254,14 +254,41 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz)
return 0;
}
+/*
+ * Tasks in a user namespace have CAP_SETFCAP that can count in ancestors of
+ * the namespace, see cap_setfcap_level(). That is a privilege outside of the
+ * namespace, so capabilities over the namespace are not enough to take
+ * control of such a task.
+ */
+static bool cap_covers_setfcap(const struct cred *cred,
+ const struct cred *child_cred)
+{
+ struct user_namespace *ns = child_cred->user_ns, *top;
+
+ /* setns() only lets in tasks whose CAP_SETFCAP reaches as far. */
+ if (cred->user_ns == ns)
+ return true;
+
+ top = setfcap_top(ns);
+ if (!top)
+ return true;
+
+ /* CAP_SYS_PTRACE up there gives control of tasks with CAP_SETFCAP. */
+ return cred_setfcap_reaches(cred, ns->parent, top->level) ||
+ !cap_capable(cred, top, CAP_SYS_PTRACE, CAP_OPT_NOAUDIT);
+}
+
/**
* cap_ptrace_access_check - Determine whether the current process may access
* another
* @child: The process to be accessed
* @mode: The mode of attachment.
*
- * If we are in the same or an ancestor user_ns and have all the target
- * task's capabilities, then ptrace access is allowed.
+ * For PTRACE_MODE_ATTACH, if the target task may make use of CAP_SETFCAP in
+ * an ancestor of its user_ns that our CAP_SETFCAP or CAP_SYS_PTRACE doesn't
+ * reach, then ptrace access is denied.
+ * Otherwise, if we are in the same or an ancestor user_ns and have all the
+ * target task's capabilities, then ptrace access is allowed.
* If we have the ptrace capability to the target user_ns, then ptrace
* access is allowed.
* Else denied.
@@ -282,11 +309,15 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
caller_caps = &cred->cap_effective;
else
caller_caps = &cred->cap_permitted;
+ if ((mode & PTRACE_MODE_ATTACH) &&
+ !cap_covers_setfcap(cred, child_cred))
+ goto deny;
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, *caller_caps))
goto out;
if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
+deny:
ret = -EPERM;
out:
rcu_read_unlock();
@@ -297,8 +328,11 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
* cap_ptrace_traceme - Determine whether another process may trace the current
* @parent: The task proposed to be the tracer
*
- * If parent is in the same or an ancestor user_ns and has all current's
- * capabilities, then ptrace access is allowed.
+ * If current may make use of CAP_SETFCAP in an ancestor of its user_ns that
+ * parent's CAP_SETFCAP or CAP_SYS_PTRACE doesn't reach, then ptrace access
+ * is denied.
+ * Otherwise, if parent is in the same or an ancestor user_ns and has all
+ * current's capabilities, then ptrace access is allowed.
* If parent has the ptrace capability to current's user_ns, then ptrace
* access is allowed.
* Else denied.
@@ -314,11 +348,14 @@ int cap_ptrace_traceme(struct task_struct *parent)
rcu_read_lock();
cred = __task_cred(parent);
child_cred = current_cred();
+ if (!cap_covers_setfcap(cred, child_cred))
+ goto deny;
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, cred->cap_permitted))
goto out;
if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
+deny:
ret = -EPERM;
out:
rcu_read_unlock();
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread
* Re: [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-23 16:44 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-23 16:44 UTC (permalink / raw)
Cc: linux-security-module
> create_user_ns() records in ns->parent_could_setfcap whether the creator
> had CAP_SETFCAP in its effective set, and verify_root_map() trusts that
> when a task in the new namespace maps the parent's uid 0. A bool only
> describes the parent, though. File capabilities belong to the kuid of a
> root user, and a namespace that maps the parent's uid 0 shares that kuid
> with the parent and possibly with ancestors further up, so what matters
> is how far up the creator's CAP_SETFCAP counted.
>
> Replace the bool with ns->setfcap_level: CAP_SETFCAP held in the
> namespace counts there and in its ancestors up to the one at that
> ->level. cap_setfcap_level() computes it from the creator's cred. A
> creator that holds CAP_SETFCAP passes on the setfcap_level of its own
> namespace, one that doesn't gets the new namespace's own level, and the
> initial namespace has 0.
>
> verify_root_map() keeps checking the same condition: the bool was false
> exactly when the level is the namespace's own.
>
> No functional change.
>
> Reviewed-by: Bradley Morgan <brads@mainlining.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP
2026-10-23 16:30 ` [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-23 16:46 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-23 16:46 UTC (permalink / raw)
Cc: linux-security-module
> Mapping uid 0 of the parent into a user namespace requires CAP_SETFCAP,
> because a task in such a namespace can write file capabilities that are
> honoured in the parent. A task that is already in the namespace has a
> full capability set there and can't be asked any more, so
> verify_root_map() goes by what the creator had, now ns->setfcap_level.
>
> But setns() lets other tasks in, and they get the full set as well:
>
> task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
> unshare(CLONE_NEWUSER)
> setns(A's user ns)
> allowed, euid == ns->owner
> write "0 0 1" to /proc/self/uid_map
> ns->setfcap_level is A's -> allowed
>
> [ ... ]
>
> Link: https://lore.kernel.org/all/20261021-bioenergie-allheilmittel-undurchdringlichen-35666eb62cff@brauner/
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=2
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP
2026-10-23 16:30 ` [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-10-23 16:47 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-23 16:47 UTC (permalink / raw)
Cc: linux-security-module
> Tasks in a user namespace created by a task with CAP_SETFCAP may map uid
> 0 of the parent and write file capabilities that are honoured there.
> That is the one privilege they keep outside of their namespace. setns()
> no longer lets anybody in who doesn't have it, but the ptrace checks
> don't know about it: for a target in another user namespace
> cap_ptrace_access_check() is satisfied with CAP_SYS_PTRACE over the
> target's namespace, which the owner rule hands to any task with the
> right euid.
>
> task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP
> unshare(CLONE_NEWUSER)
> ptrace(PTRACE_ATTACH, A)
> uids match, euid == ns->owner
> make A write "0 0 1" to its uid_map,
> or set file capabilities
> [ ... ]
> tracer can't setns() into either. PTRACE_MODE_READ is unchanged.
>
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=5
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
2026-10-23 16:30 ` [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-23 16:56 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-10-23 16:56 UTC (permalink / raw)
To: Josef Bacik, Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, keyrings
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] capabilities: unconverted v2 fscap bypass in cap_convert_nscap()
--
commit e902e2977ca7e209d89cc64e5a8e3a585eddb980
Author: Josef Bacik <josef@toxicpanda.com>
capabilities: limit fscaps to where CAP_SETFCAP reaches
This commit restricts writing file capabilities in cap_convert_nscap() so
that the root user named by a v2 or v3 security.capability xattr (both as
seen through an idmapped mount and on the underlying filesystem) cannot be
uid 0 in an ancestor user namespace beyond task_ns->setfcap_level.
> diff --git a/security/commoncap.c b/security/commoncap.c
> index 8a9d998b18972..3551ce9b046ad 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;
[Severity: High]
What happens in cap_convert_nscap() if uid 0 is not mapped in fs_ns when
writing a v2 capability xattr (size == XATTR_CAPS_SZ_2) on a non-idmapped
mount (idmap == &nop_mnt_idmap)?
Unlike the v3 conversion path further down in cap_convert_nscap(), which
checks vfsuid_valid(vfsrootid) and uid_valid(rootid) before calling
cap_root_level(), this v2 fast-path passes make_kuid(fs_ns, 0) directly to
cap_root_level() without checking uid_valid().
When uid 0 is not mapped in fs_ns, make_kuid(fs_ns, 0) returns INVALID_UID.
In cap_root_level(INVALID_UID, task_ns), from_kuid(ns, INVALID_UID) returns
(uid_t)-1 for every namespace up to init_user_ns, so cap_root_level() returns
INT_MAX. Because INT_MAX >= task_ns->setfcap_level is always true,
cap_convert_nscap() returns size and writes the unconverted v2 xattr.
For example, if a task without CAP_SETFCAP unshares a user namespace
(setting setfcap_level to 1) and maps only a non-zero UID (or creates an
unmapped child user namespace and mounts a tmpfs in it), make_kuid(fs_ns, 0)
is INVALID_UID. Wouldn't that allow the task to write an unconverted v2
security.capability xattr on the tmpfs inode, which could then be honoured
through an overlay mounted in init_user_ns, or honoured if fs_ns->uid_map is
later written to map uid 0? And when task_ns == fs_ns has no uid 0 mapping,
writing a v2 xattr writes an unconverted v2 xattr instead of falling through
to rootid_from_xattr() and failing with -EINVAL.
Should the v2 check verify uid_valid(make_kuid(fs_ns, 0)) (or should
cap_root_level() reject !uid_valid(kuid)) before allowing the unconverted v2
xattr to be written?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-23 16:30 ` [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-23 17:03 ` sashiko-bot
2026-10-23 18:42 ` Jarkko Sakkinen
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-10-23 17:03 UTC (permalink / raw)
To: Josef Bacik, Serge Hallyn, Paul Moore, Christian Brauner
Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
Serge Hallyn, linux-security-module, keyrings
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] userns: missing CAP_SETFCAP check in verify_root_map() for tasks in map_ns
--
commit 715d4f4455aa94fb0eaba838f8aa378efb3afe54
Author: Josef Bacik <josef@toxicpanda.com>
userns: check the opener and the writer before mapping uid 0
This commit updates verify_root_map() to find the topmost ancestor whose root
user is included in a new uid_map and checks that both the file opener and the
writer hold CAP_SETFCAP reaching that ancestor's level.
> diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
> index 7bd3692c2f3eb..661d7e9b72d56 100644
> --- a/kernel/user_namespace.c
> +++ b/kernel/user_namespace.c
[ ... ]
> @@ -897,42 +937,40 @@ static bool verify_root_map(const struct file *file,
> struct user_namespace *map_ns,
> struct uid_gid_map *new_map)
> {
> - int idx;
> const struct user_namespace *file_ns = file->f_cred->user_ns;
> - struct uid_gid_extent *extent0 = NULL;
> -
> - for (idx = 0; idx < new_map->nr_extents; idx++) {
> - if (new_map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
> - extent0 = &new_map->extent[idx];
> - else
> - extent0 = &new_map->forward[idx];
> - if (extent0->lower_first == 0)
> - break;
> -
> - extent0 = NULL;
> - }
> + int level;
>
> - if (!extent0)
> + level = map_root_level(map_ns->parent, new_map);
> + if (level == INT_MAX)
> return true;
>
> if (map_ns == file_ns) {
> /* The process unshared its ns and is writing to its own
> * /proc/self/uid_map. User already has full capabilites in
> * the new namespace. Verify that the parent had CAP_SETFCAP
> - * when it unshared.
> + * when it unshared, as far up as those root users go.
> * */
> - if (file_ns->setfcap_level >= file_ns->level)
> + if (file_ns->setfcap_level > level)
> return false;
> } else {
> /* Process p1 is writing to uid_map of p2, who is in a child
> * user namespace to p1's. Verify that the opener of the map
> * file has CAP_SETFCAP against the parent of the new map
> - * namespace */
> - if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP))
> + * namespace, and as far up as those root users go.
> + */
> + if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
> + !cred_setfcap_reaches(file->f_cred, map_ns->parent, level))
> return false;
> }
>
> - return true;
> + /* The file may have been handed to someone else since it was opened,
> + * so the same goes for the process that is doing the write.
> + */
> + if (map_ns == current_user_ns())
> + return map_ns->setfcap_level <= level;
> +
> + return ns_capable(map_ns->parent, CAP_SETFCAP) &&
> + cred_setfcap_reaches(current_cred(), map_ns->parent, level);
> }
[Severity: High]
When a task is in map_ns (so map_ns == file_ns for the opener or
map_ns == current_user_ns() for the writer), does checking only
map_ns->setfcap_level <= level allow a task that has dropped CAP_SETFCAP in
map_ns to still map the ancestor root user?
For example, if a process running as uid 0 with CAP_SETFCAP in init_user_ns
calls unshare(CLONE_NEWUSER), create_user_ns() sets map_ns->setfcap_level to 0.
Inside map_ns (before uid_map is written), two cases can occur:
1. Task A opens /proc/self/uid_map and passes the descriptor (by fork or
SCM_RIGHTS) to Task B in map_ns, which has dropped CAP_SETFCAP and all
other capabilities via capset().
2. A task in map_ns drops CAP_SETFCAP via capset() while keeping
CAP_SYS_ADMIN, and then opens /proc/self/uid_map itself.
When the task without CAP_SETFCAP writes "0 0 1" through:
proc_uid_map_write()
-> map_write()
-> new_idmap_permitted()
-> verify_root_map()
map_write() only checks file_ns_capable(file, map_ns, CAP_SYS_ADMIN) on the
opener, and new_idmap_permitted() takes the single-extent unprivileged branch
which only checks uid_eq(ns->owner, cred->euid):
kernel/user_namespace.c:new_idmap_permitted() {
if (cap_setid == CAP_SETUID && !verify_root_map(file, ns, new_map))
return false;
if ((new_map->nr_extents == 1) && (new_map->extent[0].count == 1) &&
uid_eq(ns->owner, cred->euid)) {
u32 id = new_map->extent[0].lower_first;
if (cap_setid == CAP_SETUID) {
kuid_t uid = make_kuid(ns->parent, id);
if (uid_eq(uid, cred->euid))
return true;
}
}
Because verify_root_map() only checks map_ns->setfcap_level <= level when
map_ns == file_ns and when map_ns == current_user_ns(), the write succeeds
even though the task dropped CAP_SETFCAP. Once "0 0 1" is installed in
map_ns->uid_map, make_kuid(map_ns, 0) becomes KUIDT_INIT(0) instead of
INVALID_UID, allowing the task in map_ns to call execve() and regain full
capabilities (including CAP_SETFCAP) via handle_privileged_root() in
security/commoncap.c.
Note that cap_setfcap_level() in security/commoncap.c already handles a cred
whose user_ns is the target namespace by checking whether CAP_SETFCAP is still
raised in cred->cap_effective before returning cred->user_ns->setfcap_level:
security/commoncap.c:cap_setfcap_level() {
if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT) != 0)
return ns->level + 1;
if (cap_raised(cred->cap_effective, CAP_SETFCAP))
return cred->user_ns->setfcap_level;
/* All we have is that we own a child of our namespace. */
return cred->user_ns->level + 1;
}
When map_ns == file_ns (or map_ns == current_user_ns()), should
verify_root_map() check file_ns_capable(file, map_ns, CAP_SETFCAP) and
cred_setfcap_reaches(file->f_cred, map_ns, level) for the opener, and
ns_capable(map_ns, CAP_SETFCAP) and
cred_setfcap_reaches(current_cred(), map_ns, level) for the writer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-23 17:03 ` sashiko-bot
@ 2026-10-23 18:42 ` Jarkko Sakkinen
0 siblings, 0 replies; 12+ messages in thread
From: Jarkko Sakkinen @ 2026-10-23 18:42 UTC (permalink / raw)
To: sashiko-reviews
Cc: Josef Bacik, Serge Hallyn, Paul Moore, Christian Brauner,
James Morris, David Howells, Andrew G. Morgan, Serge Hallyn,
linux-security-module, keyrings
On Fri, Oct 23, 2026 at 05:03:57PM +0000, sashiko-bot@kernel.org wrote:
> Sashiko AI review · https://sashiko.dev/#/patchset/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com?part=3
Interesting. For my patch set Sashiko is lacking the full cc list
(has only linux-security-module) but this came cc'd even directly
to me. Any ideas what might be the problem?
Br, Jarkko
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-10-23 18:42 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 16:30 [PATCH v5 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-23 16:30 ` [PATCH v5 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:44 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-23 16:46 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-23 17:03 ` sashiko-bot
2026-10-23 18:42 ` Jarkko Sakkinen
2026-10-23 16:30 ` [PATCH v5 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-23 16:56 ` sashiko-bot
2026-10-23 16:30 ` [PATCH v5 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-10-23 16:47 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox