34795ba
From 64a37c8197f4e1c2637cd80326f4649282176369 Mon Sep 17 00:00:00 2001
34795ba
From: Jann Horn <jann@thejh.net>
34795ba
Date: Sat, 26 Dec 2015 03:52:31 +0100
34795ba
Subject: [PATCH] ptrace: being capable wrt a process requires mapped uids/gids
34795ba
34795ba
ptrace_has_cap() checks whether the current process should be
34795ba
treated as having a certain capability for ptrace checks
34795ba
against another process. Until now, this was equivalent to
34795ba
has_ns_capability(current, target_ns, CAP_SYS_PTRACE).
34795ba
34795ba
However, if a root-owned process wants to enter a user
34795ba
namespace for some reason without knowing who owns it and
34795ba
therefore can't change to the namespace owner's uid and gid
34795ba
before entering, as soon as it has entered the namespace,
34795ba
the namespace owner can attach to it via ptrace and thereby
34795ba
gain access to its uid and gid.
34795ba
34795ba
While it is possible for the entering process to switch to
34795ba
the uid of a claimed namespace owner before entering,
34795ba
causing the attempt to enter to fail if the claimed uid is
34795ba
wrong, this doesn't solve the problem of determining an
34795ba
appropriate gid.
34795ba
34795ba
With this change, the entering process can first enter the
34795ba
namespace and then safely inspect the namespace's
34795ba
properties, e.g. through /proc/self/{uid_map,gid_map},
34795ba
assuming that the namespace owner doesn't have access to
34795ba
uid 0.
34795ba
34795ba
Changed in v2: The caller needs to be capable in the
34795ba
namespace into which tcred's uids/gids can be mapped.
34795ba
34795ba
Signed-off-by: Jann Horn <jann@thejh.net>
34795ba
---
34795ba
 kernel/ptrace.c | 33 ++++++++++++++++++++++++++++-----
34795ba
 1 file changed, 28 insertions(+), 5 deletions(-)
34795ba
34795ba
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
34795ba
index 787320de68e0..407c382b45c8 100644
34795ba
--- a/kernel/ptrace.c
34795ba
+++ b/kernel/ptrace.c
34795ba
@@ -20,6 +20,7 @@
34795ba
 #include <linux/uio.h>
34795ba
 #include <linux/audit.h>
34795ba
 #include <linux/pid_namespace.h>
34795ba
+#include <linux/user_namespace.h>
34795ba
 #include <linux/syscalls.h>
34795ba
 #include <linux/uaccess.h>
34795ba
 #include <linux/regset.h>
34795ba
@@ -207,12 +208,34 @@ static int ptrace_check_attach(struct task_struct *child, bool ignore_state)
34795ba
 	return ret;
34795ba
 }
34795ba
 
34795ba
-static int ptrace_has_cap(struct user_namespace *ns, unsigned int mode)
34795ba
+static bool ptrace_has_cap(const struct cred *tcred, unsigned int mode)
34795ba
 {
34795ba
+	struct user_namespace *tns = tcred->user_ns;
34795ba
+
34795ba
+	/* When a root-owned process enters a user namespace created by a
34795ba
+	 * malicious user, the user shouldn't be able to execute code under
34795ba
+	 * uid 0 by attaching to the root-owned process via ptrace.
34795ba
+	 * Therefore, similar to the capable_wrt_inode_uidgid() check,
34795ba
+	 * verify that all the uids and gids of the target process are
34795ba
+	 * mapped into a namespace below the current one in which the caller
34795ba
+	 * is capable.
34795ba
+	 * No fsuid/fsgid check because __ptrace_may_access doesn't do it
34795ba
+	 * either.
34795ba
+	 */
34795ba
+	while (
34795ba
+	    !kuid_has_mapping(tns, tcred->euid) ||
34795ba
+	    !kuid_has_mapping(tns, tcred->suid) ||
34795ba
+	    !kuid_has_mapping(tns, tcred->uid)  ||
34795ba
+	    !kgid_has_mapping(tns, tcred->egid) ||
34795ba
+	    !kgid_has_mapping(tns, tcred->sgid) ||
34795ba
+	    !kgid_has_mapping(tns, tcred->gid)) {
34795ba
+		tns = tns->parent;
34795ba
+	}
34795ba
+
34795ba
 	if (mode & PTRACE_MODE_NOAUDIT)
34795ba
-		return has_ns_capability_noaudit(current, ns, CAP_SYS_PTRACE);
34795ba
+		return has_ns_capability_noaudit(current, tns, CAP_SYS_PTRACE);
34795ba
 	else
34795ba
-		return has_ns_capability(current, ns, CAP_SYS_PTRACE);
34795ba
+		return has_ns_capability(current, tns, CAP_SYS_PTRACE);
34795ba
 }
34795ba
 
34795ba
 /* Returns 0 on success, -errno on denial. */
34795ba
@@ -241,7 +264,7 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
34795ba
 	    gid_eq(cred->gid, tcred->sgid) &&
34795ba
 	    gid_eq(cred->gid, tcred->gid))
34795ba
 		goto ok;
34795ba
-	if (ptrace_has_cap(tcred->user_ns, mode))
34795ba
+	if (ptrace_has_cap(tcred, mode))
34795ba
 		goto ok;
34795ba
 	rcu_read_unlock();
34795ba
 	return -EPERM;
34795ba
@@ -252,7 +275,7 @@ ok:
34795ba
 		dumpable = get_dumpable(task->mm);
34795ba
 	rcu_read_lock();
34795ba
 	if (dumpable != SUID_DUMP_USER &&
34795ba
-	    !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
34795ba
+	    !ptrace_has_cap(__task_cred(task), mode)) {
34795ba
 		rcu_read_unlock();
34795ba
 		return -EPERM;
34795ba
 	}
34795ba
-- 
34795ba
2.5.0
34795ba