sys_sendfile() copies out 8 bytes of uninitialized kernel stack (off_t sbytes) on every failing sendfile(2) β unprivileged kernel-pointer leak
| Field | Value |
|---|---|
| ID | DF-2695 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| CWE | CWE-908 β CWE-200 Information Exposure |
| File | sys/kern/uipc_syscalls.c |
| Lines | 1594 (uninit), 1677-1680 (unconditional copyout), 1734 (late zero) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass β surfaced during uipc_socket.c audit) |
| Bucket | kernleak |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
sys_sendfile() declares off_t sbytes uninitialized; kern_sendfile()
stores *sbytes = 0 only after the early error gotos (bad socket fd,
no v_object, not SOCK_STREAM, not connected, offset<0); and the exit
path unconditionally does sbytes += hdtr_size; copyout(&sbytes,
uap->sbytes, 8);. Every failing sendfile(2) with a non-NULL sbytes
therefore returns 8 bytes of stale kernel stack to unprivileged
userspace. Demonstrated as uid 1001: a preceding socket(2)
deterministically seeds the slot and sendfile(fd,-1,...) then returns
the stable kernel virtual address 0xfffff80117c88a40; the first-call
variant leaked 0xffffffff809a5db0. FreeBSD's counterpart initializes
*sbytes early and copies out only on success β the unconditional
copyout of an uninitialized local is DragonFly-specific.
Threat model & preconditions
Unprivileged local user reads one 8-byte kernel stack slot per call, repeatably and cheaply β values include kernel text/data and KVA-range pointers (KASLR defeat / stack-residue fingerprinting; the audit guest runs no KASLR, but the pointer leak is demonstrated regardless).
Proof of concept
findings/poc/DF-2695/poc_sendfile.c + leak3.c: each iteration seeds the
stack slot then calls a failing sendfile with &sbytes β stable non-zero
kernel pointer across samples on stock; all zeros on the patched
kernel. Fix (off_t sbytes = 0;) validated in the same patched build.
Recommended fix
--- a/sys/kern/uipc_syscalls.c
+++ b/sys/kern/uipc_syscalls.c
@@ -1591,7 +1591,7 @@ sys_sendfile(struct sysmsg *sysmsg, const struct sendfile_args *uap)
size_t hbytes = 0;
size_t tbytes;
off_t hdtr_size = 0;
- off_t sbytes;
+ off_t sbytes = 0;
int error;
Timeline
- 2026-08-30 Surfaced during pass-2 audit of uipc_socket.c (GLM 5.3); unpriv leak reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2695 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc_sendfile.c | β | 2.3 KB | view raw | |
| leak3.c | β | 795 B | view raw | |
| build.sh | β | 72 B | view raw | |
| run.sh | β | 159 B | view raw | |
| run.log | β | 1.9 KB | view raw | |
| env.txt | β | 177 B | view raw | |
| VERDICT.md | β | 1.8 KB | β raw | |
| fix.diff | β | 368 B | view raw | |
| verdict.json | β | 3.0 KB | view raw | |
| fix_run.log | β | 1.1 KB | view raw |
DF-2695 VERDICT
Status: REPRODUCED (unprivileged kernel-stack info leak)
sys_sendfile() leaks 8 bytes of uninitialized kernel stack to userspace
on every failing sendfile(2) call that passes a non-NULL sbytes pointer.
Root cause (line-accurate)
sys/kern/uipc_syscalls.c:1594βoff_t sbytes;(uninitialized).kern_sendfile()writes*sbytes = 0only at:1734, after the early error gotos (done0) at:1699-1732(not VREG, nov_object,holdsockfailure, notSOCK_STREAM, not connected,offset < 0).:1677-1680β the exit path unconditionally runssbytes += hdtr_size; copyout(&sbytes, uap->sbytes, sizeof(off_t));(copyout error ignored), including on the error paths above.
Reproduction
- Unprivileged (
maxx, uid 1001).leak3.cshows a stable leak: a precedingsocket(2)seeds the stack slot, thensendfile(fd, -1, 0, 0, NULL, &sbytes, 0)returns-1/EBADFwithsbytes = 0xfffff80117c88a40β a DragonFly kernel virtual address. A first-call variant leaked0xffffffff809a5db0(kernel text/data range). run.logholds the decisive output (root + unprivileged runs).
Impact
- 8 bytes of kernel stack per call, repeatedly, values include kernel pointers β KASLR defeat / kernel-stack fingerprinting on hardened systems. Limited to a single stack slot (whichever value the previous syscall left at that depth), hence Medium severity (CWE-908 β CWE-200).
- FreeBSD's equivalent code initializes
*sbytesearly and copies it out only on success; the unconditional copyout of an uninitialized local is DragonFly-specific.
Fix validation
fix.diff: off_t sbytes = 0; (one line). Patched kernel (same in-guest
make nativekernel build as DF-2694's fix): poc_sendfile/leak3 now
report 0x0000000000000000 on every path (see fix_run.log).
Fix verification
fixedOne-line fix (off_t sbytes = 0). Patched kernel returns 0 in *sbytes on every early-error path; baseline stable KVA leak gone.
['fix.diff', 'fix_run.log', 'run.log']
Confirmed kernel references
Detail
Exploit chain
unpriv sendfile(valid_file_fd, -1, 0, 0, NULL, &sbytes, 0) -> EBADF -> 8 bytes of stale kernel stack returned in *sbytes; repeatable, includes kernel pointers (KASLR defeat); single stack slot per call, value controllable only via preceding-syscall stack residue.
Evidence (decisive lines)
['run.log (root + unpriv runs; leak3.c stable KVA leak 0xfffff80117c88a40)', 'poc_sendfile.c / leak3.c', 'fix.diff + fix_run.log (patched kernel returns 0 on all paths)']
PoC changes
Added a second program (leak3.c): the initial PoC's printf-per-sample re-dirtied the stack slot and masked the leak after the first call; seeding the slot with a preceding socket(2) immediately before sendfile makes the KVA leak stable across samples.
Verified recommended fix
Initialize 'off_t sbytes = 0;' in sys_sendfile() (and ideally copy out only when kern_sendfile was reached)
Verdict
Unprivileged kernel-stack info leak reproduced: sendfile(2) failing on an early-error path (bad socket fd, non-stream socket, unconnected socket, negative offset) copies out the never-initialized local 'off_t sbytes' (uipc_syscalls.c:1594) to the user's sbytes pointer on every exit path (uipc_syscalls.c:1677-1680), because kern_sendfile() only zeroes sbytes after its early error gotos (:1734 vs :1699-1732). Demonstrated stable leak of a kernel virtual address (0xfffff80117c88a40) as unprivileged uid 1001 by seeding the stack slot with a preceding socket(2) call; also captured 0xffffffff809a5db0 on the first-call variant.
No comments yet.