⬢ DragonFlyBSD Kernel Audit
DF-5830 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5830",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "REPRODUCED on the stock guest kernel (DragonFly 6.5-DEVELOPMENT #0 Jul 2 2026), as unprivileged uid 1000, from a fresh vm.sh reset. sys_sendmsg() (sys/kern/uipc_syscalls.c:934-948) allocates the MT_CONTROL mbuf and copies in user ancillary data, then calls kern_sendmsg(), which returns early at its holdsock() failure (uipc_syscalls.c:799-801) for a non-socket/bad fd BEFORE any consumer takes the mbuf; sys_sendmsgs cleanup (uipc_syscalls.c:953-958) frees iov and sa but never control. Exactly one 256-byte mbuf is leaked per failing sendmsg(2): measured 7->20007->40007->60007/146632 mbufs for 3x20000 calls, permanent (never reclaimed after process exit), linear, and reproducible from a fresh boot. A detached 3M-iteration run exhausted the pool (154460 mbufs, 154453 attributed by the kernel to ancillary data, 77742 Kbytes, 55% of mb_map, 133 kernel allocation requests denied), spewed Warning: objcache(mbuf) exhausted on cpu0/1/4/5! on the serial console, and left the leaking process in state D (wchan objcache) immune to kill -9 - a reboot-only recovery. Impact is local DoS (resource exhaustion, no memory corruption); ceiling consistent with sibling finding DF-2714, hence Medium.",
  "exploit_chain": "unprivileged user opens any regular file (DTYPE_VNODE fd) and loops sendmsg(fd, msghdr with 16..MLEN bytes of msg_control, 0); each call leaks one MT_CONTROL mbuf permanently; ~146k iterations (~37 MB, <1 minute) exhaust the mbuf objcache system-wide: network-memory allocations are denied, console spews objcache(mbuf) exhausted, and the loop parks in m_get(M_WAITOK) in D-state surviving kill -9. No privilege or special configuration required.",
  "evidence": "run.log (control experiment: real socketpair 20000x => no growth; then 3x20000 leak rounds => 7->20007->40007->60007 exactly +1/call); run.2.log (fresh-boot re-confirmation 7->20007); run.3.log + wedge_evidence.txt (full-pool exhaustion: 154460/146632 frozen, 154453 mbufs allocated to ancillary data, 133 requests denied, kill -9 immunity, objcache-exhausted console warnings); fix_validation.log + fix.diff (patched kernel #1 Oct 13: same protocol => 7/146632 flat across 3 rounds; happy-path and EFAULT-copyin regression clean).",
  "kernel_refs": [
    "sys/kern/uipc_syscalls.c:934-948",
    "sys/kern/uipc_syscalls.c:950",
    "sys/kern/uipc_syscalls.c:799-801",
    "sys/kern/uipc_syscalls.c:953-958",
    "sys/kern/uipc_socket.c:938-942",
    "sys/kern/uipc_socket.c:1060-1061",
    "sys/kern/uipc_socket.c:1110-1111"
  ],
  "poc_changes": "PoC authored fresh for this finding (no seed). Uses only open()+sendmsg()+socketpair(); dual mode: leak mode (regular-file fd, expects ENOTSOCK per call) and ok control mode (real AF_UNIX socketpair with SCM_RIGHTS control actually consumed by the peer).",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (leak reproduced on this stock kernel; fix validated on rebuilt #1: Tue Oct 13 09:45:32 UTC 2026)",
  "runtime_sec": 1100,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o leaksendmsg leaksendmsg.c",
  "run_cmd": "netstat -m | sed -n 1p; ./leaksendmsg 20000; netstat -m | sed -n 1p",
  "code_hash": "a3e74fc718d49c6effa6ea6aa9a327d8cafb48b1824fd3949f3d3f05b1815840",
  "notes": "Guest was dirtied during verification (mbuf-pool wedge + patched-kernel install); vm.sh reset with-src executed before returning, guest left clean on stock snapshot. The only other in-tree kern_sendmsg() callers (sys_sendto uipc_syscalls.c:876, sendfile trailers uipc_syscalls.c:1666) pass control==NULL, so the leak is specific to sendmsg(2). sys_sendfile() was re-checked for the same shape: its header chain is freed on early error (uipc_syscalls.c:1970-1971) - no sibling leak. The +1 mbuf in the EFAULT regression snapshot is the ssh execs own transient socket, not cumulative over 50000 iterations.",
  "recommended_fix": "kern_sendmsg(): on the holdsock() early-return path, m_freem(control) before returning, making kern_sendmsg() consume the control mbuf on all paths (matching sosend()s contract) - see fix.diff.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "FIXED. fix.diff (kern_sendmsg frees control on the holdsock() early return) applied inside the guest /usr/src copy, kernel rebuilt (make -j4 nativekernel KERNCONF=X86_64_GENERIC, 367 s) and booted (#1 Oct 13 09:45:32). Baseline protocol 3x20000 leak rounds: stock kernel 7->20007->40007->60007; patched kernel 7->7->7 (leak gone). Regression: 20000 happy-path sendmsg-with-control on a real socketpair (no double-free, no panic) and 50000 control-copyin-EFAULT iterations (m_free path at uipc_syscalls.c:944-947, no double-free, no panic). fix_patched_reproduced=0 means the leak no longer reproduces on the patched kernel.",
  "fix_evidence": "fix_validation.log (build+install transcript, patched-kernel run outputs); fix.diff (git-apply-able, sha256 352a552bd9119a2b753b4d6e5a54441634c49bff25a40689d08d49c7db6140c6)"
}