diff options
Diffstat (limited to 'kernel/ptrace.c')
-rw-r--r-- | kernel/ptrace.c | 44 |
1 files changed, 35 insertions, 9 deletions
diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 261ee21e62db..c67aab541ee2 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -20,6 +20,7 @@ #include <linux/uio.h> #include <linux/audit.h> #include <linux/pid_namespace.h> +#include <linux/user_namespace.h> #include <linux/syscalls.h> #include <linux/uaccess.h> #include <linux/regset.h> @@ -39,6 +40,9 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent) BUG_ON(!list_empty(&child->ptrace_entry)); list_add(&child->ptrace_entry, &new_parent->ptraced); child->parent = new_parent; + rcu_read_lock(); + child->ptracer_cred = get_cred(__task_cred(new_parent)); + rcu_read_unlock(); } /** @@ -71,11 +75,15 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent) */ void __ptrace_unlink(struct task_struct *child) { + const struct cred *old_cred; BUG_ON(!child->ptrace); child->ptrace = 0; child->parent = child->real_parent; list_del_init(&child->ptrace_entry); + old_cred = child->ptracer_cred; + child->ptracer_cred = NULL; + put_cred(old_cred); spin_lock(&child->sighand->siglock); @@ -207,12 +215,34 @@ static int ptrace_check_attach(struct task_struct *child, bool ignore_state) return ret; } -static int ptrace_has_cap(struct user_namespace *ns, unsigned int mode) +static bool ptrace_has_cap(const struct cred *tcred, unsigned int mode) { + struct user_namespace *tns = tcred->user_ns; + + /* When a root-owned process enters a user namespace created by a + * malicious user, the user shouldn't be able to execute code under + * uid 0 by attaching to the root-owned process via ptrace. + * Therefore, similar to the capable_wrt_inode_uidgid() check, + * verify that all the uids and gids of the target process are + * mapped into a namespace below the current one in which the caller + * is capable. + * No fsuid/fsgid check because __ptrace_may_access doesn't do it + * either. + */ + while ( + !kuid_has_mapping(tns, tcred->euid) || + !kuid_has_mapping(tns, tcred->suid) || + !kuid_has_mapping(tns, tcred->uid) || + !kgid_has_mapping(tns, tcred->egid) || + !kgid_has_mapping(tns, tcred->sgid) || + !kgid_has_mapping(tns, tcred->gid)) { + tns = tns->parent; + } + if (mode & PTRACE_MODE_NOAUDIT) - return has_ns_capability_noaudit(current, ns, CAP_SYS_PTRACE); + return has_ns_capability_noaudit(current, tns, CAP_SYS_PTRACE); else - return has_ns_capability(current, ns, CAP_SYS_PTRACE); + return has_ns_capability(current, tns, CAP_SYS_PTRACE); } /* Returns 0 on success, -errno on denial. */ @@ -264,7 +294,7 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode) gid_eq(caller_gid, tcred->sgid) && gid_eq(caller_gid, tcred->gid)) goto ok; - if (ptrace_has_cap(tcred->user_ns, mode)) + if (ptrace_has_cap(tcred, mode)) goto ok; rcu_read_unlock(); return -EPERM; @@ -275,7 +305,7 @@ ok: dumpable = get_dumpable(task->mm); rcu_read_lock(); if (dumpable != SUID_DUMP_USER && - !ptrace_has_cap(__task_cred(task)->user_ns, mode)) { + !ptrace_has_cap(__task_cred(task), mode)) { rcu_read_unlock(); return -EPERM; } @@ -343,10 +373,6 @@ static int ptrace_attach(struct task_struct *task, long request, if (seize) flags |= PT_SEIZED; - rcu_read_lock(); - if (ns_capable(__task_cred(task)->user_ns, CAP_SYS_PTRACE)) - flags |= PT_PTRACE_CAP; - rcu_read_unlock(); task->ptrace = flags; __ptrace_link(task, current); |