All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v10 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0
@ 2026-11-12 22:08 Josef Bacik
  2026-11-12 22:08 ` [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Josef Bacik @ 2026-11-12 22:08 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, Shuah Khan, linux-kselftest, 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/
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/
v9: https://lore.kernel.org/all/20261109-b4-setfcap-userns-v9-0-0d3f00976ce2@toxicpanda.com/

v9->v10:
- 1/3: a namespace with no uid_map yet only counts if its owner is the
  joiner's root user, the only one who could map it from inside, so
  tasks that can't map it aren't refused.  The changelog says who gets refused now and
  what the check leaves to whoever sets up a map or an idmapped mount.
- 2/3: the ptrace check no longer looks at the target's capabilities.
  A target that gave up CAP_SETFCAP can still get it back from its
  inheritable set on execve(), take it from another task of its
  namespace if it kept CAP_SYS_PTRACE, or write "0 0 1" to its
  namespace's uid_map while there is none yet.  A tracer without
  CAP_SETFCAP and CAP_SYS_PTRACE can't attach to a task in a namespace
  that setns() keeps it out of (Sashiko).  CAP_SYS_PTRACE no longer
  stands in for CAP_SETFCAP, here or in 1/3: whether it lets a task take
  CAP_SETFCAP from a neighbour depends on LSMs such as Yama.  The
  changelog names the read-only users of PTRACE_MODE_ATTACH that are
  refused too.
- 3/3: cases for those three, the idmapped mount goes under /tmp
  instead of creating /mnt on the host, a failure to set it up is an
  error rather than a skip, and the binary is in .gitignore (Sashiko).
  Also cases for ptrace at a non-zero uid and one level down, and one
  that writes the file capabilities and runs them; the test skips when
  it can't run rather than failing, and setup errors are caught
  everywhere.

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 in such a namespace.  Containers that don't map host
uid 0, unprivileged users and anything with CAP_SETFCAP aren't
affected.

Testing: 3/3 runs 46 cases on their own as root in private mount
namespaces.  On vfs.all without the series the 19 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 v10:
- Link to v9: https://patch.msgid.link/20261109-b4-setfcap-userns-v9-0-0d3f00976ce2@toxicpanda.com

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                              |   72 +-
 tools/testing/selftests/namespaces/.gitignore     |    1 +
 tools/testing/selftests/namespaces/Makefile       |    3 +-
 tools/testing/selftests/namespaces/config         |    2 +
 tools/testing/selftests/namespaces/setfcap_test.c | 1505 +++++++++++++++++++++
 7 files changed, 1585 insertions(+), 5 deletions(-)
---
base-commit: 2c3ba1f1cb416b81d617b92a24a4bcaa48d574e3
change-id: 20261006-b4-setfcap-userns-d63185a31ce3


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP
  2026-11-12 22:08 [PATCH v10 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
@ 2026-11-12 22:08 ` Josef Bacik
  2026-11-12 22:17   ` sashiko-bot
  2026-11-12 22:08 ` [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
  2026-11-12 22:08 ` [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
  2 siblings, 1 reply; 7+ messages in thread
From: Josef Bacik @ 2026-11-12 22:08 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, Shuah Khan, linux-kselftest, Bradley Morgan,
	Josef Bacik

A uid 0 task that gave up CAP_SETFCAP can get it back by joining a user
namespace that another root task created.  Commit db2e718a4798
("capabilities: require CAP_SETFCAP to map uid 0") keeps such a task
from mapping uid 0 into a namespace it creates itself, because tasks in
that namespace could then write file capabilities for its root user.
setns() gives a joiner a full capability set in the namespace too, so
it can do the same there:

  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
                                      A had CAP_SETFCAP -> allowed

If A's namespace already maps host uid 0, at any uid, B can set
security.capability on a file right away.  Either way uid 1000 can then
run that file with CAP_SYS_ADMIN, which the selftest at the end of this
series shows on an unmodified kernel.

The check doesn't look at the joiner's uids, which it can change while
keeping its other capabilities, and CAP_SYS_PTRACE doesn't stand in for
CAP_SETFCAP.  What gets refused is a task without CAP_SETFCAP that has
CAP_SYS_ADMIN over a namespace that maps the joiner's root user, or that
has no uid_map yet and is owned by that root user, who could still map
it.  Containers that
don't map the joiner's root user, tasks with CAP_SETFCAP and
unprivileged users are not affected.

Like verify_root_map(), the check is about uid 0 of the joiner's own
namespace.  The root user of an ancestor that it maps at another uid,
and a root user stored through an idmapped mount, are left to whoever
set up that map or mount.

Fix by refusing setns() when the joiner doesn't have CAP_SETFCAP and
tasks in the namespace can write file capabilities for the root user of
the joiner's namespace.

Suggested-by: Serge Hallyn <serge@hallyn.com>
Link: https://lore.kernel.org/all/fd159e68-c613-4c7d-bfba-8af0a3f3a96e@hallyn.com/
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 include/linux/capability.h |  3 +++
 kernel/user_namespace.c    |  4 ++++
 security/commoncap.c       | 38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 45 insertions(+)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index 622137f66f09..9ebcdab56937 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);
 
+bool cred_borrows_setfcap(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 50c374ed4687..34becacaccd6 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -1390,6 +1390,10 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
 	if (!ns_capable(user_ns, CAP_SYS_ADMIN))
 		return -EPERM;
 
+	/* Don't hand back CAP_SETFCAP to a root task that gave it up. */
+	if (cred_borrows_setfcap(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 d47ab3022343..99a77fe459e9 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -131,6 +131,44 @@ int cap_capable(const struct cred *cred, struct user_namespace *target_ns,
 	return ret;
 }
 
+/**
+ * cred_borrows_setfcap - Check whether a userns gives back CAP_SETFCAP
+ * @cred: The credentials of a task that joins @ns or traces a task in it
+ * @ns: The user namespace
+ *
+ * A task without CAP_SETFCAP can't map uid 0 of its namespace into a user
+ * namespace it creates, see verify_root_map(), as tasks there could then
+ * write file capabilities that are honoured for that root user.  Tasks in
+ * @ns can do that if @ns maps it, at any uid.  While @ns has no uid_map yet,
+ * they can only map the owner's uid, and if that is uid 0 of the parent
+ * only if the creator of @ns had CAP_SETFCAP.
+ *
+ * Like verify_root_map(), this only looks at uid 0 of @cred's namespace, not
+ * at the root users of ancestors that it maps at another uid, and a root
+ * user stored through an idmapped mount is up to whoever sets up the mount.
+ * A map written later from the parent counts for every task in @ns, as it
+ * does for the creator: mapping uid 0 of the parent takes CAP_SETFCAP there,
+ * any other uid CAP_SETUID.
+ *
+ * Return: true if @cred doesn't have CAP_SETFCAP and tasks in @ns can write
+ * file capabilities for the root user of @cred's namespace.
+ */
+bool cred_borrows_setfcap(const struct cred *cred, struct user_namespace *ns)
+{
+	kuid_t root = make_kuid(cred->user_ns, 0);
+	uid_t id;
+
+	if (cap_raised(cred->cap_permitted, CAP_SETFCAP))
+		return false;
+
+	if (READ_ONCE(ns->uid_map.nr_extents))
+		return kuid_has_mapping(ns, root);
+
+	id = from_kuid(ns->parent, root);
+	return id != (uid_t)-1 && uid_eq(ns->owner, root) &&
+	       (id || ns->parent_could_setfcap);
+}
+
 /**
  * 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] 7+ messages in thread

* [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP
  2026-11-12 22:08 [PATCH v10 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
  2026-11-12 22:08 ` [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-11-12 22:08 ` Josef Bacik
  2026-11-12 22:23   ` sashiko-bot
  2026-11-12 22:08 ` [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
  2 siblings, 1 reply; 7+ messages in thread
From: Josef Bacik @ 2026-11-12 22:08 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, Shuah Khan, linux-kselftest, Bradley Morgan,
	Josef Bacik

A uid 0 task that gave up CAP_SETFCAP and CAP_SYS_PTRACE can still
attach to a task in a user namespace that another root task created,
and have it write file capabilities for its root user.  For a target in
a child namespace cap_ptrace_access_check() asks for CAP_SYS_PTRACE over
that namespace, and cap_capable() grants every capability there to a
task whose euid owns it, whatever it gave up:

  task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP,
  unshare(CLONE_NEWUSER)              no CAP_SYS_PTRACE
                                    ptrace(PTRACE_ATTACH, A)
                                      euid == ns->owner -> allowed
                                    make A write "0 0 1" to its uid_map,
                                    or set file capabilities

The same goes for /proc/<pid>/mem, process_vm_writev() and
pidfd_getfd().  setns() refuses B now, so ptrace is the way left into
the namespace.

The target's own capabilities are no guide.  A target that gave up
CAP_SETFCAP can still get it back from its inheritable set on execve(),
take it from another task of its namespace if it kept CAP_SYS_PTRACE, or
write "0 0 1" to its namespace's uid_map while there is none yet.

Every PTRACE_MODE_ATTACH check is refused, so reading /proc/<pid>/mem,
/proc/<pid>/stack and process_vm_readv() of such a target go too.
PTRACE_MODE_READ, a tracer and target in the same namespace, and targets
in namespaces that setns() lets the tracer into are not affected.
Within one namespace the usual rules apply, as for every other
capability.  A tracer with CAP_SYS_PTRACE but not CAP_SETFCAP is refused
as well: whether CAP_SYS_PTRACE lets it take CAP_SETFCAP from a task of
its own namespace depends on LSMs such as Yama.

Fix by refusing PTRACE_MODE_ATTACH and PTRACE_TRACEME across user
namespaces when the tracer doesn't have CAP_SETFCAP and setns() would
refuse it the target's namespace.

Suggested-by: Serge Hallyn <serge@hallyn.com>
Link: https://lore.kernel.org/all/fd159e68-c613-4c7d-bfba-8af0a3f3a96e@hallyn.com/
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 security/commoncap.c | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/security/commoncap.c b/security/commoncap.c
index 99a77fe459e9..c43abf4f148b 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -184,14 +184,31 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz)
 	return 0;
 }
 
+/*
+ * A tracer that setns() keeps out of a namespace mustn't take control of a
+ * task there either, whatever that task holds itself: it can get CAP_SETFCAP
+ * back on execve(), from another task of its namespace it traces, or by
+ * writing the uid_map of a namespace that doesn't have one yet.  Within one
+ * namespace the usual rules apply, as for every other capability.
+ */
+static bool cap_ptrace_borrows_setfcap(const struct cred *cred,
+				       const struct cred *child_cred)
+{
+	return cred->user_ns != child_cred->user_ns &&
+	       cred_borrows_setfcap(cred, child_cred->user_ns);
+}
+
 /**
  * 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 we don't have CAP_SETFCAP and the target task
+ * is in another user_ns that setns() would keep us out of, 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.
@@ -212,11 +229,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_ptrace_borrows_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();
@@ -227,8 +248,10 @@ 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 parent doesn't have CAP_SETFCAP and current is in another user_ns
+ * that setns() would keep parent out of, 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.
@@ -244,11 +267,14 @@ int cap_ptrace_traceme(struct task_struct *parent)
 	rcu_read_lock();
 	cred = __task_cred(parent);
 	child_cred = current_cred();
+	if (cap_ptrace_borrows_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] 7+ messages in thread

* [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces
  2026-11-12 22:08 [PATCH v10 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
  2026-11-12 22:08 ` [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
  2026-11-12 22:08 ` [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-11-12 22:08 ` Josef Bacik
  2026-11-12 22:19   ` sashiko-bot
  2 siblings, 1 reply; 7+ messages in thread
From: Josef Bacik @ 2026-11-12 22:08 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, Shuah Khan, linux-kselftest, Bradley Morgan,
	Josef Bacik

Add tests for whether a uid 0 task that gave up CAP_SETFCAP gets it back
by joining a user namespace whose tasks can write file capabilities for
its root user, or by attaching to such a task.

Each case runs as root in a private mount namespace with a tmpfs on
/tmp.  It sets up everything but the step it tests and fails if any of
that goes wrong, so a refusal comes from that step.

The ones that must be refused:

- setns() and PTRACE_ATTACH into namespaces that map host uid 0, at
  uid 0 or another uid, or that have no uid_map yet and whose tasks can
  still map it, and the same one level down among tasks of an
  unprivileged user
- setns() into a namespace without a uid_map under one that maps host
  uid 0 at uid 5, and setns() after moving the euid away from 0
- setns() and then writing file capabilities that uid 1000 runs with
  CAP_SYS_ADMIN, end to end
- pidfd_getfd(), PTRACE_ATTACH to targets that gave up CAP_SETFCAP but
  can get it back, and PTRACE_TRACEME towards a parent without
  CAP_SETFCAP
- joiners and tracers that kept CAP_SYS_PTRACE

The ones that must keep working are the mapping rule of commit
db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0") itself,
unprivileged users nesting namespaces, joiners and tracers with
CAP_SETFCAP, namespaces created without CAP_SETFCAP,
namespaces without a uid_map owned by another user, containers that
don't map host uid 0, idmapped mounts of a container's rootfs,
PTRACE_MODE_READ, and file capabilities written in a namespace of one's
own, after setns() with CAP_SETFCAP, by unprivileged users and through
an idmapped mount.

Everything skips when the test doesn't run as root with a full
capability set in the initial user namespace, root or uid 1000 can't
create user namespaces, or there is no tmpfs or no security.capability
on it.  The ptrace cases also skip when Yama forbids ptrace.  The
idmapped mount cases also skip when tmpfs can't be idmapped.

Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 tools/testing/selftests/namespaces/.gitignore     |    1 +
 tools/testing/selftests/namespaces/Makefile       |    3 +-
 tools/testing/selftests/namespaces/config         |    2 +
 tools/testing/selftests/namespaces/setfcap_test.c | 1505 +++++++++++++++++++++
 4 files changed, 1510 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/namespaces/.gitignore b/tools/testing/selftests/namespaces/.gitignore
index 995b082357fa..f7d1867113e8 100644
--- a/tools/testing/selftests/namespaces/.gitignore
+++ b/tools/testing/selftests/namespaces/.gitignore
@@ -11,3 +11,4 @@ stress_test
 listns_pagination_bug
 regression_pidfd_setns_test
 uid_map_range_test
+setfcap_test
diff --git a/tools/testing/selftests/namespaces/Makefile b/tools/testing/selftests/namespaces/Makefile
index 9b22d12708e1..0455b6544869 100644
--- a/tools/testing/selftests/namespaces/Makefile
+++ b/tools/testing/selftests/namespaces/Makefile
@@ -14,7 +14,8 @@ TEST_GEN_PROGS := nsid_test \
 		  stress_test \
 		  listns_pagination_bug \
 		  regression_pidfd_setns_test \
-		  uid_map_range_test
+		  uid_map_range_test \
+		  setfcap_test
 
 include ../lib.mk
 
diff --git a/tools/testing/selftests/namespaces/config b/tools/testing/selftests/namespaces/config
index d09836260262..2a1fa144398e 100644
--- a/tools/testing/selftests/namespaces/config
+++ b/tools/testing/selftests/namespaces/config
@@ -5,3 +5,5 @@ CONFIG_USER_NS=y
 CONFIG_PID_NS=y
 CONFIG_NET_NS=y
 CONFIG_CGROUPS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_XATTR=y
diff --git a/tools/testing/selftests/namespaces/setfcap_test.c b/tools/testing/selftests/namespaces/setfcap_test.c
new file mode 100644
index 000000000000..8fb7286428ab
--- /dev/null
+++ b/tools/testing/selftests/namespaces/setfcap_test.c
@@ -0,0 +1,1505 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A uid 0 task that gave up CAP_SETFCAP must not get it back by joining a
+ * user namespace whose tasks can write file capabilities for its root user,
+ * or by attaching to such a task.  Mapping and file capabilities in the
+ * namespaces involved have to keep working.
+ *
+ * "R" is a task with uid 0 and all capabilities in the initial namespace,
+ * "X" one with uid 0 that gave up CAP_SETFCAP and CAP_SYS_PTRACE in its
+ * permitted, effective and bounding sets, and "U" is uid 1000.
+ *
+ * Every test runs in a private mount namespace with a tmpfs on /tmp.
+ */
+#define _GNU_SOURCE
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/wait.h>
+#include <sys/xattr.h>
+#include <linux/capability.h>
+#include <linux/mount.h>
+
+#include "kselftest_harness.h"
+
+#ifndef __NR_pidfd_open
+#define __NR_pidfd_open 434
+#endif
+#ifndef __NR_pidfd_getfd
+#define __NR_pidfd_getfd 438
+#endif
+#ifndef __NR_open_tree
+#define __NR_open_tree 428
+#endif
+#ifndef __NR_move_mount
+#define __NR_move_mount 429
+#endif
+#ifndef __NR_mount_setattr
+#define __NR_mount_setattr 442
+#endif
+
+/* Exit codes of a copy of this program run with SETFCAP_TEST_CAPS set. */
+#define CAPS_HAS_SYS_ADMIN	42
+#define CAPS_NO_SYS_ADMIN	43
+
+/* A step that wasn't the one under test went wrong. */
+#define SETUP_ERROR		(-1000)
+
+static int capget_v3(struct __user_cap_data_struct d[2])
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+
+	return syscall(SYS_capget, &h, d);
+}
+
+__attribute__((constructor)) static void report_caps(void)
+{
+	struct __user_cap_data_struct d[2];
+
+	if (!getenv("SETFCAP_TEST_CAPS"))
+		return;
+	if (capget_v3(d))
+		_exit(1);
+	_exit(d[0].effective & (1u << CAP_SYS_ADMIN) ?
+	      CAPS_HAS_SYS_ADMIN : CAPS_NO_SYS_ADMIN);
+}
+
+/* X: uid 0 without CAP_SETFCAP and CAP_SYS_PTRACE */
+static void restrict_root(void)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+	unsigned int drop = (1u << CAP_SETFCAP) | (1u << CAP_SYS_PTRACE);
+
+	if (prctl(PR_CAPBSET_DROP, CAP_SETFCAP) ||
+	    prctl(PR_CAPBSET_DROP, CAP_SYS_PTRACE) || capget_v3(d))
+		_exit(99);
+	d[0].effective &= ~drop;
+	d[0].permitted &= ~drop;
+	if (syscall(SYS_capset, &h, d))
+		_exit(99);
+}
+
+/* Give up CAP_SETFCAP only in the permitted and effective set. */
+static void drop_setfcap_keep_bset(void)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+
+	if (capget_v3(d))
+		_exit(99);
+	d[0].effective &= ~(1u << CAP_SETFCAP);
+	d[0].permitted &= ~(1u << CAP_SETFCAP);
+	if (syscall(SYS_capset, &h, d))
+		_exit(99);
+}
+
+/* Keep CAP_SETFCAP only in the inheritable set. */
+static void keep_setfcap_inheritable(void)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+
+	if (capget_v3(d))
+		_exit(99);
+	d[0].inheritable |= 1u << CAP_SETFCAP;
+	if (syscall(SYS_capset, &h, d) || prctl(PR_CAPBSET_DROP, CAP_SETFCAP))
+		_exit(99);
+	d[0].effective &= ~(1u << CAP_SETFCAP);
+	d[0].permitted &= ~(1u << CAP_SETFCAP);
+	if (syscall(SYS_capset, &h, d))
+		_exit(99);
+}
+
+/* Give up CAP_SETFCAP only, keep CAP_SYS_PTRACE. */
+static void drop_setfcap(void)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+
+	if (prctl(PR_CAPBSET_DROP, CAP_SETFCAP) || capget_v3(d))
+		_exit(99);
+	d[0].effective &= ~(1u << CAP_SETFCAP);
+	d[0].permitted &= ~(1u << CAP_SETFCAP);
+	if (syscall(SYS_capset, &h, d))
+		_exit(99);
+}
+
+static void become_user(void)
+{
+	if (setresgid(1000, 1000, 1000) || setresuid(1000, 1000, 1000))
+		_exit(99);
+	prctl(PR_SET_DUMPABLE, 1);
+}
+
+static long wr(int fd, const char *s)
+{
+	long r = write(fd, s, strlen(s));
+
+	return r < 0 ? -errno : 0;
+}
+
+static long write_file(int pid, const char *name, const char *s)
+{
+	char path[64];
+	long r;
+	int fd;
+
+	if (pid)
+		snprintf(path, sizeof(path), "/proc/%d/%s", pid, name);
+	else
+		snprintf(path, sizeof(path), "/proc/self/%s", name);
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+	r = wr(fd, s);
+	close(fd);
+	return r;
+}
+
+static long write_map(int pid, const char *map)
+{
+	return write_file(pid, "uid_map", map);
+}
+
+/* both maps, as the setup for a test rather than its subject */
+static long map_both(int pid, const char *map)
+{
+	if (write_map(pid, map) || write_file(pid, "setgroups", "deny") ||
+	    write_file(pid, "gid_map", map))
+		return SETUP_ERROR;
+	return 0;
+}
+
+/* what an unprivileged user does after unshare(CLONE_NEWUSER) */
+static long map_user(void)
+{
+	return map_both(0, "0 1000 1\n");
+}
+
+static int userns_fd(int pid)
+{
+	char path[64];
+
+	snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+	return open(path, O_RDONLY);
+}
+
+/* setns() into the user namespace fd refers to, and close it */
+static long join_fd(int fd)
+{
+	long r;
+
+	if (fd < 0)
+		return SETUP_ERROR;
+	r = setns(fd, CLONE_NEWUSER) ? -errno : 0;
+	close(fd);
+	return r;
+}
+
+static long join(int pid)
+{
+	return join_fd(userns_fd(pid));
+}
+
+/* security.capability with CAP_SYS_ADMIN in permitted and effective; v2 if rootid < 0 */
+static long set_fcap(const char *path, int rootid)
+{
+	struct vfs_ns_cap_data d;
+	int v3 = rootid >= 0;
+
+	memset(&d, 0, sizeof(d));
+	d.magic_etc = htole32((v3 ? VFS_CAP_REVISION_3 : VFS_CAP_REVISION_2) |
+			      VFS_CAP_FLAGS_EFFECTIVE);
+	d.data[0].permitted = htole32(1u << CAP_SYS_ADMIN);
+	d.rootid = htole32(rootid);
+	if (setxattr(path, "security.capability", &d,
+		     v3 ? XATTR_CAPS_SZ_3 : XATTR_CAPS_SZ_2, 0))
+		return -errno;
+	return 0;
+}
+
+/* a copy of this program, owned by uid */
+static int new_file(const char *path, int uid)
+{
+	char buf[65536];
+	int in, out, ret = -1;
+	ssize_t n;
+
+	unlink(path);
+	in = open("/proc/self/exe", O_RDONLY);
+	if (in < 0)
+		return -1;
+	out = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0755);
+	if (out < 0)
+		goto out_in;
+	while ((n = read(in, buf, sizeof(buf))) > 0)
+		if (write(out, buf, n) != n)
+			goto out;
+	if (!n && !fchown(out, uid, uid) && !fchmod(out, 0755))
+		ret = 0;
+out:
+	close(out);
+out_in:
+	close(in);
+	return ret;
+}
+
+/*
+ * Does U get CAP_SYS_ADMIN from the file capabilities of path?  1 if it
+ * does, 0 if not, -1 if the file didn't run.
+ */
+static int user_gets_sys_admin(const char *path)
+{
+	int pid, st;
+
+	pid = fork();
+	if (pid < 0)
+		return -1;
+	if (!pid) {
+		become_user();
+		setenv("SETFCAP_TEST_CAPS", "1", 1);
+		execl(path, path, NULL);
+		_exit(1);
+	}
+	if (waitpid(pid, &st, 0) != pid || !WIFEXITED(st))
+		return -1;
+	if (WEXITSTATUS(st) == CAPS_HAS_SYS_ADMIN)
+		return 1;
+	return WEXITSTATUS(st) == CAPS_NO_SYS_ADMIN ? 0 : -1;
+}
+
+/* run fn in a child and return what it returns */
+static long in_child(long (*fn)(void *), void *arg)
+{
+	int p[2], pid;
+	long r = SETUP_ERROR;
+
+	if (pipe(p))
+		return SETUP_ERROR;
+	pid = fork();
+	if (!pid) {
+		close(p[0]);
+		r = fn(arg);
+		if (write(p[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	close(p[1]);
+	if (pid < 0 || read(p[0], &r, sizeof(r)) != sizeof(r))
+		r = SETUP_ERROR;
+	close(p[0]);
+	if (pid > 0)
+		waitpid(pid, NULL, 0);
+	return r;
+}
+
+/* A task that unshares a user namespace and waits. */
+struct holder {
+	int pid, go;
+};
+
+enum holder_kind {
+	HOLDER_ROOT,		/* R */
+	HOLDER_RESTRICTED,	/* X */
+	HOLDER_USER,		/* U, maps "0 1000 1" itself */
+};
+
+/*
+ * Fork a task that runs setup(arg), says it is ready and waits until it is
+ * reaped.  Its pid is -1 if any of that fails.
+ */
+static struct holder spawn(int (*setup)(const void *), const void *arg)
+{
+	struct holder h = { -1, -1 };
+	int ready[2], go[2];
+	char c;
+
+	if (pipe(ready))
+		return h;
+	if (pipe(go)) {
+		close(ready[0]);
+		close(ready[1]);
+		return h;
+	}
+	h.pid = fork();
+	if (!h.pid) {
+		close(ready[0]);
+		close(go[1]);
+		if (setup(arg) || write(ready[1], "r", 1) != 1)
+			_exit(1);
+		if (read(go[0], &c, 1) < 0)
+			_exit(1);
+		_exit(0);
+	}
+	close(ready[1]);
+	close(go[0]);
+	h.go = go[1];
+	if (h.pid > 0 && read(ready[0], &c, 1) != 1) {
+		kill(h.pid, SIGKILL);
+		waitpid(h.pid, NULL, 0);
+		h.pid = -1;
+	}
+	close(ready[0]);
+	if (h.pid <= 0) {
+		close(h.go);
+		h.go = -1;
+	}
+	return h;
+}
+
+struct holder_setup {
+	enum holder_kind kind;
+	int map_fd_at;
+};
+
+static int holder_setup(const void *a)
+{
+	const struct holder_setup *s = a;
+	int fd;
+
+	if (s->kind == HOLDER_RESTRICTED)
+		restrict_root();
+	if (s->kind == HOLDER_USER)
+		become_user();
+	if (unshare(CLONE_NEWUSER))
+		return -1;
+	if (s->kind == HOLDER_USER && map_user())
+		return -1;
+	if (s->map_fd_at) {
+		fd = open("/proc/self/uid_map", O_WRONLY);
+		if (fd < 0 || dup2(fd, s->map_fd_at) != s->map_fd_at)
+			return -1;
+		close(fd);
+	}
+	return 0;
+}
+
+static struct holder spawn_holder(enum holder_kind kind, int map_fd_at)
+{
+	struct holder_setup s = { kind, map_fd_at };
+
+	return spawn(holder_setup, &s);
+}
+
+static void reap_holder(struct holder h)
+{
+	if (h.pid <= 0)
+		return;
+	close(h.go);
+	kill(h.pid, SIGKILL);
+	waitpid(h.pid, NULL, 0);
+}
+
+/* ... whose maps are written from the parent namespace by the caller */
+static struct holder spawn_mapped(enum holder_kind kind, const char *map)
+{
+	struct holder h = spawn_holder(kind, 0);
+
+	if (h.pid > 0 && map_both(h.pid, map)) {
+		reap_holder(h);
+		h.pid = -1;
+	}
+	return h;
+}
+
+/*
+ * ... that joins the user namespace of pid instead, runs prep there and, if
+ * new_ns is set, unshares another one
+ */
+struct joined_setup {
+	int pid;
+	void (*prep)(void);
+	int new_ns;
+};
+
+static int joined_setup(const void *a)
+{
+	const struct joined_setup *s = a;
+
+	if (join(s->pid))
+		return -1;
+	if (s->prep)
+		s->prep();
+	if (s->new_ns && unshare(CLONE_NEWUSER))
+		return -1;
+	return 0;
+}
+
+static struct holder spawn_joined(int pid, void (*prep)(void), int new_ns)
+{
+	struct joined_setup s = { pid, prep, new_ns };
+
+	return spawn(joined_setup, &s);
+}
+
+#define PID(a) (((struct holder *)(a))->pid)
+
+static long attach_only(int pid)
+{
+	if (ptrace(PTRACE_ATTACH, pid, 0, 0))
+		return -errno;
+	waitpid(pid, NULL, 0);
+	ptrace(PTRACE_DETACH, pid, 0, 0);
+	return 0;
+}
+
+/*
+ * /tmp idmapped with the user namespace of pid, at /tmp/mnt.  Returns 1 if
+ * tmpfs can't be idmapped, -1 if anything else goes wrong.
+ */
+static int idmap_tmp(int pid)
+{
+	struct mount_attr attr = { .attr_set = MOUNT_ATTR_IDMAP };
+	int tfd = -1, nsfd, ret = -1;
+	char path[64];
+
+	snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+	nsfd = open(path, O_RDONLY);
+	if (nsfd < 0)
+		return -1;
+	tfd = syscall(__NR_open_tree, AT_FDCWD, "/tmp",
+		      OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	if (tfd < 0) {
+		if (errno == ENOSYS)
+			ret = 1;
+		goto out;
+	}
+	attr.userns_fd = nsfd;
+	if (syscall(__NR_mount_setattr, tfd, "", AT_EMPTY_PATH, &attr,
+		    sizeof(attr))) {
+		if (errno == EINVAL || errno == EOPNOTSUPP || errno == ENOSYS)
+			ret = 1;
+		goto out;
+	}
+	if (syscall(__NR_move_mount, tfd, "", AT_FDCWD, "/tmp/mnt",
+		    MOVE_MOUNT_F_EMPTY_PATH))
+		goto out;
+	ret = 0;
+out:
+	if (tfd >= 0)
+		close(tfd);
+	close(nsfd);
+	return ret;
+}
+
+/* Each test: private mounts, a tmpfs on /tmp with copies of this program */
+static void prepare(struct __test_metadata *_metadata)
+{
+	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
+	ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
+	ASSERT_EQ(mount("tmpfs", "/tmp", "tmpfs", 0, "mode=1777"), 0);
+	ASSERT_EQ(new_file("/tmp/x", 0), 0);
+	ASSERT_EQ(new_file("/tmp/u", 1000), 0);
+	ASSERT_EQ(mkdir("/tmp/mnt", 0755), 0);
+}
+
+/* What keeps the tests from running here, or NULL */
+static const char *missing(void)
+{
+	struct __user_cap_data_struct d[2];
+	unsigned long a, b, c;
+	int i, pid, st, last;
+	FILE *f;
+
+	if (geteuid())
+		return "needs root";
+	f = fopen("/proc/self/uid_map", "r");
+	if (!f)
+		return "no /proc/self/uid_map";
+	i = fscanf(f, "%lu %lu %lu", &a, &b, &c);
+	fclose(f);
+	if (i != 3 || a || b || c != 4294967295UL)
+		return "not in the initial user namespace";
+	f = fopen("/proc/sys/kernel/cap_last_cap", "r");
+	if (!f)
+		return "no /proc/sys/kernel/cap_last_cap";
+	i = fscanf(f, "%d", &last);
+	fclose(f);
+	if (i != 1 || capget_v3(d))
+		return "can't read the capability sets";
+	for (i = 0; i <= last; i++)
+		if (!(d[i / 32].effective & (1u << (i % 32))) ||
+		    prctl(PR_CAPBSET_READ, i) != 1)
+			return "needs a full capability set";
+	pid = fork();
+	if (!pid)
+		_exit(unshare(CLONE_NEWUSER) ? 1 : 0);
+	if (pid < 0 || waitpid(pid, &st, 0) != pid || !WIFEXITED(st))
+		return "fork() failed";
+	if (WEXITSTATUS(st))
+		return "can't create user namespaces";
+	pid = fork();
+	if (!pid) {
+		become_user();
+		_exit(unshare(CLONE_NEWUSER) ? 1 : 0);
+	}
+	if (pid < 0 || waitpid(pid, &st, 0) != pid || !WIFEXITED(st))
+		return "fork() failed";
+	if (WEXITSTATUS(st))
+		return "uid 1000 can't create user namespaces";
+	pid = fork();
+	if (!pid) {
+		if (unshare(CLONE_NEWNS) ||
+		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+		    mount("tmpfs", "/tmp", "tmpfs", 0, NULL))
+			_exit(2);
+		if (new_file("/tmp/x", 0))
+			_exit(3);
+		_exit(set_fcap("/tmp/x", -1) == -EOPNOTSUPP ? 4 : 0);
+	}
+	if (pid < 0 || waitpid(pid, &st, 0) != pid || !WIFEXITED(st))
+		return "fork() failed";
+	switch (WEXITSTATUS(st)) {
+	case 0:
+		break;
+	case 2:
+		return "can't mount a tmpfs on /tmp";
+	case 4:
+		return "no security.capability on tmpfs";
+	default:
+		return "can't copy the test program to tmpfs";
+	}
+	return NULL;
+}
+
+FIXTURE(setfcap) {};
+
+FIXTURE_SETUP(setfcap)
+{
+	const char *why = missing();
+
+	if (why)
+		SKIP(return, "%s", why);
+	prepare(_metadata);
+}
+
+FIXTURE_TEARDOWN(setfcap) {}
+
+/* ---------------------------------------------------------------------- */
+/* Mapping uid 0 of the parent                                           */
+
+static long x_maps_from_parent(void *a)
+{
+	restrict_root();
+	return write_map(PID(a), "0 0 1\n");
+}
+
+TEST_F(setfcap, map_from_parent_without_setfcap)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_maps_from_parent, &h), -EPERM);
+	reap_holder(h);
+}
+
+static long x_maps_own(void *a)
+{
+	restrict_root();
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_own_without_setfcap)
+{
+	EXPECT_EQ(in_child(x_maps_own, NULL), -EPERM);
+}
+
+/* What has to keep working. */
+
+static long r_maps_own(void *a)
+{
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_own)
+{
+	EXPECT_EQ(in_child(r_maps_own, NULL), 0);
+}
+
+static long r_maps_from_parent(void *a)
+{
+	return write_map(PID(a), "0 0 1000\n");
+}
+
+TEST_F(setfcap, map_from_parent)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_maps_from_parent, &h), 0);
+	reap_holder(h);
+}
+
+static long child_maps(void *a)
+{
+	return write_map(0, "0 0 1\n");
+}
+
+static long r_child_maps(void *a)
+{
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	return in_child(child_maps, NULL);
+}
+
+TEST_F(setfcap, map_by_child_of_creator)
+{
+	EXPECT_EQ(in_child(r_child_maps, NULL), 0);
+}
+
+static long r_joins_and_maps(void *a)
+{
+	long r = join(PID(a));
+
+	if (r)
+		return r;
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_after_setns_with_setfcap)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_joins_and_maps, &h), 0);
+	reap_holder(h);
+}
+
+static long r_joins_writes_fd(void *a)
+{
+	void **v = a;
+	long r = join(PID(v[0]));
+
+	if (r)
+		return r;
+	return wr(*(int *)v[1], "0 0 1\n");
+}
+
+TEST_F(setfcap, map_opened_in_parent_written_inside)
+{
+	struct holder h;
+	char path[64];
+	void *v[2];
+	int fd;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	snprintf(path, sizeof(path), "/proc/%d/uid_map", h.pid);
+	fd = open(path, O_WRONLY);
+	ASSERT_GE(fd, 0);
+	v[0] = &h;
+	v[1] = &fd;
+	EXPECT_EQ(in_child(r_joins_writes_fd, v), 0);
+	close(fd);
+	reap_holder(h);
+}
+
+static long u_nested_maps_own(void *a)
+{
+	become_user();
+	if (unshare(CLONE_NEWUSER) || map_user())
+		return SETUP_ERROR;
+	return in_child(r_maps_own, NULL);
+}
+
+TEST_F(setfcap, map_unprivileged_nested)
+{
+	EXPECT_EQ(in_child(u_nested_maps_own, NULL), 0);
+}
+
+static long u_nested_maps_child(void *a)
+{
+	struct holder h;
+	long r;
+
+	become_user();
+	if (unshare(CLONE_NEWUSER) || map_user())
+		return SETUP_ERROR;
+	h = spawn_holder(HOLDER_ROOT, 0);
+	if (h.pid <= 0)
+		return SETUP_ERROR;
+	r = write_map(h.pid, "0 0 1\n");
+	reap_holder(h);
+	return r;
+}
+
+TEST_F(setfcap, map_unprivileged_nested_from_parent)
+{
+	EXPECT_EQ(in_child(u_nested_maps_child, NULL), 0);
+}
+
+/* ---------------------------------------------------------------------- */
+/* File capabilities                                                       */
+
+/* What has to keep working. */
+
+static long r_maps_sets_fcap(void *a)
+{
+	if (unshare(CLONE_NEWUSER) || map_both(0, "0 0 1\n"))
+		return SETUP_ERROR;
+	return set_fcap("/tmp/x", -1);
+}
+
+TEST_F(setfcap, fcap_own_ns)
+{
+	EXPECT_EQ(in_child(r_maps_sets_fcap, NULL), 0);
+	EXPECT_EQ(user_gets_sys_admin("/tmp/x"), 1);
+}
+
+static long r_joins_sets_fcap(void *a)
+{
+	long r = join(PID(a));
+
+	if (r)
+		return r;
+	return set_fcap("/tmp/x", -1);
+}
+
+TEST_F(setfcap, fcap_after_setns_with_setfcap)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_joins_sets_fcap, &h), 0);
+	reap_holder(h);
+}
+
+static long u_sets_fcap(void *a)
+{
+	become_user();
+	if (unshare(CLONE_NEWUSER) || map_user())
+		return SETUP_ERROR;
+	return set_fcap("/tmp/u", -1);
+}
+
+TEST_F(setfcap, fcap_unprivileged)
+{
+	EXPECT_EQ(in_child(u_sets_fcap, NULL), 0);
+}
+
+static long u_nested_sets_fcap_inner(void *a)
+{
+	if (unshare(CLONE_NEWUSER) || map_both(0, "0 0 1\n"))
+		return SETUP_ERROR;
+	return set_fcap("/tmp/u", -1);
+}
+
+static long u_nested_sets_fcap(void *a)
+{
+	become_user();
+	if (unshare(CLONE_NEWUSER) || map_user())
+		return SETUP_ERROR;
+	return in_child(u_nested_sets_fcap_inner, NULL);
+}
+
+TEST_F(setfcap, fcap_unprivileged_nested)
+{
+	EXPECT_EQ(in_child(u_nested_sets_fcap, NULL), 0);
+}
+
+static long u_joins_own_sets_fcap(void *a)
+{
+	long r;
+
+	become_user();
+	r = join(PID(a));
+	if (r)
+		return r;
+	return set_fcap("/tmp/u", -1);
+}
+
+TEST_F(setfcap, fcap_unprivileged_after_setns)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_USER, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(u_joins_own_sets_fcap, &h), 0);
+	reap_holder(h);
+}
+
+/* ---------------------------------------------------------------------- */
+/* Idmapped mounts: a container's rootfs can make its root host uid 0, so  */
+/* that file capabilities written through it are honoured on the host.    */
+/* That is up to whoever sets up the mount, so X may still join a         */
+/* container that doesn't map host uid 0 and write them.                  */
+
+struct idmap_case {
+	const char *ns_map, *idmap_map;
+	int file_uid, restricted;
+};
+
+struct idmap_join {
+	int pid, restricted;
+};
+
+/* join the namespace from a sibling of its holder and write the xattr */
+static long idmap_joins_sets_fcap(void *a)
+{
+	const struct idmap_join *j = a;
+
+	if (j->restricted)
+		restrict_root();
+	return join(j->pid) ? SETUP_ERROR : set_fcap("/tmp/mnt/x", 0);
+}
+
+/* returns 1 to skip: no idmapped mounts on tmpfs */
+static int idmap_case(const struct idmap_case *c, long *r)
+{
+	struct holder ns, im = { -1, -1 };
+	struct idmap_join j;
+	int ret = -1;
+
+	ns = spawn_mapped(HOLDER_ROOT, c->ns_map);
+	if (ns.pid <= 0)
+		return -1;
+	if (c->idmap_map) {
+		im = spawn_mapped(HOLDER_ROOT, c->idmap_map);
+		if (im.pid <= 0)
+			goto out;
+	}
+	if (new_file("/tmp/x", c->file_uid))
+		goto out;
+	ret = idmap_tmp(c->idmap_map ? im.pid : ns.pid);
+	if (ret)
+		goto out;
+	j.pid = ns.pid;
+	j.restricted = c->restricted;
+	*r = in_child(idmap_joins_sets_fcap, &j);
+	umount2("/tmp/mnt", MNT_DETACH);
+	ret = 0;
+out:
+	reap_holder(ns);
+	reap_holder(im);
+	return ret;
+}
+
+FIXTURE(idmap) {};
+
+FIXTURE_VARIANT(idmap) {
+	struct idmap_case c;
+	long expect;
+};
+
+/* R in its container writes through the container's own idmapped rootfs */
+FIXTURE_VARIANT_ADD(idmap, container_root) {
+	.c = { "0 100000 1000\n", NULL, 0, 0 },
+	.expect = 0,
+};
+
+/* left to the admin: the container doesn't map host uid 0 */
+FIXTURE_VARIANT_ADD(idmap, container_without_host_root) {
+	.c = { "0 100000 1000\n", NULL, 0, 1 },
+	.expect = 0,
+};
+
+FIXTURE_VARIANT_ADD(idmap, unrelated_idmap) {
+	.c = { "0 100000 1000\n", "200000 100000 1\n", 200000, 1 },
+	.expect = 0,
+};
+
+FIXTURE_SETUP(idmap)
+{
+	const char *why = missing();
+
+	if (why)
+		SKIP(return, "%s", why);
+	prepare(_metadata);
+}
+
+FIXTURE_TEARDOWN(idmap) {}
+
+TEST_F(idmap, fcap)
+{
+	long r = SETUP_ERROR;
+	int ret;
+
+	ret = idmap_case(&variant->c, &r);
+	ASSERT_GE(ret, 0);
+	if (ret == 1)
+		SKIP(return, "no idmapped mounts on tmpfs");
+	EXPECT_EQ(r, variant->expect);
+}
+
+/* ---------------------------------------------------------------------- */
+/* setns()                                                                 */
+/*                                                                         */
+/* A task that joins gets a full capability set in the namespace, so      */
+/* mapping uid 0 or writing file capabilities afterwards has no check of  */
+/* its own: setns() is the one.                                           */
+
+static long x_joins(void *a)
+{
+	restrict_root();
+	return join(PID(a));
+}
+
+static long r_joins(void *a)
+{
+	return join(PID(a));
+}
+
+TEST_F(setfcap, setns_ns_mapping_host_root)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* the root user can be mapped at any uid */
+TEST_F(setfcap, setns_ns_mapping_host_root_at_nonzero_uid)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "5 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins, &h), -EPERM);
+	reap_holder(h);
+}
+
+TEST_F(setfcap, setns_ns_without_map_yet)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* the same one level down, among tasks of an unprivileged user */
+static long u_nested_x(long (*step)(void *))
+{
+	struct holder h;
+	long r;
+
+	become_user();
+	if (unshare(CLONE_NEWUSER) || map_user())
+		return SETUP_ERROR;
+	/* R-like: full set in the user's namespace */
+	h = spawn_holder(HOLDER_ROOT, 0);
+	if (h.pid <= 0)
+		return SETUP_ERROR;
+	r = in_child(step, &h);
+	reap_holder(h);
+	return r;
+}
+
+static long u_nested_x_joins(void *a)
+{
+	return u_nested_x(x_joins);
+}
+
+TEST_F(setfcap, setns_nested_unprivileged)
+{
+	EXPECT_EQ(in_child(u_nested_x_joins, NULL), -EPERM);
+}
+
+/* host uid 0 is uid 5 of the parent, which the owner may map without it */
+static long x_joins_unmapped_under_nonzero_root(void *a)
+{
+	struct holder p, n;
+	long r;
+
+	p = spawn_mapped(HOLDER_ROOT, "5 0 1\n");
+	if (p.pid <= 0)
+		return SETUP_ERROR;
+	n = spawn_joined(p.pid, restrict_root, 1);
+	r = n.pid > 0 ? in_child(x_joins, &n) : SETUP_ERROR;
+	reap_holder(n);
+	reap_holder(p);
+	return r;
+}
+
+TEST_F(setfcap, setns_ns_without_map_yet_root_at_nonzero_uid)
+{
+	EXPECT_EQ(in_child(x_joins_unmapped_under_nonzero_root, NULL), -EPERM);
+}
+
+/* X moves its euid away from 0 and takes back its other capabilities */
+static long x_with_other_euid_joins(void *a)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+	int fd;
+
+	restrict_root();
+	fd = userns_fd(PID(a));
+	if (fd < 0 || setresuid(-1, 1000, -1) || capget_v3(d))
+		return SETUP_ERROR;
+	d[0].effective = d[0].permitted;
+	d[1].effective = d[1].permitted;
+	if (syscall(SYS_capset, &h, d))
+		return SETUP_ERROR;
+	return join_fd(fd);
+}
+
+TEST_F(setfcap, setns_with_other_euid)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_with_other_euid_joins, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* ... and the end result: file capabilities that uid 1000 runs with */
+static long x_joins_sets_fcap(void *a)
+{
+	long r;
+
+	restrict_root();
+	r = join(PID(a));
+	return r ? r : set_fcap("/tmp/x", -1);
+}
+
+TEST_F(setfcap, setns_then_fcap_for_host_root)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins_sets_fcap, &h), -EPERM);
+	EXPECT_EQ(user_gets_sys_admin("/tmp/x"), 0);
+	reap_holder(h);
+}
+
+/* CAP_SYS_PTRACE doesn't stand in for CAP_SETFCAP */
+static long ptracer_joins(void *a)
+{
+	drop_setfcap();
+	return join(PID(a));
+}
+
+TEST_F(setfcap, setns_with_sys_ptrace)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(ptracer_joins, &h), -EPERM);
+	reap_holder(h);
+}
+
+/*
+ * Its creator is uid 1000 with CAP_SETFCAP, so tasks in there can only map
+ * uid 1000.  X keeps CAP_SYS_PTRACE to see a namespace of another user.
+ */
+static int capable_user_unshares(const void *a)
+{
+	struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 };
+	struct __user_cap_data_struct d[2];
+
+	if (prctl(PR_SET_KEEPCAPS, 1) || setresgid(1000, 1000, 1000) ||
+	    setresuid(1000, 1000, 1000) || capget_v3(d))
+		return -1;
+	d[0].effective = d[0].permitted;
+	d[1].effective = d[1].permitted;
+	if (syscall(SYS_capset, &h, d))
+		return -1;
+	return unshare(CLONE_NEWUSER) ? -1 : 0;
+}
+
+TEST_F(setfcap, setns_ns_without_map_yet_of_another_owner)
+{
+	struct holder h;
+
+	h = spawn(capable_user_unshares, NULL);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(ptracer_joins, &h), 0);
+	reap_holder(h);
+}
+
+/* its creator had no CAP_SETFCAP, so nobody in there can map uid 0 */
+TEST_F(setfcap, setns_ns_created_without_setfcap)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_RESTRICTED, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins, &h), 0);
+	reap_holder(h);
+}
+
+/* a container that doesn't map host uid 0 */
+TEST_F(setfcap, setns_container)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 100000 1000\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins, &h), 0);
+	reap_holder(h);
+}
+
+TEST_F(setfcap, setns_unprivileged_ns)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_USER, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_joins, &h), 0);
+	reap_holder(h);
+}
+
+/* ---------------------------------------------------------------------- */
+/* ptrace                                                                  */
+
+FIXTURE(ptrace) {};
+
+FIXTURE_SETUP(ptrace)
+{
+	const char *why = missing();
+	FILE *f;
+	int scope;
+
+	if (why)
+		SKIP(return, "%s", why);
+	f = fopen("/proc/sys/kernel/yama/ptrace_scope", "r");
+	if (f) {
+		if (fscanf(f, "%d", &scope) == 1 && scope == 3) {
+			fclose(f);
+			SKIP(return, "Yama doesn't allow ptrace");
+		}
+		fclose(f);
+	}
+	prepare(_metadata);
+}
+
+FIXTURE_TEARDOWN(ptrace) {}
+
+static long x_attaches(void *a)
+{
+	restrict_root();
+	return attach_only(PID(a));
+}
+
+static long r_attaches(void *a)
+{
+	return attach_only(PID(a));
+}
+
+TEST_F(ptrace, into_ns_without_map_yet)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), -EPERM);
+	reap_holder(h);
+}
+
+static long x_takes_fd(void *a)
+{
+	int pfd, fd;
+
+	restrict_root();
+	pfd = syscall(__NR_pidfd_open, PID(a), 0);
+	if (pfd < 0)
+		return SETUP_ERROR;
+	fd = syscall(__NR_pidfd_getfd, pfd, 100, 0);
+	if (fd < 0)
+		return -errno;
+	close(fd);
+	return 0;
+}
+
+/* pidfd_getfd() checks for PTRACE_MODE_ATTACH */
+TEST_F(ptrace, pidfd_getfd)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 100);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_takes_fd, &h), -EPERM);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, into_ns_mapping_host_root)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* the root user can be mapped at any uid */
+TEST_F(ptrace, into_ns_mapping_host_root_at_nonzero_uid)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "5 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), -EPERM);
+	reap_holder(h);
+}
+
+static long u_nested_x_attaches(void *a)
+{
+	return u_nested_x(x_attaches);
+}
+
+TEST_F(ptrace, nested_unprivileged)
+{
+	EXPECT_EQ(in_child(u_nested_x_attaches, NULL), -EPERM);
+}
+
+/* X attaches to a task that joined the namespace and ran prep there */
+struct joined_target {
+	int pid;
+	void (*prep)(void);
+};
+
+static long x_attaches_joiner(void *a)
+{
+	const struct joined_target *j = a;
+	struct holder t;
+	long r;
+
+	t = spawn_joined(j->pid, j->prep, 0);
+	if (t.pid <= 0)
+		return SETUP_ERROR;
+	r = in_child(x_attaches, &t);
+	reap_holder(t);
+	return r;
+}
+
+/* an execve() as uid 0 gives the target CAP_SETFCAP back from its bset */
+TEST_F(ptrace, target_with_setfcap_in_bset)
+{
+	struct joined_target j = { 0, drop_setfcap_keep_bset };
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	j.pid = h.pid;
+	EXPECT_EQ(in_child(x_attaches_joiner, &j), -EPERM);
+	reap_holder(h);
+}
+
+/*
+ * The target's own capabilities don't matter: it gets CAP_SETFCAP back from
+ * its inheritable set on execve(), or takes it from another task of its
+ * namespace with CAP_SYS_PTRACE.
+ */
+TEST_F(ptrace, target_with_setfcap_inheritable)
+{
+	struct joined_target j = { 0, keep_setfcap_inheritable };
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	j.pid = h.pid;
+	EXPECT_EQ(in_child(x_attaches_joiner, &j), -EPERM);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, target_with_sys_ptrace)
+{
+	struct joined_target j = { 0, drop_setfcap };
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	j.pid = h.pid;
+	EXPECT_EQ(in_child(x_attaches_joiner, &j), -EPERM);
+	reap_holder(h);
+}
+
+/* ... or writes "0 0 1" while its namespace has no uid_map yet */
+TEST_F(ptrace, target_without_setfcap_ns_without_map_yet)
+{
+	struct joined_target j = { 0, restrict_root };
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	j.pid = h.pid;
+	EXPECT_EQ(in_child(x_attaches_joiner, &j), -EPERM);
+	reap_holder(h);
+}
+
+/*
+ * PTRACE_TRACEME from a child that joined a namespace mapping host uid 0,
+ * with a parent that gave up CAP_SETFCAP and CAP_SYS_PTRACE after the fork
+ */
+struct traceme {
+	int pid, restricted;
+};
+
+static long parent_of_traceme(void *a)
+{
+	const struct traceme *t = a;
+	int p[2], go[2], pid;
+	long r = SETUP_ERROR;
+	char c;
+
+	if (pipe(p) || pipe(go))
+		return SETUP_ERROR;
+	pid = fork();
+	if (!pid) {
+		close(p[0]);
+		close(go[1]);
+		if (read(go[0], &c, 1) != 1)
+			_exit(1);
+		if (join(t->pid))
+			r = SETUP_ERROR;
+		else
+			r = ptrace(PTRACE_TRACEME, 0, 0, 0) ? -errno : 0;
+		if (write(p[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	close(p[1]);
+	close(go[0]);
+	if (pid < 0) {
+		r = SETUP_ERROR;
+	} else {
+		if (t->restricted)
+			restrict_root();
+		if (write(go[1], "g", 1) != 1 ||
+		    read(p[0], &r, sizeof(r)) != sizeof(r))
+			r = SETUP_ERROR;
+	}
+	close(go[1]);
+	close(p[0]);
+	if (pid > 0) {
+		kill(pid, SIGKILL);
+		waitpid(pid, NULL, 0);
+	}
+	return r;
+}
+
+TEST_F(ptrace, traceme_by_restricted_parent)
+{
+	struct traceme t = { 0, 1 };
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	t.pid = h.pid;
+	EXPECT_EQ(in_child(parent_of_traceme, &t), -EPERM);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, traceme_by_parent_with_setfcap)
+{
+	struct traceme t = { 0, 0 };
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	t.pid = h.pid;
+	EXPECT_EQ(in_child(parent_of_traceme, &t), 0);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, into_container)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 100000 1000\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), 0);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, ns_mapping_only_user)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 1000 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), 0);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, between_restricted)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_RESTRICTED, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_attaches, &h), 0);
+	reap_holder(h);
+}
+
+TEST_F(ptrace, with_setfcap)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_attaches, &h), 0);
+	reap_holder(h);
+}
+
+/* CAP_SYS_PTRACE doesn't stand in for CAP_SETFCAP */
+static long ptracer_attaches(void *a)
+{
+	drop_setfcap();
+	return attach_only(PID(a));
+}
+
+TEST_F(ptrace, with_sys_ptrace)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(ptracer_attaches, &h), -EPERM);
+	reap_holder(h);
+}
+
+static long x_reads_environ(void *a)
+{
+	char path[64];
+	int fd;
+
+	restrict_root();
+	snprintf(path, sizeof(path), "/proc/%d/environ", PID(a));
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return -errno;
+	close(fd);
+	return 0;
+}
+
+/* PTRACE_MODE_READ is unchanged */
+TEST_F(setfcap, ptrace_read_mode_unchanged)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_reads_environ, &h), 0);
+	reap_holder(h);
+}
+
+TEST_HARNESS_MAIN

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP
  2026-11-12 22:08 ` [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-11-12 22:17   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-12 22:17 UTC (permalink / raw)
  Cc: linux-security-module

> A uid 0 task that gave up CAP_SETFCAP can get it back by joining a user
> namespace that another root task created.  Commit db2e718a4798
> ("capabilities: require CAP_SETFCAP to map uid 0") keeps such a task
> from mapping uid 0 into a namespace it creates itself, because tasks in
> that namespace could then write file capabilities for its root user.
> setns() gives a joiner a full capability set in the namespace too, so
> it can do the same there:
> 
>   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
>                                       A had CAP_SETFCAP -> allowed
> 
> [ ... ]
> 
> Suggested-by: Serge Hallyn <serge@hallyn.com>
> Link: https://lore.kernel.org/all/fd159e68-c613-4c7d-bfba-8af0a3f3a96e@hallyn.com/
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261112-b4-setfcap-userns-v10-0-84f6f7faa903@toxicpanda.com?part=1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces
  2026-11-12 22:08 ` [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
@ 2026-11-12 22:19   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-12 22:19 UTC (permalink / raw)
  Cc: linux-security-module

> Add tests for whether a uid 0 task that gave up CAP_SETFCAP gets it back
> by joining a user namespace whose tasks can write file capabilities for
> its root user, or by attaching to such a task.
> 
> Each case runs as root in a private mount namespace with a tmpfs on
> /tmp.  It sets up everything but the step it tests and fails if any of
> that goes wrong, so a refusal comes from that step.
> 
> The ones that must be refused:
> 
> - setns() and PTRACE_ATTACH into namespaces that map host uid 0, at
>   uid 0 or another uid, or that have no uid_map yet and whose tasks can
>   still map it, and the same one level down among tasks of an
>   unprivileged user
> - setns() into a namespace without a uid_map under one that maps host
> [ ... ]
> create user namespaces, or there is no tmpfs or no security.capability
> on it.  The ptrace cases also skip when Yama forbids ptrace.  The
> idmapped mount cases also skip when tmpfs can't be idmapped.
> 
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261112-b4-setfcap-userns-v10-0-84f6f7faa903@toxicpanda.com?part=3


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP
  2026-11-12 22:08 ` [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-11-12 22:23   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-12 22:23 UTC (permalink / raw)
  Cc: linux-security-module

> A uid 0 task that gave up CAP_SETFCAP and CAP_SYS_PTRACE can still
> attach to a task in a user namespace that another root task created,
> and have it write file capabilities for its root user.  For a target in
> a child namespace cap_ptrace_access_check() asks for CAP_SYS_PTRACE over
> that namespace, and cap_capable() grants every capability there to a
> task whose euid owns it, whatever it gave up:
> 
>   task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP,
>   unshare(CLONE_NEWUSER)              no CAP_SYS_PTRACE
>                                     ptrace(PTRACE_ATTACH, A)
>                                       euid == ns->owner -> allowed
>                                     make A write "0 0 1" to its uid_map,
>                                     or set file capabilities
> 
> The same goes for /proc/<pid>/mem, process_vm_writev() and
> [ ... ]
> 
> Suggested-by: Serge Hallyn <serge@hallyn.com>
> Link: https://lore.kernel.org/all/fd159e68-c613-4c7d-bfba-8af0a3f3a96e@hallyn.com/
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261112-b4-setfcap-userns-v10-0-84f6f7faa903@toxicpanda.com?part=2


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-11-12 22:23 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 22:08 [PATCH v10 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-11-12 22:08 ` [PATCH v10 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-11-12 22:17   ` sashiko-bot
2026-11-12 22:08 ` [PATCH v10 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-11-12 22:23   ` sashiko-bot
2026-11-12 22:08 ` [PATCH v10 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
2026-11-12 22:19   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.