wait4/wait6 leak uninitialized kernel stack via status, rusage/wrusage and siginfo on WNOHANG/WCONTINUED return paths
| Field | Value |
|---|---|
| ID | DF-0027 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-909 Initialization of Resource with a Sensitive Value; CWE-200 Information Exposure |
| File | sys/kern/kern_exit.c |
| Lines | 913-948 (wait4), 950-992 (wait6), 1388-1431 (kern_wait) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
sys_wait4 and sys_wait6 declare uninitialized stack locals (int status;,
struct __wrusage wrusage;, siginfo_t info;), call kern_wait(...), then
copyout() those locals whenever kern_wait returns error == 0. But
kern_wait returns error == 0 on the WNOHANG-with-no-waitable-child path
(:1427-1431, only *res is set) and on the WCONTINUED match path
(:1388-1419, leaves *wrusage untouched) without writing *status,
*wrusage, or *info. The wrappers therefore copyout uninitialized
kernel-stack bytes to userland: up to ~4 B (status), ~72 B (wait4
rusage), ~144 B (wait6 __wrusage), and ~128 B (wait6 siginfo_t) per
call. An unprivileged local user with a running child can sample this
deterministically and repeatably โ a KASLR/stack-residue oracle.
Root cause
sys/kern/kern_exit.c:913-948 (sys_wait4):
struct __wrusage wrusage; /* :916 uninitialized */
int status; /* :918 uninitialized */
...
error = kern_wait(idtype, id, &status, options, &wrusage, NULL,
&sysmsg->sysmsg_result);
if (error == 0 && uap->status)
error = copyout(&status, uap->status, sizeof(*uap->status)); /* :942 */
if (error == 0 && uap->rusage) {
ruadd(&wrusage.wru_self, &wrusage.wru_children);
error = copyout(&wrusage.wru_self, uap->rusage, sizeof(*uap->rusage)); /* :945 */
}
sys_wait6 (:950-992) is the same shape plus a siginfo_t copyout
(:991).
kern_wait WNOHANG-no-match (:1427-1431):
if (options & WNOHANG) {
*res = 0;
error = 0;
goto done; /* status/wrusage/info never written */
}
The WCONTINUED branch (:1388-1419) sets *res, *status = SIGCONT, and
*info but never writes *wrusage. ECHILD (nfound==0) is safe only
because the wrappers gate copyout on error == 0; the WNOHANG-no-match path
returns error == 0 and is therefore exposed.
Threat model & preconditions
- Attacker position: any local unprivileged user.
- Privileges gained or impact: information disclosure. Each call leaks up
to ~4 B + ~72 B (
wait4) or ~144 B + ~128 B (wait6) of kernel-stack residue (pointer fragments, canary, prior-syscall data). A samplable KASLR/stack-residue oracle that lowers the bar for exploiting a separate kernel bug. Not a direct LPE. - Required config or capabilities: none; default kernel.
- Reachability:
wait4(child, &status, WNOHANG, &ru)orwait6(...)with a running (non-waitable) child present.
Proof of concept
PoC source: findings/poc/DF-0027/wait_leak.c
Build & run (unprivileged)
cc -o wait_leak findings/poc/DF-0027/wait_leak.c ./wait_leak
Expected output
iter 0: status=0x<residue> (LEAKED) rusage-nonzero-byte (LEAKED) ... result: LEAK CONFIRMED
Impact
Low-impact kernel-stack info leak, samplable in a tight loop. Useful as a KASLR/stack-residue oracle ingredient. Rated Low (info-leak only; same class as DF-0007/DF-0010).
Recommended fix
Initialize all output parameters up front in kern_wait so the early-success
non-reap return paths cannot leak:
--- a/sys/kern/kern_exit.c
+++ b/sys/kern/kern_exit.c
@@ -1020
+ /*
+ * Initialize all output parameters up front so that early-success
+ * return paths that do not reap a child (e.g. WNOHANG with no
+ * waitable child, or WCONTINUED) cannot leak uninitialized kernel
+ * stack memory back to userland via the wait4/wait6 copyouts.
+ */
+ *status = 0;
+ bzero(wrusage, sizeof(*wrusage));
+ if (info)
+ bzero(info, sizeof(*info));
(Place this at the top of kern_wait, after argument validation.)
References
sys/kern/kern_exit.c:913-948โsys_wait4uninitialized locals + copyout.sys/kern/kern_exit.c:950-992โsys_wait6(same shape + siginfo).sys/kern/kern_exit.c:1427-1431โ WNOHANG-no-match early-success return.- CWE-909, CWE-200.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_exit.c. - pending Reported to DragonFlyBSD security contact.