All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0
@ 2026-10-26 15:41 Josef Bacik
  2026-10-26 15:41 ` [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches Josef Bacik
                   ` (5 more replies)
  0 siblings, 6 replies; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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/

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:

Every user namespace gets a setfcap_level when it's created, which is
how far up the creator's CAP_SETFCAP counted.  If its tasks can write
file capabilities for the root user of an ancestor, because the
namespace maps that root user or can still map it, setns() only lets in
tasks whose own CAP_SETFCAP reaches that far, and PTRACE_ATTACH asks the
tracer for the same.  The checks on the way to file capabilities then go
by the namespace instead of the cred: a uid_map write that maps the
parent's uid 0 (opener and writer), and setting security.capability.

The visible difference from v1 is the setns() restriction.  A task
without CAP_SETFCAP can't join, or attach to tasks in, a namespace that
maps the root user of an ancestor, such as host uid 0, and was created
by a task that had it.  A task that joins takes on the namespace's
reach.  Containers that don't map host uid 0, unprivileged users and
anything with CAP_SETFCAP aren't affected.

Testing: 6/6 runs every case on its own, 48 in all, as root in private
mount namespaces.  On vfs.all without the series 22 of them fail: the
ways around the rule, and the two v2 xattrs without a root user that are
now refused; with the series all pass, including the idmapped mount
ones.  The pidfd, namespaces, mount_setattr and ptrace selftests give
identical results with and without.  The TLA+ model, which has no
idmapped mounts, no v2 xattrs and maps only uid 0 of the parent, finds
no violation with the narrower setns() and ptrace checks, and still
finds the setns() bypass when the check is taken out.

--- Original email (v1) ---

Hello,

Commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
stops a root task that has given up CAP_SETFCAP from creating a user
namespace that maps uid 0 and then writing file capabilities in it that
the initial namespace honours.  The check only looks at the task that
creates the namespace, so if somebody who did have CAP_SETFCAP created
one, there are still several ways around it:

- setns() into their namespace and write uid_map from inside (patch 2)
- write "0 0 1" through a uid_map fd that they opened (patch 3)
- setns() into a namespace of theirs that already maps uid 0 and set
  security.capability there, no map write needed (patch 4)
- ptrace one of their tasks and have it do any of the above (patch 5)

On an unmodified kernel we took a uid 0 task with CAP_SETFCAP dropped
from its permitted, effective and bounding sets and, through each of
these, ended up with a file that a uid 1000 user execs with
CAP_SYS_ADMIN in its effective set.

Patch 1 adds cred->setfcap_level, which records how far up the
namespace tree a task's CAP_SETFCAP reached when it entered its
namespace, and patches 2-5 check it.  Patch 4 is the check that closes
the class, the map patches make the uid 0 map rule mean what
db2e718a4798 meant it to, and patch 5 keeps ptrace from borrowing what
the target is entitled to.

This does change behaviour.  Everything new is -EPERM:

- a task that entered a namespace without CAP_SETFCAP outside can't map
  uid 0 of the outside or write fscaps for that root user anymore
- a uid_map fd opened by a task with CAP_SETFCAP can't be used by a
  task without it to map uid 0
- if a privileged task maps "0 0 1" from the parent for a namespace
  created by a task without CAP_SETFCAP, that namespace can no longer
  write fscaps honoured outside
- PTRACE_ATTACH and PTRACE_TRACEME fail when the tracer gave up
  CAP_SETFCAP and CAP_SYS_PTRACE, the target didn't, and they share a
  root user

Rootless containers, privileged runtimes writing the map from the
parent, nested unprivileged namespaces and containers that don't map
host uid 0 aren't affected.  The ptrace check is one compare for
targets in the initial namespace and in namespaces entered without
the capability.

Testing: a set of flows run on the base and patched kernels, every
bypass route above gets -EPERM with the series and the 16 legitimate
flows behave the same.  The capabilities, namespaces, ptrace, pidfd and
proc selftests give the same results before and after. Thanks,

Josef

---
Changes in 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 (6):
      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
      selftests/namespaces: test the CAP_SETFCAP rule for user namespaces

 include/linux/capability.h                        |    6 +
 include/linux/user_namespace.h                    |    7 +-
 kernel/user_namespace.c                           |   95 +-
 security/commoncap.c                              |  184 ++-
 tools/testing/selftests/namespaces/Makefile       |    3 +-
 tools/testing/selftests/namespaces/config         |    3 +
 tools/testing/selftests/namespaces/setfcap_test.c | 1362 +++++++++++++++++++++
 7 files changed, 1626 insertions(+), 34 deletions(-)
---
base-commit: 2c3ba1f1cb416b81d617b92a24a4bcaa48d574e3
change-id: 20261006-b4-setfcap-userns-d63185a31ce3


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

* [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 15:48   ` sashiko-bot
  2026-10-26 15:41 ` [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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

create_user_ns() records in ns->parent_could_setfcap whether the creator
had CAP_SETFCAP in its effective set, and verify_root_map() trusts that
when a task in the new namespace maps the parent's uid 0.  A bool only
describes the parent, though.  File capabilities belong to the kuid of a
root user, and a namespace that maps the parent's uid 0 shares that kuid
with the parent and possibly with ancestors further up, so what matters
is how far up the creator's CAP_SETFCAP counted.

Replace the bool with ns->setfcap_level: CAP_SETFCAP held in the
namespace counts there and in its ancestors up to the one at that
->level.  cap_setfcap_level() computes it from the creator's cred.  A
creator that holds CAP_SETFCAP passes on the setfcap_level of its own
namespace, one that doesn't gets the new namespace's own level, and the
initial namespace has 0.

verify_root_map() keeps checking the same condition: the bool was false
exactly when the level is the namespace's own.

No functional change.

Reviewed-by: Bradley Morgan <brads@mainlining.org>
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 include/linux/capability.h     |  3 +++
 include/linux/user_namespace.h |  7 ++++---
 kernel/user_namespace.c        |  4 ++--
 security/commoncap.c           | 27 +++++++++++++++++++++++++++
 4 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index 622137f66f09..026974a5111b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -34,6 +34,7 @@ struct cpu_vfs_cap_data {
 #define _USER_CAP_HEADER_SIZE  (sizeof(struct __user_cap_header_struct))
 #define _KERNEL_CAP_T_SIZE     (sizeof(kernel_cap_t))
 
+struct cred;
 struct file;
 struct inode;
 struct dentry;
@@ -222,4 +223,6 @@ int get_vfs_caps_from_disk(const struct mnt_idmap *idmap,
 int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
 		      const void **ivalue, size_t size);
 
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns);
+
 #endif /* !_LINUX_CAPABILITY_H */
diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h
index 91232053775d..f3753b6b6d29 100644
--- a/include/linux/user_namespace.h
+++ b/include/linux/user_namespace.h
@@ -86,9 +86,10 @@ struct user_namespace {
 	kgid_t			group;
 	struct ns_common	ns;
 	unsigned long		flags;
-	/* parent_could_setfcap: true if the creator if this ns had CAP_SETFCAP
-	 * in its effective capability set at the child ns creation time. */
-	bool			parent_could_setfcap;
+	/* setfcap_level: CAP_SETFCAP held in this ns counts here and in the
+	 * ancestors up to the one at this ->level, see cap_setfcap_level().
+	 */
+	int			setfcap_level;
 
 #ifdef CONFIG_KEYS
 	/* List of joinable keyrings in this namespace.  Modification access of
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 50c374ed4687..a5a94367600d 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -125,7 +125,7 @@ int create_user_ns(struct cred *new)
 	if (!ns)
 		goto fail_dec;
 
-	ns->parent_could_setfcap = cap_raised(new->cap_effective, CAP_SETFCAP);
+	ns->setfcap_level = cap_setfcap_level(new, parent_ns);
 
 	ret = ns_common_init(ns);
 	if (ret)
@@ -921,7 +921,7 @@ static bool verify_root_map(const struct file *file,
 		 * the new namespace.  Verify that the parent had CAP_SETFCAP
 		 * when it unshared.
 		 * */
-		if (!file_ns->parent_could_setfcap)
+		if (file_ns->setfcap_level >= file_ns->level)
 			return false;
 	} else {
 		/* Process p1 is writing to uid_map of p2, who is in a child
diff --git a/security/commoncap.c b/security/commoncap.c
index d47ab3022343..4f49dba65426 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -131,6 +131,33 @@ int cap_capable(const struct cred *cred, struct user_namespace *target_ns,
 	return ret;
 }
 
+/**
+ * cap_setfcap_level - Determine how far up CAP_SETFCAP of a cred reaches
+ * @cred: The credentials to use
+ * @ns: The user namespace to look from
+ *
+ * File capabilities belong to the kuid of a namespace's root user, and the
+ * same kuid can be the root user of ancestors of that namespace.  Every task
+ * gets CAP_SETFCAP when it enters a user namespace, so having it there says
+ * nothing about those ancestors.  user_ns->setfcap_level does: it is taken
+ * from the creator when the namespace is created.
+ *
+ * Return: the ->level of the topmost namespace, from @ns upwards, for which
+ * CAP_SETFCAP of @cred counts; @ns->level + 1 if it doesn't even count over
+ * @ns, which includes an @ns that isn't @cred's namespace or a descendant.
+ */
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
+{
+	if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT) != 0)
+		return ns->level + 1;
+
+	if (cap_raised(cred->cap_effective, CAP_SETFCAP))
+		return cred->user_ns->setfcap_level;
+
+	/* All we have is that we own a child of our namespace. */
+	return cred->user_ns->level + 1;
+}
+
 /**
  * cap_settime - Determine whether the current process may set the system clock
  * @ts: The time to set

-- 
2.55.0


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

* [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
  2026-10-26 15:41 ` [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 15:54   ` sashiko-bot
  2026-10-26 15:41 ` [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0 Josef Bacik
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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, Josef Bacik

Mapping uid 0 of the parent into a user namespace requires CAP_SETFCAP,
because a task in such a namespace can write file capabilities that are
honoured in the parent.  A task that is already in the namespace has a
full capability set there and can't be asked any more, so
verify_root_map() goes by what the creator had, now ns->setfcap_level.

But setns() lets other tasks in, and they get the full set as well:

  task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP
  unshare(CLONE_NEWUSER)
                                    setns(A's user ns)
                                      allowed, euid == ns->owner
                                    write "0 0 1" to /proc/self/uid_map
                                      ns->setfcap_level is A's -> allowed

or, if A's namespace maps uid 0 already, B sets security.capability on a
file.  Either way B ends up with file capabilities that are honoured in
the initial namespace, which it could not have written there, nor in a
namespace it created itself.

Refuse setns() into a user namespace whose tasks can write file
capabilities for the root user of an ancestor that the joiner's
CAP_SETFCAP doesn't reach.  That is the case when the namespace maps
such a root user, or has no uid map yet and its parent maps one.
cred_may_setfcap_userns() checks it, and cred_setfcap_reaches() is the
comparison it and the later checks use.  Then every task in such a
namespace is entitled to what its setfcap_level says, and the checks on
the way to file capabilities can go by the namespace.

An idmapped mount can store a root user that the namespace doesn't map,
for example a "0 100000" container whose rootfs maps 100000 to host uid
0.  That is a deliberate setup and up to whoever creates the mount, so
it is not considered here.

The change in behaviour is that a task without CAP_SETFCAP gets -EPERM
when it tries to setns() into a user namespace that maps the root user
of an ancestor, and was created by a task whose CAP_SETFCAP reached that
far.  Containers that don't map host uid 0, tasks with CAP_SETFCAP, and
unprivileged users joining their own namespaces are not affected.

Link: https://lore.kernel.org/all/20261021-bioenergie-allheilmittel-undurchdringlichen-35666eb62cff@brauner/
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 include/linux/capability.h |  3 +++
 kernel/user_namespace.c    |  6 +++++
 security/commoncap.c       | 58 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+)

diff --git a/include/linux/capability.h b/include/linux/capability.h
index 026974a5111b..e4199b03668b 100644
--- a/include/linux/capability.h
+++ b/include/linux/capability.h
@@ -224,5 +224,8 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
 		      const void **ivalue, size_t size);
 
 int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns);
+bool cred_setfcap_reaches(const struct cred *cred, struct user_namespace *ns,
+			  int level);
+bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns);
 
 #endif /* !_LINUX_CAPABILITY_H */
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index a5a94367600d..7bd3692c2f3e 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -1390,6 +1390,12 @@ static int userns_install(struct nsset *nsset, struct ns_common *ns)
 	if (!ns_capable(user_ns, CAP_SYS_ADMIN))
 		return -EPERM;
 
+	/* Tasks in the namespace may have CAP_SETFCAP that counts outside of
+	 * it, so only let in tasks whose own counts as far.
+	 */
+	if (!cred_may_setfcap_userns(current_cred(), user_ns))
+		return -EPERM;
+
 	cred = nsset_cred(nsset);
 	if (!cred)
 		return -EINVAL;
diff --git a/security/commoncap.c b/security/commoncap.c
index 4f49dba65426..8a9d998b1897 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -158,6 +158,64 @@ int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
 	return cred->user_ns->level + 1;
 }
 
+/**
+ * cred_setfcap_reaches - Check how far up CAP_SETFCAP of a cred reaches
+ * @cred: The credentials to use
+ * @ns: The user namespace to look from
+ * @level: The ->level of the ancestor of @ns it has to count in
+ *
+ * Return: true if CAP_SETFCAP of @cred counts in @ns and in its ancestors
+ * up to the one at @level.
+ */
+bool cred_setfcap_reaches(const struct cred *cred, struct user_namespace *ns,
+			  int level)
+{
+	return cap_setfcap_level(cred, ns) <= level;
+}
+
+/*
+ * The topmost ancestor of @ns whose root user tasks in @ns can write file
+ * capabilities for: within @ns->setfcap_level, and mapped into @ns, or into
+ * its parent while @ns has no uid_map yet.  NULL if there is none.  An
+ * idmapped mount can store other root users, but that is up to whoever sets
+ * up the mount.
+ */
+static struct user_namespace *setfcap_top(struct user_namespace *ns)
+{
+	struct user_namespace *seen, *p, *top = NULL;
+
+	if (ns->setfcap_level >= ns->level)
+		return NULL;
+
+	seen = READ_ONCE(ns->uid_map.nr_extents) ? ns : ns->parent;
+	for (p = ns->parent; p; p = p->parent) {
+		if (p->level < ns->setfcap_level)
+			break;
+		if (kuid_has_mapping(seen, make_kuid(p, 0)))
+			top = p;
+	}
+
+	return top;
+}
+
+/**
+ * cred_may_setfcap_userns - Check whether a cred covers what a userns can do
+ * @cred: The credentials to use
+ * @ns: The user namespace
+ *
+ * Tasks in @ns can have CAP_SETFCAP that counts in ancestors of @ns, see
+ * cap_setfcap_level().  Check that CAP_SETFCAP of @cred reaches as far up as
+ * tasks in @ns can make use of theirs.
+ *
+ * Return: true if it does.
+ */
+bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns)
+{
+	struct user_namespace *top = setfcap_top(ns);
+
+	return !top || cred_setfcap_reaches(cred, ns->parent, top->level);
+}
+
 /**
  * cap_settime - Determine whether the current process may set the system clock
  * @ts: The time to set

-- 
2.55.0


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

* [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
  2026-10-26 15:41 ` [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches Josef Bacik
  2026-10-26 15:41 ` [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 16:00   ` sashiko-bot
  2026-10-26 15:41 ` [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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, Josef Bacik

verify_root_map() looks at the cred that opened the map file: it asks
whether it has CAP_SETFCAP over the parent, or, for an opener inside the
namespace, whether the creator had.  Two things are missing.

The writer isn't checked.  For every other privileged mapping
new_idmap_permitted() wants the capability from the opener and from the
task that calls write(), but a map that consists of the single line
"0 0 1" takes the unprivileged branch, where only the opener's euid
counts.  So an open uid_map file is a token for mapping uid 0:

  task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP
  fd = open("/proc/<pid>/uid_map")
  B gets fd by inheritance
  or SCM_RIGHTS
                                    write(fd, "0 0 1")
                                      opener had CAP_SETFCAP -> allowed

And the check only happens for a map with an extent that starts at the
parent's uid 0, and only asks for CAP_SETFCAP over the parent.  But the
root user of an ancestor can be mapped into the parent at any uid, and
the parent's own root user can be the root user of ancestors further up.
A task without CAP_SETFCAP that unshared a namespace, which a privileged
task then mapped "1000 0 1" for, can unshare again and map "0 1000 1":
host uid 0 is uid 0 of the new namespace, and nothing was checked.

Find every ancestor whose root user the new map contains, at whatever uid
it has in the parent, and require that the opener and the writer both
reach the topmost of them.  A task outside the namespace that is being
mapped needs CAP_SETFCAP over the parent, reaching that far by
cap_setfcap_level().  A task inside it needs CAP_SETFCAP there, and
ns->setfcap_level has to reach that far: a task in the namespace that
gave up CAP_SETFCAP would otherwise map uid 0 and get it back from
execve().

This only affects maps that contain the root user of the parent or of
one of its ancestors.  A write by a task without CAP_SETFCAP to a file
opened by one with it, and a map by a task whose CAP_SETFCAP stops short
of the root user it passes on, now fail with -EPERM.

Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 kernel/user_namespace.c | 87 ++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 64 insertions(+), 23 deletions(-)

diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 7bd3692c2f3e..db078dd6406a 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -881,15 +881,55 @@ VISIBLE_IF_KUNIT int uid_gid_map_sort(struct uid_gid_map *map)
 }
 EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort);
 
+static bool map_has_id(struct uid_gid_map *map, u32 id)
+{
+	struct uid_gid_extent *extent;
+	int idx;
+
+	for (idx = 0; idx < map->nr_extents; idx++) {
+		if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
+			extent = &map->extent[idx];
+		else
+			extent = &map->forward[idx];
+		if (id >= extent->lower_first &&
+		    id - extent->lower_first < extent->count)
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * The lowest ->level among @parent and its ancestors whose root user @new_map
+ * maps, whatever uid that root user has in @parent; INT_MAX if there is none.
+ */
+static int map_root_level(struct user_namespace *parent,
+			  struct uid_gid_map *new_map)
+{
+	struct user_namespace *p;
+	int level = INT_MAX;
+	uid_t id;
+
+	for (p = parent; p; p = p->parent) {
+		id = from_kuid(parent, make_kuid(p, 0));
+		if (id != (uid_t)-1 && map_has_id(new_map, id))
+			level = p->level;
+	}
+
+	return level;
+}
+
 /**
  * verify_root_map() - check the uid 0 mapping
  * @file: idmapping file
  * @map_ns: user namespace of the target process
  * @new_map: requested idmap
  *
- * If a process requests mapping parent uid 0 into the new ns, verify that the
- * process writing the map had the CAP_SETFCAP capability as the target process
- * will be able to write fscaps that are valid in ancestor user namespaces.
+ * If a process requests mapping the root user of the parent ns, or of one of
+ * its ancestors, into the new ns, verify that the process that opened the map
+ * file and the process writing the map had the CAP_SETFCAP capability as the
+ * target process will be able to write fscaps that are valid in ancestor user
+ * namespaces.
  *
  * Return: true if the mapping is allowed, false if not.
  */
@@ -897,42 +937,43 @@ static bool verify_root_map(const struct file *file,
 			    struct user_namespace *map_ns,
 			    struct uid_gid_map *new_map)
 {
-	int idx;
 	const struct user_namespace *file_ns = file->f_cred->user_ns;
-	struct uid_gid_extent *extent0 = NULL;
-
-	for (idx = 0; idx < new_map->nr_extents; idx++) {
-		if (new_map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS)
-			extent0 = &new_map->extent[idx];
-		else
-			extent0 = &new_map->forward[idx];
-		if (extent0->lower_first == 0)
-			break;
-
-		extent0 = NULL;
-	}
+	int level;
 
-	if (!extent0)
+	level = map_root_level(map_ns->parent, new_map);
+	if (level == INT_MAX)
 		return true;
 
 	if (map_ns == file_ns) {
 		/* The process unshared its ns and is writing to its own
 		 * /proc/self/uid_map.  User already has full capabilites in
-		 * the new namespace.  Verify that the parent had CAP_SETFCAP
-		 * when it unshared.
+		 * the new namespace.  Verify that it still has CAP_SETFCAP
+		 * there, and that the parent had it when it unshared, as far up
+		 * as those root users go.
 		 * */
-		if (file_ns->setfcap_level >= file_ns->level)
+		if (!file_ns_capable(file, map_ns, CAP_SETFCAP) ||
+		    file_ns->setfcap_level > level)
 			return false;
 	} else {
 		/* Process p1 is writing to uid_map of p2, who is in a child
 		 * user namespace to p1's.  Verify that the opener of the map
 		 * file has CAP_SETFCAP against the parent of the new map
-		 * namespace */
-		if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP))
+		 * namespace, and as far up as those root users go.
+		 */
+		if (!file_ns_capable(file, map_ns->parent, CAP_SETFCAP) ||
+		    !cred_setfcap_reaches(file->f_cred, map_ns->parent, level))
 			return false;
 	}
 
-	return true;
+	/* The file may have been handed to someone else since it was opened,
+	 * so the same goes for the process that is doing the write.
+	 */
+	if (map_ns == current_user_ns())
+		return ns_capable(map_ns, CAP_SETFCAP) &&
+		       map_ns->setfcap_level <= level;
+
+	return ns_capable(map_ns->parent, CAP_SETFCAP) &&
+	       cred_setfcap_reaches(current_cred(), map_ns->parent, level);
 }
 
 /*

-- 
2.55.0


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

* [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
                   ` (2 preceding siblings ...)
  2026-10-26 15:41 ` [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 16:05   ` sashiko-bot
  2026-10-26 15:41 ` [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
  2026-10-26 15:41 ` [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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, Josef Bacik

Commit db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
keeps a root process without CAP_SETFCAP from creating a user namespace
that maps uid 0, in which it could attach capabilities to a file that
are then honoured outside.  But the map doesn't have to be written by
that process.  A privileged task can write it from the parent, and the
file capabilities are not checked against the outside at all:

  task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP
                                    unshare(CLONE_NEWUSER)
                                      ns->setfcap_level = 1
  write "0 0 1" to B's uid_map, gid_map
    A has CAP_SETFCAP -> allowed
                                    setxattr(file, "security.capability")
                                      rootid = kuid 0
  uid 1000 in the initial namespace:
  execve(file)
    file capabilities apply

The map doesn't even have to show uid 0 of the parent as uid 0, since a
v3 xattr can name any mapped uid as the root user.

Check at the place where it matters, in cap_convert_nscap().  On an
idmapped mount the root user named in the xattr has two identities: the
kuid seen through this mount, and the kuid that is stored and seen
through every other mount of the filesystem.  If either of them is the
root user of an ancestor of the caller's namespace too, CAP_SETFCAP has
to reach up to there, which ns->setfcap_level tells.

A v2 xattr skips the conversion when the writer has CAP_SETFCAP over the
filesystem's namespace, and belongs to that namespace's root user.  It
gets the same check: if that root user is out of reach, the xattr goes
through the conversion instead, which names the root user of the
writer's namespace and refuses it when that one is out of reach too.
Otherwise a task that unshared without CAP_SETFCAP could mount a tmpfs
in its namespace, write a v2 xattr on it, and have it honoured through
an overlay mounted from the initial namespace.  If the filesystem's
namespace doesn't map uid 0 at all, the v2 xattr has no root user and
nobody could run the file with it, so it is refused with -EINVAL instead
of stored.

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 | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 2 deletions(-)

diff --git a/security/commoncap.c b/security/commoncap.c
index 8a9d998b1897..4e0b00b7b1a3 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -216,6 +216,29 @@ bool cred_may_setfcap_userns(const struct cred *cred, struct user_namespace *ns)
 	return !top || cred_setfcap_reaches(cred, ns->parent, top->level);
 }
 
+/**
+ * cap_root_level - Find the topmost namespace in which a kuid is the root user
+ * @kuid: The kuid to look for
+ * @ns: The user namespace to start from
+ *
+ * Return: the lowest ->level among @ns and its ancestors in which @kuid is
+ * uid 0, which is how far up file capabilities with that root user are
+ * honoured; INT_MAX if there is no such namespace.
+ */
+static int cap_root_level(kuid_t kuid, struct user_namespace *ns)
+{
+	int level = INT_MAX;
+
+	for (;; ns = ns->parent) {
+		if (from_kuid(ns, kuid) == 0)
+			level = ns->level;
+		if (ns == &init_user_ns)
+			break;
+	}
+
+	return level;
+}
+
 /**
  * cap_settime - Determine whether the current process may set the system clock
  * @ts: The time to set
@@ -671,19 +694,46 @@ int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry,
 		return -EINVAL;
 	if (!capable_wrt_inode_uidgid(idmap, inode, CAP_SETFCAP))
 		return -EPERM;
-	if (size == XATTR_CAPS_SZ_2 && (idmap == &nop_mnt_idmap))
-		if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP))
+	/*
+	 * A v2 xattr belongs to the root user of the filesystem's namespace,
+	 * which may be the root user of ancestors of our namespace as well.
+	 * There may be none, then nobody could ever run the file with these
+	 * capabilities.  Only write it unconverted if that root user is
+	 * within reach, the checks below handle everything else.
+	 */
+	if (size == XATTR_CAPS_SZ_2 && idmap == &nop_mnt_idmap &&
+	    ns_capable(fs_ns, CAP_SETFCAP)) {
+		kuid_t fsroot = make_kuid(fs_ns, 0);
+
+		if (!uid_valid(fsroot))
+			return -EINVAL;
+		if (cap_root_level(fsroot, task_ns) >= task_ns->setfcap_level)
 			/* user is privileged, just write the v2 */
 			return size;
+	}
 
 	vfsrootid = rootid_from_xattr(*ivalue, size, task_ns);
 	if (!vfsuid_valid(vfsrootid))
 		return -EINVAL;
 
+	/*
+	 * The root user may be the root user of ancestors of our namespace as
+	 * well.  CAP_SETFCAP that we got for entering it doesn't cover those.
+	 * On an idmapped mount the root user is vfsrootid as seen through
+	 * this mount and rootid as seen through every other mount of the
+	 * filesystem, so both have to stay within reach.
+	 */
+	if (cap_root_level(vfsuid_into_kuid(vfsrootid), task_ns) <
+	    task_ns->setfcap_level)
+		return -EPERM;
+
 	rootid = from_vfsuid(idmap, fs_ns, vfsrootid);
 	if (!uid_valid(rootid))
 		return -EINVAL;
 
+	if (cap_root_level(rootid, task_ns) < task_ns->setfcap_level)
+		return -EPERM;
+
 	nsrootid = from_kuid(fs_ns, rootid);
 	if (nsrootid == -1)
 		return -EINVAL;

-- 
2.55.0


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

* [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
                   ` (3 preceding siblings ...)
  2026-10-26 15:41 ` [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 15:56   ` sashiko-bot
  2026-10-26 15:41 ` [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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, Josef Bacik

Tasks in a user namespace created by a task with CAP_SETFCAP may map uid
0 of the parent and write file capabilities that are honoured there.
That is the one privilege they keep outside of their namespace.  setns()
no longer lets anybody in who doesn't have it, but the ptrace checks
don't know about it: for a target in another user namespace
cap_ptrace_access_check() is satisfied with CAP_SYS_PTRACE over the
target's namespace, which the owner rule hands to any task with the
right euid.

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

The same goes for /proc/<pid>/mem, process_vm_writev() and pidfd_getfd().

Require for PTRACE_MODE_ATTACH and PTRACE_TRACEME what setns() requires
to get into the target's namespace: CAP_SETFCAP of the tracer has to
reach the root users of ancestors that tasks in the namespace can write
file capabilities for, or the tracer has CAP_SYS_PTRACE over the topmost
of those ancestors, which gives control of tasks there that have
CAP_SETFCAP anyway.  As in setns(), root users stored through an
idmapped mount are not considered.  A tracer in the target's namespace
got in with as much and always passes.

Targets in the initial namespace, in namespaces created without
CAP_SETFCAP (what unprivileged users create) and in namespaces that
don't map the root user of an ancestor (the usual container) are not
affected.  What remains is a tracer that gave up CAP_SETFCAP and
CAP_SYS_PTRACE and a target in a namespace that maps the root user of
an ancestor and was created by a task that didn't give it up, which the
tracer can't setns() into either.  PTRACE_MODE_READ is unchanged.

Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 security/commoncap.c | 45 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 41 insertions(+), 4 deletions(-)

diff --git a/security/commoncap.c b/security/commoncap.c
index 4e0b00b7b1a3..91ed4a5f4b25 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -254,14 +254,41 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz)
 	return 0;
 }
 
+/*
+ * Tasks in a user namespace have CAP_SETFCAP that can count in ancestors of
+ * the namespace, see cap_setfcap_level().  That is a privilege outside of the
+ * namespace, so capabilities over the namespace are not enough to take
+ * control of such a task.
+ */
+static bool cap_covers_setfcap(const struct cred *cred,
+			       const struct cred *child_cred)
+{
+	struct user_namespace *ns = child_cred->user_ns, *top;
+
+	/* setns() only lets in tasks whose CAP_SETFCAP reaches as far. */
+	if (cred->user_ns == ns)
+		return true;
+
+	top = setfcap_top(ns);
+	if (!top)
+		return true;
+
+	/* CAP_SYS_PTRACE up there gives control of tasks with CAP_SETFCAP. */
+	return cred_setfcap_reaches(cred, ns->parent, top->level) ||
+	       !cap_capable(cred, top, CAP_SYS_PTRACE, CAP_OPT_NOAUDIT);
+}
+
 /**
  * cap_ptrace_access_check - Determine whether the current process may access
  *			   another
  * @child: The process to be accessed
  * @mode: The mode of attachment.
  *
- * If we are in the same or an ancestor user_ns and have all the target
- * task's capabilities, then ptrace access is allowed.
+ * For PTRACE_MODE_ATTACH, if the target task may make use of CAP_SETFCAP in
+ * an ancestor of its user_ns that our CAP_SETFCAP or CAP_SYS_PTRACE doesn't
+ * reach, then ptrace access is denied.
+ * Otherwise, if we are in the same or an ancestor user_ns and have all the
+ * target task's capabilities, then ptrace access is allowed.
  * If we have the ptrace capability to the target user_ns, then ptrace
  * access is allowed.
  * Else denied.
@@ -282,11 +309,15 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
 		caller_caps = &cred->cap_effective;
 	else
 		caller_caps = &cred->cap_permitted;
+	if ((mode & PTRACE_MODE_ATTACH) &&
+	    !cap_covers_setfcap(cred, child_cred))
+		goto deny;
 	if (cred->user_ns == child_cred->user_ns &&
 	    cap_issubset(child_cred->cap_permitted, *caller_caps))
 		goto out;
 	if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
 		goto out;
+deny:
 	ret = -EPERM;
 out:
 	rcu_read_unlock();
@@ -297,8 +328,11 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
  * cap_ptrace_traceme - Determine whether another process may trace the current
  * @parent: The task proposed to be the tracer
  *
- * If parent is in the same or an ancestor user_ns and has all current's
- * capabilities, then ptrace access is allowed.
+ * If current may make use of CAP_SETFCAP in an ancestor of its user_ns that
+ * parent's CAP_SETFCAP or CAP_SYS_PTRACE doesn't reach, then ptrace access
+ * is denied.
+ * Otherwise, if parent is in the same or an ancestor user_ns and has all
+ * current's capabilities, then ptrace access is allowed.
  * If parent has the ptrace capability to current's user_ns, then ptrace
  * access is allowed.
  * Else denied.
@@ -314,11 +348,14 @@ int cap_ptrace_traceme(struct task_struct *parent)
 	rcu_read_lock();
 	cred = __task_cred(parent);
 	child_cred = current_cred();
+	if (!cap_covers_setfcap(cred, child_cred))
+		goto deny;
 	if (cred->user_ns == child_cred->user_ns &&
 	    cap_issubset(child_cred->cap_permitted, cred->cap_permitted))
 		goto out;
 	if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE))
 		goto out;
+deny:
 	ret = -EPERM;
 out:
 	rcu_read_unlock();

-- 
2.55.0


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

* [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces
  2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
                   ` (4 preceding siblings ...)
  2026-10-26 15:41 ` [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-10-26 15:41 ` Josef Bacik
  2026-10-26 16:00   ` sashiko-bot
  5 siblings, 1 reply; 13+ messages in thread
From: Josef Bacik @ 2026-10-26 15:41 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, Josef Bacik

Add tests for who may map the root user of an ancestor user namespace,
write file capabilities that are honoured in an ancestor, join a
namespace whose tasks may do so, or attach to such a task.

Each case runs as root in a private mount namespace with a tmpfs on
/tmp.  The ones that must be refused are the ways around the rule that
the preceding patches close: setns() into a namespace created with
CAP_SETFCAP, a uid_map fd opened by somebody else, pidfd_getfd(), a
writer inside the namespace that gave up CAP_SETFCAP, the root user of
an ancestor mapped at a non-zero uid, v2 and v3 xattrs written after a
privileged task mapped uid 0 for a namespace created without
CAP_SETFCAP, a joiner of such a namespace, and PTRACE_ATTACH.  The ones
that must keep working are a task mapping its own namespace, a
privileged task mapping it from the parent, unprivileged users nesting
namespaces, joiners that have CAP_SETFCAP, file capabilities in all of
those, containers that don't map host uid 0, and idmapped mounts of a
container's rootfs.  Where file capabilities are written the test also
checks whether uid 1000 gets CAP_SYS_ADMIN from them, by running a copy
of itself.

The idmapped mount cases skip when tmpfs can't be idmapped.

Assisted-by: LLM
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
 tools/testing/selftests/namespaces/Makefile       |    3 +-
 tools/testing/selftests/namespaces/config         |    3 +
 tools/testing/selftests/namespaces/setfcap_test.c | 1362 +++++++++++++++++++++
 3 files changed, 1367 insertions(+), 1 deletion(-)

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..dfbabc35cd8a 100644
--- a/tools/testing/selftests/namespaces/config
+++ b/tools/testing/selftests/namespaces/config
@@ -5,3 +5,6 @@ CONFIG_USER_NS=y
 CONFIG_PID_NS=y
 CONFIG_NET_NS=y
 CONFIG_CGROUPS=y
+CONFIG_TMPFS=y
+CONFIG_TMPFS_XATTR=y
+CONFIG_KEYS=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..0a82b239b368
--- /dev/null
+++ b/tools/testing/selftests/namespaces/setfcap_test.c
@@ -0,0 +1,1362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Who may map the root user of an ancestor user namespace, write file
+ * capabilities that are honoured in an ancestor, join a namespace whose
+ * tasks may do so, or take control of such a task.
+ *
+ * "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/keyctl.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);
+}
+
+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 long join(int pid)
+{
+	char path[64];
+	int fd, r;
+
+	snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+	fd = open(path, O_RDONLY);
+	if (fd < 0)
+		return SETUP_ERROR;
+	r = setns(fd, CLONE_NEWUSER);
+	close(fd);
+	return r ? -errno : 0;
+}
+
+/* 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;
+	ssize_t n;
+
+	unlink(path);
+	in = open("/proc/self/exe", O_RDONLY);
+	out = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0755);
+	if (in < 0 || out < 0)
+		return -1;
+	while ((n = read(in, buf, sizeof(buf))) > 0)
+		if (write(out, buf, n) != n)
+			return -1;
+	if (fchown(out, uid, uid) || fchmod(out, 0755))
+		return -1;
+	close(in);
+	close(out);
+	return 0;
+}
+
+/* Does U get CAP_SYS_ADMIN from the file capabilities of path? */
+static int user_gets_sys_admin(const char *path)
+{
+	int pid, st;
+
+	pid = fork();
+	if (!pid) {
+		become_user();
+		setenv("SETFCAP_TEST_CAPS", "1", 1);
+		execl(path, path, NULL);
+		_exit(1);
+	}
+	waitpid(pid, &st, 0);
+	return WIFEXITED(st) && WEXITSTATUS(st) == CAPS_HAS_SYS_ADMIN;
+}
+
+/* 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) {
+		r = fn(arg);
+		if (write(p[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	close(p[1]);
+	if (read(p[0], &r, sizeof(r)) != sizeof(r))
+		r = SETUP_ERROR;
+	close(p[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 */
+};
+
+static struct holder spawn_holder(enum holder_kind kind, int map_fd_at)
+{
+	struct holder h = { -1, -1 };
+	int ready[2], go[2];
+	char c;
+
+	if (pipe(ready) || pipe(go))
+		return h;
+	h.pid = fork();
+	if (!h.pid) {
+		close(ready[0]);
+		close(go[1]);
+		if (kind == HOLDER_RESTRICTED)
+			restrict_root();
+		if (kind == HOLDER_USER)
+			become_user();
+		if (unshare(CLONE_NEWUSER))
+			_exit(1);
+		if (kind == HOLDER_USER && map_user())
+			_exit(1);
+		if (map_fd_at)
+			dup2(open("/proc/self/uid_map", O_WRONLY), map_fd_at);
+		if (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]);
+	if (read(ready[0], &c, 1) != 1)
+		h.pid = -1;
+	close(ready[0]);
+	h.go = go[1];
+	return h;
+}
+
+/* ... 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))
+		h.pid = -1;
+	return h;
+}
+
+static void reap_holder(struct holder h)
+{
+	if (h.pid <= 0)
+		return;
+	close(h.go);
+	kill(h.pid, SIGKILL);
+	waitpid(h.pid, NULL, 0);
+}
+
+#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 /mnt */
+static int idmap_tmp(int pid)
+{
+	struct mount_attr attr = { .attr_set = MOUNT_ATTR_IDMAP };
+	char path[64];
+	int tfd, nsfd;
+
+	mkdir("/mnt", 0755);
+	snprintf(path, sizeof(path), "/proc/%d/ns/user", pid);
+	nsfd = open(path, O_RDONLY);
+	tfd = syscall(__NR_open_tree, AT_FDCWD, "/tmp",
+		      OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
+	if (nsfd < 0 || tfd < 0)
+		return -1;
+	attr.userns_fd = nsfd;
+	if (syscall(__NR_mount_setattr, tfd, "", AT_EMPTY_PATH, &attr,
+		    sizeof(attr)))
+		return -1;
+	if (syscall(__NR_move_mount, tfd, "", AT_FDCWD, "/mnt",
+		    MOVE_MOUNT_F_EMPTY_PATH))
+		return -1;
+	close(tfd);
+	close(nsfd);
+	return 0;
+}
+
+/* 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);
+	mkdir("/mnt", 0755);
+}
+
+FIXTURE(setfcap) {};
+
+FIXTURE_SETUP(setfcap)
+{
+	if (geteuid())
+		SKIP(return, "needs root");
+	prepare(_metadata);
+}
+
+FIXTURE_TEARDOWN(setfcap) {}
+
+/* ---------------------------------------------------------------------- */
+/* Mapping uid 0 of the parent, or the root user of an ancestor at any uid */
+
+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);
+}
+
+static long x_joins_and_maps(void *a)
+{
+	long r;
+
+	restrict_root();
+	r = join(PID(a));
+	if (r)
+		return r;
+	return write_map(0, "0 0 1\n");
+}
+
+/* X can't get into R's namespace to map uid 0 from inside */
+TEST_F(setfcap, map_after_setns)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins_and_maps, &h), -EPERM);
+	reap_holder(h);
+}
+
+static long x_writes_fd(void *a)
+{
+	restrict_root();
+	return wr(*(int *)a, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_through_borrowed_fd)
+{
+	struct holder h;
+	char path[64];
+	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);
+	EXPECT_EQ(in_child(x_writes_fd, &fd), -EPERM);
+	close(fd);
+	reap_holder(h);
+}
+
+static long x_steals_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;
+	return wr(fd, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_through_pidfd_getfd)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 100);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_steals_fd, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* the same one level down, among tasks of an unprivileged user */
+static long u_nested(void *a)
+{
+	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(x_joins_and_maps, &h);
+	reap_holder(h);
+	return r;
+}
+
+TEST_F(setfcap, map_after_setns_nested_unprivileged)
+{
+	EXPECT_EQ(in_child(u_nested, NULL), -EPERM);
+}
+
+static long x_joins_unshares_maps(void *a)
+{
+	long r;
+
+	restrict_root();
+	r = join(PID(a));
+	if (r)
+		return r;
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_after_setns_and_unshare)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins_unshares_maps, &h), -EPERM);
+	reap_holder(h);
+}
+
+/* the writer opened the map, then went into an unrelated namespace */
+static long r_opens_then_leaves(void *a)
+{
+	char path[64];
+	int fd;
+
+	snprintf(path, sizeof(path), "/proc/%d/uid_map", PID(a));
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return SETUP_ERROR;
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	return wr(fd, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_writer_left_for_other_ns)
+{
+	struct holder h;
+
+	h = spawn_holder(HOLDER_ROOT, 0);
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(r_opens_then_leaves, &h), -EPERM);
+	reap_holder(h);
+}
+
+/*
+ * R maps host uid 0 as uid 1000 of X's namespace.  X maps "0 1000 1" one
+ * level down: host uid 0 is uid 0 there without any extent at uid 0.
+ */
+static long x_nonzero_root(void *a)
+{
+	int s1[2], s2[2], res[2], pid;
+	long r = SETUP_ERROR;
+	char c;
+
+	if (pipe(s1) || pipe(s2) || pipe(res))
+		return SETUP_ERROR;
+	pid = fork();
+	if (!pid) {
+		restrict_root();
+		if (unshare(CLONE_NEWUSER) || write(s1[1], "u", 1) != 1 ||
+		    read(s2[0], &c, 1) != 1)
+			_exit(1);
+		if (unshare(CLONE_NEWUSER))
+			r = SETUP_ERROR;
+		else
+			r = write_map(0, "0 1000 1\n");
+		if (write(res[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	if (read(s1[0], &c, 1) != 1 || map_both(pid, "1000 0 1\n") ||
+	    write(s2[1], "g", 1) != 1 || read(res[0], &r, sizeof(r)) != sizeof(r))
+		r = SETUP_ERROR;
+	waitpid(pid, NULL, 0);
+	return r;
+}
+
+TEST_F(setfcap, map_ancestor_root_at_nonzero_uid)
+{
+	EXPECT_EQ(in_child(x_nonzero_root, NULL), -EPERM);
+}
+
+/*
+ * R's namespace has no map yet.  R opens its uid_map and hands the fd to a
+ * task of the same namespace that gave up CAP_SETFCAP (but kept it in the
+ * bounding set, so it would get it back from execve() as uid 0).
+ */
+static long dropped_writes_inherited_fd(void *a)
+{
+	int fd, pid, st;
+	long r = SETUP_ERROR;
+	int p[2];
+
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	fd = open("/proc/self/uid_map", O_WRONLY);
+	if (fd < 0 || pipe(p))
+		return SETUP_ERROR;
+	pid = fork();
+	if (!pid) {
+		drop_setfcap_keep_bset();
+		r = wr(fd, "0 0 1\n");
+		if (write(p[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	if (read(p[0], &r, sizeof(r)) != sizeof(r))
+		r = SETUP_ERROR;
+	waitpid(pid, &st, 0);
+	return r;
+}
+
+TEST_F(setfcap, map_in_ns_writer_without_setfcap)
+{
+	EXPECT_EQ(in_child(dropped_writes_inherited_fd, NULL), -EPERM);
+}
+
+/* ... and the same task opening its own map */
+static long dropped_opens_and_writes(void *a)
+{
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	drop_setfcap_keep_bset();
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_in_ns_opener_without_setfcap)
+{
+	EXPECT_EQ(in_child(dropped_opens_and_writes, 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);
+}
+
+static long session_to_parent(void *a)
+{
+	if (syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0 ||
+	    syscall(SYS_keyctl, KEYCTL_SESSION_TO_PARENT) < 0)
+		return SETUP_ERROR;
+	return 0;
+}
+
+/* a cred replaced by key_change_session_keyring() keeps what it had */
+static long r_new_session_keyring_maps(void *a)
+{
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	if (in_child(session_to_parent, NULL))
+		return SETUP_ERROR;
+	return write_map(0, "0 0 1\n");
+}
+
+TEST_F(setfcap, map_after_session_to_parent)
+{
+	EXPECT_EQ(in_child(r_new_session_keyring_maps, NULL), 0);
+}
+
+/* ---------------------------------------------------------------------- */
+/* File capabilities that are honoured in an ancestor                      */
+
+static long x_joins_sets_fcap(void *a)
+{
+	long r;
+
+	restrict_root();
+	r = join(PID(a));
+	if (r)
+		return r;
+	return set_fcap("/tmp/x", -1);
+}
+
+TEST_F(setfcap, fcap_after_setns)
+{
+	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);
+	reap_holder(h);
+	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
+}
+
+static long x_joins_sets_fcap_rootid5(void *a)
+{
+	long r;
+
+	restrict_root();
+	r = join(PID(a));
+	if (r)
+		return r;
+	return set_fcap("/tmp/x", 5);
+}
+
+/* a v3 xattr can name the root user by any uid it has in the namespace */
+TEST_F(setfcap, fcap_after_setns_v3_rootid)
+{
+	struct holder h;
+
+	h = spawn_mapped(HOLDER_ROOT, "5 0 1\n");
+	ASSERT_GT(h.pid, 0);
+	EXPECT_EQ(in_child(x_joins_sets_fcap_rootid5, &h), -EPERM);
+	reap_holder(h);
+	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
+}
+
+/* X unshares and R maps "0 0 1" for it from the parent */
+static long x_unshares_r_maps(void *a)
+{
+	int version = *(int *)a;
+	int s1[2], s2[2], res[2], pid;
+	long r = SETUP_ERROR;
+	char c;
+
+	if (pipe(s1) || pipe(s2) || pipe(res))
+		return SETUP_ERROR;
+	pid = fork();
+	if (!pid) {
+		restrict_root();
+		if (unshare(CLONE_NEWUSER) || write(s1[1], "u", 1) != 1 ||
+		    read(s2[0], &c, 1) != 1)
+			_exit(1);
+		if (version == 2) {
+			/* a v2 xattr on a tmpfs of its own */
+			if (unshare(CLONE_NEWNS) ||
+			    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+			    mount("tmpfs", "/mnt", "tmpfs", 0, NULL) ||
+			    new_file("/mnt/f", 0))
+				r = SETUP_ERROR;
+			else
+				r = set_fcap("/mnt/f", -1);
+		} else {
+			r = set_fcap("/tmp/x", -1);
+		}
+		if (write(res[1], &r, sizeof(r)) != sizeof(r))
+			_exit(1);
+		_exit(0);
+	}
+	if (read(s1[0], &c, 1) != 1 || map_both(pid, "0 0 1\n") ||
+	    write(s2[1], "g", 1) != 1 || read(res[0], &r, sizeof(r)) != sizeof(r))
+		r = SETUP_ERROR;
+	waitpid(pid, NULL, 0);
+	return r;
+}
+
+TEST_F(setfcap, fcap_creator_without_setfcap_parent_mapped)
+{
+	int version = 3;
+
+	EXPECT_EQ(in_child(x_unshares_r_maps, &version), -EPERM);
+	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
+}
+
+/* the v2 fast path of cap_convert_nscap() */
+TEST_F(setfcap, fcap_v2_creator_without_setfcap_parent_mapped)
+{
+	int version = 2;
+
+	EXPECT_EQ(in_child(x_unshares_r_maps, &version), -EPERM);
+}
+
+/* R maps "5 0 1", gives up CAP_SETFCAP, unshares again */
+static long r_drops_then_nests(void *a)
+{
+	if (unshare(CLONE_NEWUSER) || map_both(0, "5 0 1\n"))
+		return SETUP_ERROR;
+	restrict_root();
+	if (set_fcap("/tmp/x", 5) != -EPERM)
+		return SETUP_ERROR;
+	if (unshare(CLONE_NEWUSER))
+		return SETUP_ERROR;
+	if (write_map(0, "5 5 1\n"))
+		return -EPERM;
+	write_file(0, "setgroups", "deny");
+	write_file(0, "gid_map", "5 5 1\n");
+	return set_fcap("/tmp/x", 5);
+}
+
+TEST_F(setfcap, fcap_after_dropping_setfcap_and_nesting)
+{
+	EXPECT_EQ(in_child(r_drops_then_nests, NULL), -EPERM);
+	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
+}
+
+/* X unshares, R maps "0 0 1" for it, and another R joins it */
+static long joiner_takes_ns_reach(void *a)
+{
+	struct holder h;
+	long r;
+
+	h = spawn_mapped(HOLDER_RESTRICTED, "0 0 1\n");
+	if (h.pid <= 0)
+		return SETUP_ERROR;
+	r = join(h.pid);
+	if (!r)
+		r = set_fcap("/tmp/x", -1);
+	reap_holder(h);
+	return r;
+}
+
+TEST_F(setfcap, fcap_joiner_takes_namespace_reach)
+{
+	EXPECT_EQ(in_child(joiner_takes_ns_reach, NULL), -EPERM);
+	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
+}
+
+/*
+ * A v2 xattr on a filesystem whose namespace has no uid 0 has no root user
+ * and is honoured nowhere: refuse it rather than storing it unconverted.
+ */
+static long r_v2_without_uid0(void *a)
+{
+	if (unshare(CLONE_NEWUSER) || map_both(0, "1000 0 1\n"))
+		return SETUP_ERROR;
+	/* host uid 0, our uid, is uid 1000 here */
+	if (unshare(CLONE_NEWNS) ||
+	    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+	    mount("tmpfs", "/mnt", "tmpfs", 0, NULL) || new_file("/mnt/f", 1000))
+		return SETUP_ERROR;
+	return set_fcap("/mnt/f", -1);
+}
+
+TEST_F(setfcap, fcap_v2_without_uid0_in_fs_ns)
+{
+	EXPECT_EQ(in_child(r_v2_without_uid0, NULL), -EINVAL);
+}
+
+/* ... and from the parent namespace, where the writer does have a root user */
+static long r_v2_without_uid0_from_parent(void *a)
+{
+	struct holder *h = a;
+	char path[64];
+
+	snprintf(path, sizeof(path), "/proc/%d/root/mnt/f", h->pid);
+	return set_fcap(path, -1);
+}
+
+TEST_F(setfcap, fcap_v2_without_uid0_from_parent)
+{
+	int ready[2], go[2], mapped[2], pid;
+	struct holder h;
+	char c;
+
+	ASSERT_EQ(pipe(ready), 0);
+	ASSERT_EQ(pipe(go), 0);
+	ASSERT_EQ(pipe(mapped), 0);
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (!pid) {
+		/* host uid 0 is uid 1000 in here, nobody is uid 0 */
+		if (unshare(CLONE_NEWUSER) || write(ready[1], "u", 1) != 1 ||
+		    read(mapped[0], &c, 1) != 1 || unshare(CLONE_NEWNS) ||
+		    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+		    mount("tmpfs", "/mnt", "tmpfs", 0, NULL) ||
+		    new_file("/mnt/f", 1000) || write(ready[1], "m", 1) != 1)
+			_exit(1);
+		if (read(go[0], &c, 1) < 0)
+			_exit(1);
+		_exit(0);
+	}
+	h.pid = pid;
+	h.go = go[1];
+	ASSERT_EQ(read(ready[0], &c, 1), 1);
+	ASSERT_EQ(map_both(pid, "1000 0 1\n"), 0);
+	ASSERT_EQ(write(mapped[1], "g", 1), 1);
+	ASSERT_EQ(read(ready[0], &c, 1), 1);
+	EXPECT_EQ(in_child(r_v2_without_uid0_from_parent, &h), -EINVAL);
+	reap_holder(h);
+}
+
+/* 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_TRUE(user_gets_sys_admin("/tmp/x"));
+}
+
+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);
+}
+
+static long r_v2_own_fs(void *a)
+{
+	if (unshare(CLONE_NEWUSER) || map_both(0, "0 0 1\n"))
+		return SETUP_ERROR;
+	if (unshare(CLONE_NEWNS) ||
+	    mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) ||
+	    mount("tmpfs", "/mnt", "tmpfs", 0, NULL) || new_file("/mnt/f", 0))
+		return SETUP_ERROR;
+	return set_fcap("/mnt/f", -1);
+}
+
+TEST_F(setfcap, fcap_v2_own_ns)
+{
+	EXPECT_EQ(in_child(r_v2_own_fs, NULL), 0);
+}
+
+/* ---------------------------------------------------------------------- */
+/* Idmapped mounts: the root user is both the one seen through the mount   */
+/* and the one stored.  Mounts that make a container's root host uid 0 are  */
+/* up to whoever sets them up.                                             */
+
+struct idmap_case {
+	const char *ns_map, *idmap_map;
+	int file_uid, restricted;
+};
+
+/* returns 1 to skip: no idmapped mounts on tmpfs */
+static int idmap_case(const struct idmap_case *c, long *r)
+{
+	struct holder ns, im;
+	int pid, p[2], st;
+
+	ns = spawn_mapped(HOLDER_ROOT, c->ns_map);
+	if (ns.pid <= 0)
+		return -1;
+	im = c->idmap_map ? spawn_mapped(HOLDER_ROOT, c->idmap_map) : ns;
+	if (im.pid <= 0 || new_file("/tmp/x", c->file_uid))
+		return -1;
+	if (idmap_tmp(im.pid)) {
+		reap_holder(ns);
+		if (c->idmap_map)
+			reap_holder(im);
+		return 1;
+	}
+	if (pipe(p))
+		return -1;
+	/* join from a child of the namespace's holder's sibling: pass the pid */
+	pid = fork();
+	if (!pid) {
+		long res;
+
+		if (c->restricted)
+			restrict_root();
+		res = join(ns.pid);
+		if (!res)
+			res = set_fcap("/mnt/x", 0);
+		if (write(p[1], &res, sizeof(res)) != sizeof(res))
+			_exit(1);
+		_exit(0);
+	}
+	if (read(p[0], r, sizeof(*r)) != sizeof(*r))
+		*r = SETUP_ERROR;
+	waitpid(pid, &st, 0);
+	umount2("/mnt", MNT_DETACH);
+	reap_holder(ns);
+	if (c->idmap_map)
+		reap_holder(im);
+	return 0;
+}
+
+FIXTURE(idmap) {};
+
+FIXTURE_VARIANT(idmap) {
+	struct idmap_case c;
+	long expect;
+};
+
+/* X can't join a namespace that maps host uid 0 to get at the mount */
+FIXTURE_VARIANT_ADD(idmap, reverse_mapping) {
+	.c = { "0 0 1\n", "100000 0 1\n", 100000, 1 },
+	.expect = -EPERM,
+};
+
+/* 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)
+{
+	if (geteuid())
+		SKIP(return, "needs root");
+	prepare(_metadata);
+}
+
+FIXTURE_TEARDOWN(idmap) {}
+
+TEST_F(idmap, fcap)
+{
+	long r;
+	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()                                                                 */
+
+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);
+}
+
+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);
+}
+
+/* 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                                                                  */
+
+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(setfcap, 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);
+}
+
+TEST_F(setfcap, 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);
+}
+
+TEST_F(setfcap, 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(setfcap, 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(setfcap, 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(setfcap, 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);
+}
+
+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] 13+ messages in thread

* Re: [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches
  2026-10-26 15:41 ` [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches Josef Bacik
@ 2026-10-26 15:48   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 15:48 UTC (permalink / raw)
  Cc: linux-security-module

> create_user_ns() records in ns->parent_could_setfcap whether the creator
> had CAP_SETFCAP in its effective set, and verify_root_map() trusts that
> when a task in the new namespace maps the parent's uid 0.  A bool only
> describes the parent, though.  File capabilities belong to the kuid of a
> root user, and a namespace that maps the parent's uid 0 shares that kuid
> with the parent and possibly with ancestors further up, so what matters
> is how far up the creator's CAP_SETFCAP counted.
> 
> Replace the bool with ns->setfcap_level: CAP_SETFCAP held in the
> namespace counts there and in its ancestors up to the one at that
> ->level.  cap_setfcap_level() computes it from the creator's cred.  A
> creator that holds CAP_SETFCAP passes on the setfcap_level of its own
> namespace, one that doesn't gets the new namespace's own level, and the
> initial namespace has 0.
> 
> verify_root_map() keeps checking the same condition: the bool was false
> exactly when the level is the namespace's own.
> 
> No functional change.
> 
> Reviewed-by: Bradley Morgan <brads@mainlining.org>
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=1


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

* Re: [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP
  2026-10-26 15:41 ` [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
@ 2026-10-26 15:54   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 15:54 UTC (permalink / raw)
  Cc: linux-security-module

> Mapping uid 0 of the parent into a user namespace requires CAP_SETFCAP,
> because a task in such a namespace can write file capabilities that are
> honoured in the parent.  A task that is already in the namespace has a
> full capability set there and can't be asked any more, so
> verify_root_map() goes by what the creator had, now ns->setfcap_level.
> 
> But setns() lets other tasks in, and they get the full set as well:
> 
>   task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP
>   unshare(CLONE_NEWUSER)
>                                     setns(A's user ns)
>                                       allowed, euid == ns->owner
>                                     write "0 0 1" to /proc/self/uid_map
>                                       ns->setfcap_level is A's -> allowed
> 
> [ ... ]
> 
> Link: https://lore.kernel.org/all/20261021-bioenergie-allheilmittel-undurchdringlichen-35666eb62cff@brauner/
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=2


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

* Re: [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP
  2026-10-26 15:41 ` [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
@ 2026-10-26 15:56   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 15:56 UTC (permalink / raw)
  Cc: linux-security-module

> Tasks in a user namespace created by a task with CAP_SETFCAP may map uid
> 0 of the parent and write file capabilities that are honoured there.
> That is the one privilege they keep outside of their namespace.  setns()
> no longer lets anybody in who doesn't have it, but the ptrace checks
> don't know about it: for a target in another user namespace
> cap_ptrace_access_check() is satisfied with CAP_SYS_PTRACE over the
> target's namespace, which the owner rule hands to any task with the
> right euid.
> 
>   task A: uid 0, full caps          task B: uid 0, no CAP_SETFCAP
>   unshare(CLONE_NEWUSER)
>                                     ptrace(PTRACE_ATTACH, A)
>                                       uids match, euid == ns->owner
>                                     make A write "0 0 1" to its uid_map,
>                                     or set file capabilities
> [ ... ]
> tracer can't setns() into either.  PTRACE_MODE_READ is unchanged.
> 
> Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
> Assisted-by: LLM
> Signed-off-by: Josef Bacik <josef@toxicpanda.com>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=5


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

* Re: [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0
  2026-10-26 15:41 ` [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0 Josef Bacik
@ 2026-10-26 16:00   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 16:00 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")
> [ ... ]
> 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>

Sashiko has reviewed this patch and found no issues.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=3


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

* Re: [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces
  2026-10-26 15:41 ` [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
@ 2026-10-26 16:00   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 16:00 UTC (permalink / raw)
  To: Josef Bacik, Serge Hallyn, Paul Moore, Christian Brauner
  Cc: James Morris, David Howells, Jarkko Sakkinen, Andrew G. Morgan,
	Serge Hallyn, linux-security-module, keyrings, Shuah Khan

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] selftests/namespaces: unreachable set_fcap() and write_map() in setfcap_test
- [Medium] selftests/namespaces: fail instead of skip on ENOSYS in session_to_parent()
- [Medium] selftests/namespaces: missing parent session keyring join in r_new_session_keyring_maps()
--

commit ef6b694dab9e316eaa07835cf40f4ad86e6267ac
Author: Josef Bacik <josef@toxicpanda.com>

selftests/namespaces: test the CAP_SETFCAP rule for user namespaces

This commit adds the setfcap_test selftest to verify the CAP_SETFCAP rules for
mapping ancestor root users, writing file capabilities in user namespaces and
idmapped mounts, joining namespaces via setns(), and attaching via ptrace().

> diff --git a/tools/testing/selftests/namespaces/setfcap_test.c b/tools/testing/selftests/namespaces/setfcap_test.c
> new file mode 100644
> index 0000000000000..0a82b239b3685
> --- /dev/null
> +++ b/tools/testing/selftests/namespaces/setfcap_test.c

[ ... ]

> +static long x_joins_unshares_maps(void *a)
> +{
> +	long r;
> +
> +	restrict_root();
> +	r = join(PID(a));
> +	if (r)
> +		return r;
> +	if (unshare(CLONE_NEWUSER))
> +		return SETUP_ERROR;
> +	return write_map(0, "0 0 1\n");
> +}
> +
> +TEST_F(setfcap, map_after_setns_and_unshare)
> +{
> +	struct holder h;
> +
> +	h = spawn_mapped(HOLDER_ROOT, "0 0 1\n");
> +	ASSERT_GT(h.pid, 0);
> +	EXPECT_EQ(in_child(x_joins_unshares_maps, &h), -EPERM);
> +	reap_holder(h);
> +}

[ ... ]

> +static long session_to_parent(void *a)
> +{
> +	if (syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) < 0 ||
> +	    syscall(SYS_keyctl, KEYCTL_SESSION_TO_PARENT) < 0)
> +		return SETUP_ERROR;
> +	return 0;
> +}
> +
> +/* a cred replaced by key_change_session_keyring() keeps what it had */
> +static long r_new_session_keyring_maps(void *a)
> +{
> +	if (unshare(CLONE_NEWUSER))
> +		return SETUP_ERROR;
> +	if (in_child(session_to_parent, NULL))
> +		return SETUP_ERROR;
> +	return write_map(0, "0 0 1\n");
> +}
> +
> +TEST_F(setfcap, map_after_session_to_parent)
> +{
> +	EXPECT_EQ(in_child(r_new_session_keyring_maps, NULL), 0);
> +}

[Severity: Medium]
If the running kernel is built without CONFIG_KEYS, syscall(SYS_keyctl, ...)
in session_to_parent() fails with ENOSYS (or EOPNOTSUPP), which
r_new_session_keyring_maps() translates into SETUP_ERROR and causes
map_after_session_to_parent to fail EXPECT_EQ(..., 0).

Should map_after_session_to_parent check for ENOSYS / EOPNOTSUPP and call
SKIP() with a reason message when keyctl is not supported by the kernel?

[Severity: Medium]
Can KEYCTL_SESSION_TO_PARENT in session_to_parent() also fail with EPERM when
the test binary is invoked via sudo or su from an unprivileged login session?

In security/keys/keyctl.c:keyctl_session_to_parent(), the kernel requires the
parent's existing session keyring UID to match the child's effective UID:

	/* the keyrings must have the same UID */
	if ((pcred->session_keyring &&
	     !uid_eq(pcred->session_keyring->uid, mycred->euid)) ||
	    !uid_eq(mycred->session_keyring->uid, mycred->euid))
		goto unlock;

If the parent process in r_new_session_keyring_maps() inherited a session
keyring created for a non-zero UID before elevating privileges,
pcred->session_keyring->uid will not match mycred->euid (0), causing
KEYCTL_SESSION_TO_PARENT to fail and return SETUP_ERROR.

Should r_new_session_keyring_maps() call
syscall(SYS_keyctl, KEYCTL_JOIN_SESSION_KEYRING, NULL) in the parent before
spawning session_to_parent() so the parent's session keyring is owned by
UID 0?

> +
> +/* ---------------------------------------------------------------------- */
> +/* File capabilities that are honoured in an ancestor                      */
> +
> +static long x_joins_sets_fcap(void *a)
> +{
> +	long r;
> +
> +	restrict_root();
> +	r = join(PID(a));
> +	if (r)
> +		return r;
> +	return set_fcap("/tmp/x", -1);
> +}
> +
> +TEST_F(setfcap, fcap_after_setns)
> +{
> +	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);
> +	reap_holder(h);
> +	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
> +}
> +
> +static long x_joins_sets_fcap_rootid5(void *a)
> +{
> +	long r;
> +
> +	restrict_root();
> +	r = join(PID(a));
> +	if (r)
> +		return r;
> +	return set_fcap("/tmp/x", 5);
> +}
> +
> +/* a v3 xattr can name the root user by any uid it has in the namespace */
> +TEST_F(setfcap, fcap_after_setns_v3_rootid)
> +{
> +	struct holder h;
> +
> +	h = spawn_mapped(HOLDER_ROOT, "5 0 1\n");
> +	ASSERT_GT(h.pid, 0);
> +	EXPECT_EQ(in_child(x_joins_sets_fcap_rootid5, &h), -EPERM);
> +	reap_holder(h);
> +	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
> +}

[ ... ]

> +/* R maps "5 0 1", gives up CAP_SETFCAP, unshares again */
> +static long r_drops_then_nests(void *a)
> +{
> +	if (unshare(CLONE_NEWUSER) || map_both(0, "5 0 1\n"))
> +		return SETUP_ERROR;
> +	restrict_root();
> +	if (set_fcap("/tmp/x", 5) != -EPERM)
> +		return SETUP_ERROR;
> +	if (unshare(CLONE_NEWUSER))
> +		return SETUP_ERROR;
> +	if (write_map(0, "5 5 1\n"))
> +		return -EPERM;
> +	write_file(0, "setgroups", "deny");
> +	write_file(0, "gid_map", "5 5 1\n");
> +	return set_fcap("/tmp/x", 5);
> +}
> +
> +TEST_F(setfcap, fcap_after_dropping_setfcap_and_nesting)
> +{
> +	EXPECT_EQ(in_child(r_drops_then_nests, NULL), -EPERM);
> +	EXPECT_FALSE(user_gets_sys_admin("/tmp/x"));
> +}

[ ... ]

> +/* returns 1 to skip: no idmapped mounts on tmpfs */
> +static int idmap_case(const struct idmap_case *c, long *r)
> +{
> +	struct holder ns, im;
> +	int pid, p[2], st;
> +
> +	ns = spawn_mapped(HOLDER_ROOT, c->ns_map);
> +	if (ns.pid <= 0)
> +		return -1;
> +	im = c->idmap_map ? spawn_mapped(HOLDER_ROOT, c->idmap_map) : ns;
> +	if (im.pid <= 0 || new_file("/tmp/x", c->file_uid))
> +		return -1;
> +	if (idmap_tmp(im.pid)) {
> +		reap_holder(ns);
> +		if (c->idmap_map)
> +			reap_holder(im);
> +		return 1;
> +	}
> +	if (pipe(p))
> +		return -1;
> +	/* join from a child of the namespace's holder's sibling: pass the pid */
> +	pid = fork();
> +	if (!pid) {
> +		long res;
> +
> +		if (c->restricted)
> +			restrict_root();
> +		res = join(ns.pid);
> +		if (!res)
> +			res = set_fcap("/mnt/x", 0);
> +		if (write(p[1], &res, sizeof(res)) != sizeof(res))
> +			_exit(1);
> +		_exit(0);
> +	}
> +	if (read(p[0], r, sizeof(*r)) != sizeof(*r))
> +		*r = SETUP_ERROR;
> +	waitpid(pid, &st, 0);
> +	umount2("/mnt", MNT_DETACH);
> +	reap_holder(ns);
> +	if (c->idmap_map)
> +		reap_holder(im);
> +	return 0;
> +}
> +
> +FIXTURE(idmap) {};
> +
> +FIXTURE_VARIANT(idmap) {
> +	struct idmap_case c;
> +	long expect;
> +};
> +
> +/* X can't join a namespace that maps host uid 0 to get at the mount */
> +FIXTURE_VARIANT_ADD(idmap, reverse_mapping) {
> +	.c = { "0 0 1\n", "100000 0 1\n", 100000, 1 },
> +	.expect = -EPERM,
> +};

[Severity: Medium]
Are the set_fcap() and nested write_map() calls in several of these negative
tests reachable?

1. In map_after_setns_and_unshare (x_joins_unshares_maps()), fcap_after_setns
(x_joins_sets_fcap()), and fcap_after_setns_v3_rootid
(x_joins_sets_fcap_rootid5()), the target namespace h is created with
HOLDER_ROOT and maps host UID 0 (at UID 0 or UID 5). When the restricted child
calls join(PID(a)), setns() is rejected in userns_install() ->
cred_may_setfcap_userns() with -EPERM. Because those helpers immediately
return r when join() fails, the subsequent unshare(CLONE_NEWUSER) /
write_map() in x_joins_unshares_maps() and set_fcap() in x_joins_sets_fcap()
and x_joins_sets_fcap_rootid5() are never executed.

2. In fcap_after_dropping_setfcap_and_nesting (r_drops_then_nests()), after
mapping "5 0 1\n" in the first namespace, calling restrict_root(), and calling
unshare(CLONE_NEWUSER) a second time, write_map(0, "5 5 1\n") always fails
with -EPERM in verify_root_map() because UID 5 in the parent namespace maps
host UID 0. The check:

	if (write_map(0, "5 5 1\n"))
		return -EPERM;

always returns at that point (and also masks any non-EPERM error from
write_map()), leaving the rest of the function:

	write_file(0, "setgroups", "deny");
	write_file(0, "gid_map", "5 5 1\n");
	return set_fcap("/tmp/x", 5);

unreachable. As a result, neither fcap_after_setns_v3_rootid nor
fcap_after_dropping_setfcap_and_nesting reaches cap_convert_nscap() with a
non-zero v3 rootid (5) from a task that holds CAP_SETFCAP in its own user
namespace.

3. Similarly, in idmap_case() for the reverse_mapping variant, im is set up
with "100000 0 1\n" and mounted idmapped at /mnt, while ns is spawned with
HOLDER_ROOT and "0 0 1\n". When c->restricted is set, join(ns.pid) fails with
-EPERM in setns(), so set_fcap("/mnt/x", 0) is never called against the
idmapped mount. Because idmap_case() always spawns ns with HOLDER_ROOT,
cap_convert_nscap() is never exercised on an idmapped mount by a task in a
user namespace created without CAP_SETFCAP (such as one spawned with
HOLDER_RESTRICTED where parent root mapped the UIDs).

Should these tests spawn or enter aHOLDER_RESTRICTED namespace mapped by the
parent (or assert that setup steps like join() and write_map() succeed) so
that set_fcap() and the nested operations are actually reached?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=6

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

* Re: [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches
  2026-10-26 15:41 ` [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
@ 2026-10-26 16:05   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-10-26 16:05 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.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-b4-setfcap-userns-v6-0-25f10b7ca497@toxicpanda.com?part=4


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

end of thread, other threads:[~2026-10-26 16:05 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26 15:41 [PATCH v6 0/6] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-10-26 15:41 ` [PATCH v6 1/6] userns: record how far up CAP_SETFCAP reaches Josef Bacik
2026-10-26 15:48   ` sashiko-bot
2026-10-26 15:41 ` [PATCH v6 2/6] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-10-26 15:54   ` sashiko-bot
2026-10-26 15:41 ` [PATCH v6 3/6] userns: check the opener and the writer before mapping uid 0 Josef Bacik
2026-10-26 16:00   ` sashiko-bot
2026-10-26 15:41 ` [PATCH v6 4/6] capabilities: limit fscaps to where CAP_SETFCAP reaches Josef Bacik
2026-10-26 16:05   ` sashiko-bot
2026-10-26 15:41 ` [PATCH v6 5/6] capabilities: don't let ptrace borrow CAP_SETFCAP Josef Bacik
2026-10-26 15:56   ` sashiko-bot
2026-10-26 15:41 ` [PATCH v6 6/6] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
2026-10-26 16:00   ` 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.