From: Josef Bacik <josef@toxicpanda.com>
To: Serge Hallyn <serge@hallyn.com>, Paul Moore <paul@paul-moore.com>,
Christian Brauner <brauner@kernel.org>
Cc: James Morris <jmorris@namei.org>,
David Howells <dhowells@redhat.com>,
Jarkko Sakkinen <jarkko@kernel.org>,
"Andrew G. Morgan" <morgan@kernel.org>,
Serge Hallyn <sergeh@kernel.org>,
linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org, keyrings@vger.kernel.org,
linux-fsdevel@vger.kernel.org, Shuah Khan <shuah@kernel.org>,
linux-kselftest@vger.kernel.org,
Bradley Morgan <brads@mainlining.org>,
Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH v9 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0
Date: Mon, 09 Nov 2026 19:49:09 +0000 [thread overview]
Message-ID: <20261109-b4-setfcap-userns-v9-0-0d3f00976ce2@toxicpanda.com> (raw)
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/
v5: https://lore.kernel.org/all/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com/
v6: https://lore.kernel.org/all/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com/
v7: https://lore.kernel.org/all/20261026-b4-setfcap-userns-v7-0-bf8a576f7259@toxicpanda.com/
v8: https://lore.kernel.org/all/20261027-b4-setfcap-userns-v8-0-2674adc63f3c@toxicpanda.com/
v8->v9:
- Dropped the setfcap_level patch and the checks on the uid_map opener
and writer and on file capabilities (old 1/6, 3/6 and 4/6). They only
stopped a task that gave up CAP_SETFCAP from using a namespace, a map
fd or a map that a task with CAP_SETFCAP set up for it, and that task
could have done it all on its own (Serge).
- setns() and ptrace compare kuids instead of levels: a task without
CAP_SETFCAP can't join a namespace whose tasks can write file
capabilities for the root user of its own namespace, and can't attach
to a task with CAP_SETFCAP in one unless it has CAP_SYS_PTRACE
(Serge). The check doesn't go by the task's uids, which it can move
away from 0 while keeping its other capabilities.
- The selftest drops the cases of the dropped patches and adds
PTRACE_TRACEME, a joiner that moved its euid, a target with
CAP_SETFCAP only in its bounding set, and a namespace without a
uid_map under one that maps host uid 0 at uid 5, 39 cases now.
v7->v8:
- 6/6 only, 1-5 are unchanged. A case whose setup failed in a child
waited for the harness timeout, because the parent kept its own copy
of the pipe end it read from. The cases where a privileged task maps
for one that gave up CAP_SETFCAP now share one helper that talks to
it over a socket pair, and the others close the ends they don't use,
so a failed step ends the case with a setup error at once (Sashiko).
v6->v7:
- 6/6 only, 1-5 are unchanged. Every case now reaches the step it is
named for, and a setup step that fails is an error rather than a
refusal. Cases that stopped at setns() are setns() cases now: a task
that joins has CAP_SETFCAP wherever the creator had it, so nothing
after setns() checks it. In the v3 rootid, nesting after dropping
CAP_SETFCAP and reverse idmapped mount cases a privileged task maps a
namespace created without CAP_SETFCAP, so the xattr gets written.
pidfd_getfd() is a ptrace case of its own. The session keyring case
joins a keyring of its own first, so it works under su, and skips
without keyrings (Sashiko).
v5->v6:
- New 6/6: a kselftest with every case the series has been through,
48 of them. Without the series 22 fail, with v5 the 4 below fail.
- 3/5: a task inside the namespace that is being mapped also has to
hold CAP_SETFCAP there to open or write a uid_map that maps an
ancestor's root user. Before, a task that gave up CAP_SETFCAP could
write "0 0 1" and get it back from execve() (Sashiko).
- 4/5: a v2 xattr on a filesystem whose namespace has no uid 0 is
refused with -EINVAL instead of stored unconverted, where it was
honoured nowhere. Falling through to the conversion would not do:
for a writer outside that namespace it names the writer's root user
instead (Sashiko).
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:
Since db2e718a4798 a root task that gave up CAP_SETFCAP can't map its
root user into a user namespace it creates. It can still join a
namespace that another root task created, or attach to a task in one,
and get CAP_SETFCAP over its root user that way. Patch 1 refuses
setns() when the namespace maps that root user, at any uid, or has no
uid_map yet and its tasks can still map it. Patch 2 refuses ptrace
attach to a task with CAP_SETFCAP in such a namespace, unless the
tracer has CAP_SYS_PTRACE. Containers that don't map host uid 0,
unprivileged users and anything with CAP_SETFCAP aren't affected.
Testing: 3/3 runs 39 cases on their own as root in private mount
namespaces. On vfs.all without the series the 11 setns() and ptrace
cases that must be refused fail; with the series all pass. The pidfd,
namespaces, mount_setattr and ptrace selftests give identical results
with and without.
--- 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 v9:
- Link to v8: https://patch.msgid.link/20261027-b4-setfcap-userns-v8-0-2674adc63f3c@toxicpanda.com
Changes in v8:
- Link to v7: https://patch.msgid.link/20261026-b4-setfcap-userns-v7-0-bf8a576f7259@toxicpanda.com
Changes in v7:
- Link to v6: https://patch.msgid.link/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com
Changes in v6:
- Link to v5: https://patch.msgid.link/20261023-b4-setfcap-userns-v5-0-0620223be951@toxicpanda.com
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 (3):
userns: don't let setns() lend CAP_SETFCAP
capabilities: don't let ptrace borrow CAP_SETFCAP
selftests/namespaces: test the CAP_SETFCAP rule for user namespaces
include/linux/capability.h | 3 +
kernel/user_namespace.c | 4 +
security/commoncap.c | 69 +-
tools/testing/selftests/namespaces/Makefile | 3 +-
tools/testing/selftests/namespaces/config | 2 +
tools/testing/selftests/namespaces/setfcap_test.c | 1210 +++++++++++++++++++++
6 files changed, 1286 insertions(+), 5 deletions(-)
---
base-commit: 2c3ba1f1cb416b81d617b92a24a4bcaa48d574e3
change-id: 20261006-b4-setfcap-userns-d63185a31ce3
next reply other threads:[~2026-11-09 19:49 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 19:49 Josef Bacik [this message]
2026-11-09 19:49 ` [PATCH v9 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-11-09 19:49 ` [PATCH v9 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-11-09 19:49 ` [PATCH v9 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109-b4-setfcap-userns-v9-0-0d3f00976ce2@toxicpanda.com \
--to=josef@toxicpanda.com \
--cc=brads@mainlining.org \
--cc=brauner@kernel.org \
--cc=dhowells@redhat.com \
--cc=jarkko@kernel.org \
--cc=jmorris@namei.org \
--cc=keyrings@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=morgan@kernel.org \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
--cc=sergeh@kernel.org \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®