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