[PATCH 1/5] cred: record how far up CAP_SETFCAP reaches

Josef Bacik josef at toxicpanda.com
Tue Oct 6 15:44:17 UTC 2026


File capabilities are tied to the kuid of the root user of a user
namespace, and a namespace that maps uid 0 of its parent shares that kuid
with the parent.  So CAP_SETFCAP in such a namespace is worth as much as
CAP_SETFCAP in the parent.  But every task gets a full capability set when
it enters a user namespace, and from then on its credentials say nothing
about what it was allowed to do outside.

The only trace that is kept is ns->parent_could_setfcap, which describes
the task that created the namespace and not the ones that use it.

Add cred->setfcap_level: CAP_SETFCAP of these credentials counts in
cred->user_ns and in its ancestors down to that ->level.  It is 0 in the
initial namespace, where the capability sets speak for themselves.  When
credentials enter a namespace, set_cred_user_ns() keeps the value if they
hold CAP_SETFCAP, and otherwise raises it to the first namespace on the
way down over which they have it: the child they own, or else the
namespace that is being entered.  cap_setfcap_level() computes that.  The
value can only grow, and it is inherited over fork() and execve() like the
rest of the cred.  key_change_session_keyring() copies field by field and
has to carry it over.

Nothing looks at the new field yet.

Assisted-by: LLM
Signed-off-by: Josef Bacik <josef at toxicpanda.com>
---
 include/linux/capability.h   |  3 +++
 include/linux/cred.h         |  1 +
 kernel/user_namespace.c      |  3 +++
 security/commoncap.c         | 26 ++++++++++++++++++++++++++
 security/keys/process_keys.c |  1 +
 5 files changed, 34 insertions(+)

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/cred.h b/include/linux/cred.h
index 6ef1750c93e2..b95119099993 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -123,6 +123,7 @@ struct cred {
 	kuid_t		fsuid;		/* UID for VFS ops */
 	kgid_t		fsgid;		/* GID for VFS ops */
 	unsigned	securebits;	/* SUID-less security management */
+	int		setfcap_level;	/* how far up CAP_SETFCAP counts */
 	kernel_cap_t	cap_inheritable; /* caps our children can inherit */
 	kernel_cap_t	cap_permitted;	/* caps we're permitted */
 	kernel_cap_t	cap_effective;	/* caps we can actually use */
diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c
index 1b23d819d398..6b45df3a8d82 100644
--- a/kernel/user_namespace.c
+++ b/kernel/user_namespace.c
@@ -44,6 +44,9 @@ static void dec_user_namespaces(struct ucounts *ucounts)
 
 static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns)
 {
+	/* The last chance to see what we can do outside of the new namespace. */
+	cred->setfcap_level = cap_setfcap_level(cred, user_ns->parent);
+
 	/* Start with the same capabilities as init but useless for doing
 	 * anything as the capabilities are bound to the new user namespace.
 	 */
diff --git a/security/commoncap.c b/security/commoncap.c
index d47ab3022343..513473f859be 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -131,6 +131,32 @@ int cap_capable(const struct cred *cred, struct user_namespace *target_ns,
 	return ret;
 }
 
+/**
+ * cap_setfcap_level - Determine how far up CAP_SETFCAP of a cred reaches
+ * @cred: The credentials to use
+ * @ns: The user namespace of @cred or one of its descendants
+ *
+ * File capabilities belong to the kuid of a namespace's root user, and the
+ * same kuid can be the root user of ancestors of that namespace.  Every task
+ * gets CAP_SETFCAP when it enters a user namespace, so having it there says
+ * nothing about those ancestors.  cred->setfcap_level does: it is handed down
+ * from namespace to namespace for as long as the capability is held.
+ *
+ * Return: the ->level of the topmost namespace, from @ns upwards, for which
+ * CAP_SETFCAP of @cred counts; @ns->level + 1 if it doesn't even over @ns.
+ */
+int cap_setfcap_level(const struct cred *cred, struct user_namespace *ns)
+{
+	if (cap_capable(cred, ns, CAP_SETFCAP, CAP_OPT_NOAUDIT))
+		return ns->level + 1;
+
+	if (cap_raised(cred->cap_effective, CAP_SETFCAP))
+		return cred->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
diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c
index a63c46bb2d14..f9cf3ce2426f 100644
--- a/security/keys/process_keys.c
+++ b/security/keys/process_keys.c
@@ -939,6 +939,7 @@ void key_change_session_keyring(struct callback_head *twork)
 	new->group_info	= get_group_info(old->group_info);
 
 	new->securebits	= old->securebits;
+	new->setfcap_level	= old->setfcap_level;
 	new->cap_inheritable	= old->cap_inheritable;
 	new->cap_permitted	= old->cap_permitted;
 	new->cap_effective	= old->cap_effective;

-- 
2.55.0




More information about the Linux-security-module-archive mailing list