* [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0
@ 2026-10-14 20:47 Josef Bacik
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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
v1: https://lore.kernel.org/all/20261006-b4-setfcap-userns-v1-0-f47e7ed66072@toxicpanda.com/
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. setns() only lets in
tasks whose own CAP_SETFCAP reaches at least that far, so everybody
inside a namespace is entitled to the same thing, and the checks go by
the namespace instead of the cred. The checks are in the same places as
v1: a uid_map write that maps the parent's uid 0 (opener and writer),
setting security.capability (both the root user seen through the mount
and the one stored on disk, for idmapped mounts), and PTRACE_MODE_ATTACH
and PTRACE_TRACEME.
The visible difference from v1 is the setns() restriction. A task
without CAP_SETFCAP can't join a namespace created by a task that had
it, including an ordinary "0 100000" container, where in v1 it could
join and was only refused the map, fscaps and ptrace. PTRACE_ATTACH
follows the same rule, so it can't attach to the tasks inside either.
A task that joins takes on the namespace's reach, so a privileged task
that joins a namespace created without CAP_SETFCAP can't write fscaps
for a root user above it either. Unprivileged users joining their own
namespaces, and anything with CAP_SETFCAP, aren't affected.
Testing: the v1 flows plus the new setns() and ptrace cases, 41 in
all, on vfs.all with and without the series, and every one comes out as
expected, including the idmapped mount ones. The pidfd, namespaces,
mount_setattr and ptrace selftests give identical results with and
without. We also redid the TLA+ model the series came out of with the
state on the namespace. Without the setns() check it finds a joiner
writing "0 0 1" through the creator's uid_map fd, with it there's no
violation.
--- 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 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 | 4 ++
include/linux/user_namespace.h | 7 +--
kernel/user_namespace.c | 38 ++++++++++----
security/commoncap.c | 115 +++++++++++++++++++++++++++++++++++++++--
4 files changed, 148 insertions(+), 16 deletions(-)
---
base-commit: ac311bf4f20e614d8e94cae4c62f075f2e5ef16c
change-id: 20261006-b4-setfcap-userns-d63185a31ce3
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
@ 2026-10-14 20:47 ` Josef Bacik
2026-10-14 20:54 ` sashiko-bot
2026-10-14 20:55 ` Bradley Morgan
2026-10-14 20:47 ` [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
` (3 subsequent siblings)
4 siblings, 2 replies; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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
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.
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 | 26 ++++++++++++++++++++++++++
4 files changed, 35 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..a51b9d51d96d 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -131,6 +131,32 @@ 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 of @cred or one of its descendants
+ *
+ * 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 over @ns.
+ */
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
+{
+ if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT))
+ 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] 14+ messages in thread
* [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-14 20:47 ` Josef Bacik
2026-10-14 20:54 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
` (2 subsequent siblings)
4 siblings, 2 replies; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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 unless the joiner's CAP_SETFCAP
reaches at least as far up as the namespace's setfcap_level. Then every
task in a namespace is entitled to what its setfcap_level says, and the
checks on the way to file capabilities can go by the namespace. This
has to apply to every user namespace, also to those that don't map the
parent's uid 0: through an idmapped mount, a root user that only exists
in the namespace can be stored as the parent's.
The change in behaviour is that a task without CAP_SETFCAP gets -EPERM
when it tries to setns() into a user namespace created by a task that
had it, such as a root-created container. Tasks with CAP_SETFCAP, and
unprivileged users joining their own namespaces, are not affected.
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 | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index a5a94367600d..330085a7d7d4 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -1390,6 +1390,13 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
if (!ns_capable(user_ns, CAP_SYS_ADMIN))
return -EPERM;
+ /* Everybody in the namespace gets CAP_SETFCAP that reaches as far up
+ * as its setfcap_level, so only let in tasks whose own reaches as far.
+ */
+ if (cap_setfcap_level(current_cred(), user_ns->parent) >
+ user_ns->setfcap_level)
+ return -EPERM;
+
cred = nsset_cred(nsset);
if (!cred)
return -EINVAL;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-14 20:47 ` [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-14 20:47 ` Josef Bacik
2026-10-14 20:57 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-14 20:47 ` [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
4 siblings, 2 replies; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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 CAP_SETFCAP over the parent isn't always enough. The parent may map
its own parent's uid 0, so its root user is the root user of ancestors
further up, and the capability has to count there too. A task without
CAP_SETFCAP that unshared a namespace, which a privileged task then
mapped "0 0 1" for, has CAP_SETFCAP in it. It could unshare again and
pass on uid 0 of the initial namespace one level further down.
Find the topmost namespace that shares the parent's root user with
cap_root_level(), and require that the opener and the writer both reach
that far: 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 uid 0 of the parent namespace. 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>
---
include/linux/capability.h | 1 +
kernel/user_namespace.c | 29 +++++++++++++++++++++--------
security/commoncap.c | 23 +++++++++++++++++++++++
3 files changed, 45 insertions(+), 8 deletions(-)
diff --git a/include/linux/capability.h b/include/linux/capability.h
index 026974a5111b..90f9f976d216 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -224,5 +224,6 @@ 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);
+int cap_root_level(kuid_t kuid, struct user_namespace *ns);
#endif /* !_LINUX_CAPABILITY_H */
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 330085a7d7d4..7dba6594516d 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -888,8 +888,9 @@ EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort);
* @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.
+ * 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,7 +898,7 @@ static bool verify_root_map(const struct file *file,
struct user_namespace *map_ns,
struct uid_gid_map *new_map)
{
- int idx;
+ int idx, level;
const struct user_namespace *file_ns = file->f_cred->user_ns;
struct uid_gid_extent *extent0 = NULL;
@@ -915,24 +916,36 @@ static bool verify_root_map(const struct file *file,
if (!extent0)
return true;
+ /* The parent may in turn share its root user with its ancestors. */
+ level = cap_root_level(make_kuid(map_ns->parent, 0), map_ns->parent);
+
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 that root user goes.
* */
- 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 that root user goes.
+ */
+ if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
+ cap_setfcap_level(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) &&
+ cap_setfcap_level(current_cred(), map_ns->parent) <= level;
}
/*
diff --git a/security/commoncap.c b/security/commoncap.c
index a51b9d51d96d..f52f1c7a87be 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -157,6 +157,29 @@ int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
return cred->user_ns->level + 1;
}
+/**
+ * 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.
+ */
+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
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
` (2 preceding siblings ...)
2026-10-14 20:47 ` [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-14 20:47 ` Josef Bacik
2026-10-14 20:56 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
4 siblings, 1 reply; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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.
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 | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/security/commoncap.c b/security/commoncap.c
index f52f1c7a87be..2cae4354aeae 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -644,10 +644,24 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
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] 14+ messages in thread
* [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
` (3 preceding siblings ...)
2026-10-14 20:47 ` [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-14 20:47 ` Josef Bacik
2026-10-14 20:55 ` sashiko-bot
4 siblings, 1 reply; 14+ messages in thread
From: Josef Bacik @ 2026-10-14 20:47 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 as far up as the namespace's setfcap_level, or the tracer has
CAP_SYS_PTRACE over the ancestor at that level, which gives control of
tasks there that have CAP_SETFCAP anyway. Whether the namespace maps
the root user of that ancestor doesn't matter, for the same reason as in
setns(): through an idmapped mount its tasks can store file capabilities
for it all the same. A tracer in the target's namespace got in with as
much and always passes.
Targets in the initial namespace and in namespaces created without
CAP_SETFCAP (what unprivileged users create) are not affected. What
remains is a tracer that gave up CAP_SETFCAP and CAP_SYS_PTRACE and a
target in a namespace created by a task that didn't, such as a
root-created container, 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 | 52 ++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 48 insertions(+), 4 deletions(-)
diff --git a/security/commoncap.c b/security/commoncap.c
index 2cae4354aeae..83cb5c9c3a25 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -195,14 +195,48 @@ 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;
+
+ if (ns->setfcap_level >= ns->level)
+ return true;
+
+ /* setns() only lets in tasks whose CAP_SETFCAP reaches as far. */
+ if (cred->user_ns == ns)
+ return true;
+
+ /*
+ * Whether the namespace maps the root user of an ancestor doesn't
+ * matter: through an idmapped mount its tasks can store file
+ * capabilities for it anyway. So ask for as much as setns() does.
+ */
+ for (top = ns->parent; top->level > ns->setfcap_level; top = top->parent)
+ ;
+
+ /* CAP_SYS_PTRACE up there gives control of tasks with CAP_SETFCAP. */
+ return cap_setfcap_level(cred, ns->parent) <= ns->setfcap_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.
@@ -223,11 +257,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();
@@ -238,8 +276,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.
@@ -255,11 +296,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] 14+ messages in thread
* Re: [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-14 20:54 ` sashiko-bot
2026-10-14 20:55 ` Bradley Morgan
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-14 20:54 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.
>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP
2026-10-14 20:47 ` [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-14 20:54 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
1 sibling, 0 replies; 14+ messages in thread
From: Bradley Morgan @ 2026-10-14 20:54 UTC (permalink / raw)
To: josef
Cc: brauner, dhowells, jarkko, jmorris, keyrings, linux-fsdevel,
linux-kernel, linux-security-module, morgan, paul, serge, sergeh
On 14 October 2026 21:47:47 BST, Josef Bacik <josef@toxicpanda.com> wrote:
>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 unless the joiner's CAP_SETFCAP
>reaches at least as far up as the namespace's setfcap_level. Then every
>task in a namespace is entitled to what its setfcap_level says, and the
>checks on the way to file capabilities can go by the namespace. This
>has to apply to every user namespace, also to those that don't map the
>parent's uid 0: through an idmapped mount, a root user that only exists
>in the namespace can be stored as the parent's.
>
>The change in behaviour is that a task without CAP_SETFCAP gets -EPERM
>when it tries to setns() into a user namespace created by a task that
>had it, such as a root-created container. Tasks with CAP_SETFCAP, and
>unprivileged users joining their own namespaces, are not affected.
Gnarly.
>
>Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
>Assisted-by: LLM
Ok.
>Signed-off-by: Josef Bacik <josef@toxicpanda.com>
>---
> kernel/user_namespace.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
>diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
>index a5a94367600d..330085a7d7d4 100644
>--- a/kernel/user_namespace.c
>+++ b/kernel/user_namespace.c
>@@ -1390,6 +1390,13 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
> if (!ns_capable(user_ns, CAP_SYS_ADMIN))
> return -EPERM;
>
>+ /* Everybody in the namespace gets CAP_SETFCAP that reaches as far up
>+ * as its setfcap_level, so only let in tasks whose own reaches as far.
>+ */
Wow!
>+ if (cap_setfcap_level(current_cred(), user_ns->parent) >
>+ user_ns->setfcap_level)
>+ return -EPERM;
>+
Clean! Please add
Reviewed-by: Bradley Morgan <brads@mainlining.org>
> cred = nsset_cred(nsset);
> if (!cred)
> return -EINVAL;
>
>
--- Thanks!
"I'm not a very positive person" - Linus torvalds
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-14 20:54 ` sashiko-bot
@ 2026-10-14 20:55 ` Bradley Morgan
1 sibling, 0 replies; 14+ messages in thread
From: Bradley Morgan @ 2026-10-14 20:55 UTC (permalink / raw)
To: josef
Cc: brauner, dhowells, jarkko, jmorris, keyrings, linux-fsdevel,
linux-kernel, linux-security-module, morgan, paul, serge, sergeh
On 14 October 2026 21:47:46 BST, Josef Bacik <josef@toxicpanda.com> wrote:
>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.
>
>Assisted-by: LLM
Hi! I mean, I'm not a expert at this side of the kernel, but on the userns
side of things, it looks fine and *sane* enough to me (famous last words!
:d)
Reviewed-by: Bradley Morgan <brads@mainlining.org>
>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 | 26 ++++++++++++++++++++++++++
> 4 files changed, 35 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..a51b9d51d96d 100644
>--- a/security/commoncap.c
>+++ b/security/commoncap.c
>@@ -131,6 +131,32 @@ 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 of @cred or one of its descendants
>+ *
>+ * 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 over @ns.
>+ */
>+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
>+{
>+ if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT))
>+ 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
>
>
--- Thanks!
"I'm not a very positive person" - Linus torvalds
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP
2026-10-14 20:47 ` [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-10-14 20:55 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-14 20:55 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
> [ ... ]
> 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. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com?part=5
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches
2026-10-14 20:47 ` [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-14 20:56 ` sashiko-bot
0 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-14 20:56 UTC (permalink / raw)
Cc: linux-security-module
> 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:
> [ ... ]
> 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com?part=4
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-14 20:47 ` [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-14 20:57 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
1 sibling, 0 replies; 14+ messages in thread
From: Bradley Morgan @ 2026-10-14 20:57 UTC (permalink / raw)
To: josef
Cc: brauner, dhowells, jarkko, jmorris, keyrings, linux-fsdevel,
linux-kernel, linux-security-module, morgan, paul, serge, sergeh
On 14 October 2026 21:47:48 BST, Josef Bacik <josef@toxicpanda.com> wrote:
>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 CAP_SETFCAP over the parent isn't always enough. The parent may map
>its own parent's uid 0, so its root user is the root user of ancestors
>further up, and the capability has to count there too. A task without
>CAP_SETFCAP that unshared a namespace, which a privileged task then
>mapped "0 0 1" for, has CAP_SETFCAP in it. It could unshare again and
>pass on uid 0 of the initial namespace one level further down.
>
>Find the topmost namespace that shares the parent's root user with
>cap_root_level(), and require that the opener and the writer both reach
>that far: 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 uid 0 of the parent namespace. 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.
Ok. Look. I feel there will be nits on this exact review. But I review
more based on "is this code correct? ", because the users are my priority
right now until I learn maintainers opinions or liking.
Reviewed-by: Bradley Morgan <brads@mainlining.org>
>
>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 | 1 +
> kernel/user_namespace.c | 29 +++++++++++++++++++++--------
> security/commoncap.c | 23 +++++++++++++++++++++++
> 3 files changed, 45 insertions(+), 8 deletions(-)
>
>diff --git a/include/linux/capability.h b/include/linux/capability.h
>index 026974a5111b..90f9f976d216 100644
>--- a/include/linux/capability.h
>+++ b/include/linux/capability.h
>@@ -224,5 +224,6 @@ 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);
>+int cap_root_level(kuid_t kuid, struct user_namespace *ns);
>
> #endif /* !_LINUX_CAPABILITY_H */
>diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
>index 330085a7d7d4..7dba6594516d 100644
>--- a/kernel/user_namespace.c
>+++ b/kernel/user_namespace.c
>@@ -888,8 +888,9 @@ EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort);
> * @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.
>+ * 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,7 +898,7 @@ static bool verify_root_map(const struct file *file,
> struct user_namespace *map_ns,
> struct uid_gid_map *new_map)
> {
>- int idx;
>+ int idx, level;
> const struct user_namespace *file_ns = file->f_cred->user_ns;
> struct uid_gid_extent *extent0 = NULL;
>
>@@ -915,24 +916,36 @@ static bool verify_root_map(const struct file *file,
> if (!extent0)
> return true;
>
>+ /* The parent may in turn share its root user with its ancestors. */
>+ level = cap_root_level(make_kuid(map_ns->parent, 0), map_ns->parent);
>+
> 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 that root user goes.
> * */
>- 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 that root user goes.
>+ */
>+ if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
>+ cap_setfcap_level(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) &&
>+ cap_setfcap_level(current_cred(), map_ns->parent) <= level;
> }
>
> /*
>diff --git a/security/commoncap.c b/security/commoncap.c
>index a51b9d51d96d..f52f1c7a87be 100644
>--- a/security/commoncap.c
>+++ b/security/commoncap.c
>@@ -157,6 +157,29 @@ int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
> return cred->user_ns->level + 1;
> }
>
>+/**
>+ * 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.
>+ */
>+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
>
>
--- Thanks!
"I'm not a very positive person" - Linus torvalds
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0
2026-10-14 20:47 ` [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-14 20:57 ` Bradley Morgan
@ 2026-10-14 20:58 ` sashiko-bot
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-14 20:58 UTC (permalink / raw)
Cc: linux-security-module
> 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")
> [ ... ]
> 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com?part=3
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP
2026-10-14 20:47 ` [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-14 20:54 ` Bradley Morgan
@ 2026-10-14 20:58 ` sashiko-bot
1 sibling, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-14 20:58 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
>
> [ ... ]
> unprivileged users joining their own namespaces, are not affected.
>
> 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. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-b4-setfcap-userns-v2-0-fac4d19281d7@toxicpanda.com?part=2
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-14 20:58 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 20:47 [PATCH v2 0/5] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-14 20:47 ` [PATCH v2 1/5] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-14 20:54 ` sashiko-bot
2026-10-14 20:55 ` Bradley Morgan
2026-10-14 20:47 ` [PATCH v2 2/5] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-14 20:54 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 3/5] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-14 20:57 ` Bradley Morgan
2026-10-14 20:58 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 4/5] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-14 20:56 ` sashiko-bot
2026-10-14 20:47 ` [PATCH v2 5/5] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-10-14 20:55 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox