β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2695

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.

--- 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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2695 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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 = 0 only at :1734, after the early error gotos (done0) at :1699-1732 (not VREG, no v_object, holdsock failure, not SOCK_STREAM, not connected, offset < 0).
  • :1677-1680 β€” the exit path unconditionally runs sbytes += hdtr_size; copyout(&sbytes, uap->sbytes, sizeof(off_t)); (copyout error ignored), including on the error paths above.

Reproduction

  • Unprivileged (maxx, uid 1001). leak3.c shows a stable leak: a preceding socket(2) seeds the stack slot, then sendfile(fd, -1, 0, 0, NULL, &sbytes, 0) returns -1/EBADF with sbytes = 0xfffff80117c88a40 β€” a DragonFly kernel virtual address. A first-call variant leaked 0xffffffff809a5db0 (kernel text/data range).
  • run.log holds 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 *sbytes early 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

fixed
baseline reproduced→ patch + rebuild →patched clean

One-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']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sun Aug 30 23:56:25 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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.