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//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 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 --- 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