From: Josef Bacik <josef@toxicpanda.com>
To: Serge Hallyn <serge@hallyn.com>, Paul Moore <paul@paul-moore.com>,
Christian Brauner <brauner@kernel.org>
Cc: James Morris <jmorris@namei.org>,
David Howells <dhowells@redhat.com>,
Jarkko Sakkinen <jarkko@kernel.org>,
"Andrew G. Morgan" <morgan@kernel.org>,
Serge Hallyn <sergeh@kernel.org>,
linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org, keyrings@vger.kernel.org,
linux-fsdevel@vger.kernel.org, Shuah Khan <shuah@kernel.org>,
linux-kselftest@vger.kernel.org,
Bradley Morgan <brads@mainlining.org>,
Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH v9 2/3] capabilities: don't let ptrace borrow CAP_SETFCAP
Date: Mon, 09 Nov 2026 19:49:11 +0000 [thread overview]
Message-ID: <20261109-b4-setfcap-userns-v9-2-0d3f00976ce2@toxicpanda.com> (raw)
In-Reply-To: <20261109-b4-setfcap-userns-v9-0-0d3f00976ce2@toxicpanda.com>
A uid 0 task that gave up CAP_SETFCAP and CAP_SYS_PTRACE can still
attach to a task in a user namespace that another root task created,
and have it write file capabilities for its root user. For a target in
a child namespace cap_ptrace_access_check() asks for CAP_SYS_PTRACE over
that namespace, and cap_capable() grants every capability there to a
task whose euid owns it, whatever it gave up:
task A: uid 0, full caps task B: uid 0, no CAP_SETFCAP,
unshare(CLONE_NEWUSER) no CAP_SYS_PTRACE
ptrace(PTRACE_ATTACH, A)
euid == ns->owner -> allowed
make A write "0 0 1" to its uid_map,
or set file capabilities
The same goes for /proc/<pid>/mem, process_vm_writev() and
pidfd_getfd(). setns() refuses B now, so ptrace is the way left into
the namespace.
PTRACE_MODE_READ, tracers with CAP_SYS_PTRACE, a tracer and target in
the same namespace, and targets in namespaces whose tasks can't write
file capabilities for the tracer's root user are not affected.
Fix by refusing PTRACE_MODE_ATTACH and PTRACE_TRACEME across user
namespaces when the tracer has neither CAP_SETFCAP nor CAP_SYS_PTRACE,
the target has CAP_SETFCAP or can get it back from its bounding set, and
setns() would refuse the tracer the target's namespace.
Suggested-by: Serge Hallyn <serge@hallyn.com>
Link: https://lore.kernel.org/all/fd159e68-c613-4c7d-bfba-8af0a3f3a96e@hallyn.com/
Fixes: db2e718a4798 ("capabilities: require CAP_SETFCAP to map uid 0")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
---
security/commoncap.c | 39 +++++++++++++++++++++++++++++++++++----
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/security/commoncap.c b/security/commoncap.c
index 0ece141c68a6..d550a4e5d36a 100644
--- a/security/commoncap.c
+++ b/security/commoncap.c
@@ -176,14 +176,35 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz)
return 0;
}
+/*
+ * See cred_borrows_setfcap(). Within one namespace the usual rules apply: a
+ * tracer with CAP_SYS_PTRACE can take control of tasks with CAP_SETFCAP
+ * anyway. A target that still has CAP_SETFCAP in its bounding set gets it
+ * back from an execve() the tracer can make it do.
+ */
+static bool cap_ptrace_borrows_setfcap(const struct cred *cred,
+ const struct cred *child_cred)
+{
+ if (cred->user_ns == child_cred->user_ns)
+ return false;
+ if (!cap_raised(child_cred->cap_permitted, CAP_SETFCAP) &&
+ !cap_raised(child_cred->cap_bset, CAP_SETFCAP))
+ return false;
+ return !cap_raised(cred->cap_permitted, CAP_SYS_PTRACE) &&
+ cred_borrows_setfcap(cred, child_cred->user_ns);
+}
+
/**
* cap_ptrace_access_check - Determine whether the current process may access
* another
* @child: The process to be accessed
* @mode: The mode of attachment.
*
- * If we are in the same or an ancestor user_ns and have all the target
- * task's capabilities, then ptrace access is allowed.
+ * For PTRACE_MODE_ATTACH, if we have neither CAP_SETFCAP nor CAP_SYS_PTRACE
+ * and the target task is in another user_ns, where it can get CAP_SETFCAP
+ * over our root user, 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.
@@ -204,11 +225,15 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode)
caller_caps = &cred->cap_effective;
else
caller_caps = &cred->cap_permitted;
+ if ((mode & PTRACE_MODE_ATTACH) &&
+ cap_ptrace_borrows_setfcap(cred, child_cred))
+ goto deny;
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, *caller_caps))
goto out;
if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
+deny:
ret = -EPERM;
out:
rcu_read_unlock();
@@ -219,8 +244,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 parent has neither CAP_SETFCAP nor CAP_SYS_PTRACE and current is in
+ * another user_ns, where it can get CAP_SETFCAP over parent's root user,
+ * 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.
@@ -236,11 +264,14 @@ int cap_ptrace_traceme(struct task_struct *parent)
rcu_read_lock();
cred = __task_cred(parent);
child_cred = current_cred();
+ if (cap_ptrace_borrows_setfcap(cred, child_cred))
+ goto deny;
if (cred->user_ns == child_cred->user_ns &&
cap_issubset(child_cred->cap_permitted, cred->cap_permitted))
goto out;
if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE))
goto out;
+deny:
ret = -EPERM;
out:
rcu_read_unlock();
--
2.55.0
next prev parent reply other threads:[~2026-11-09 19:49 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 19:49 [PATCH v9 0/3] capabilities: close the ways around the CAP_SETFCAP rule for uid 0 Josef Bacik
2026-11-09 19:49 ` [PATCH v9 1/3] userns: don't let setns() lend CAP_SETFCAP Josef Bacik
2026-11-09 19:49 ` Josef Bacik [this message]
2026-11-09 19:49 ` [PATCH v9 3/3] selftests/namespaces: test the CAP_SETFCAP rule for user namespaces Josef Bacik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109-b4-setfcap-userns-v9-2-0d3f00976ce2@toxicpanda.com \
--to=josef@toxicpanda.com \
--cc=brads@mainlining.org \
--cc=brauner@kernel.org \
--cc=dhowells@redhat.com \
--cc=jarkko@kernel.org \
--cc=jmorris@namei.org \
--cc=keyrings@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=morgan@kernel.org \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
--cc=sergeh@kernel.org \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®