DF-2714 / verdict.json
{ "finding_id": "DF-2714", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "sys_sendfile() marshals hdtr headers into an unbounded M_WAITOK mbuf chain (m_uiomove) before any socket validation or buffer accounting; reproduced as unprivileged user maxx on the stock kernel: a 56 MB header request transiently held 26684/33296 (80%) of the global mbuf cluster pool, and a 256 MB request left the syscall permanently blocked in the objcache allocator (D3, WCHAN=objcache) holding essentially every cluster, unkillable with kill -9, requiring a guest reset. A 1 MB cap on header/trailer totals (fix.diff, kernel #2) removes both the spike and the wedge (peak cluster sample 256/33296 = idle baseline).", "exploit_chain": "open(\"/etc/passwd\") for fd; socket(AF_INET,SOCK_DGRAM) for s (any socket works - validation happens after marshalling); sf_hdtr with one iovec of attacker-chosen length; sendfile() allocates attacker-chosen mbuf memory. Below pool size: transient 80%+ pool hold. Above pool size: uninterruptible sleep holding the entire cluster pool -> system-wide network starvation until reboot.", "evidence": [ "run.log: '26684/33296 mbuf clusters in use' sampled at 100ms during the 56MB run (baseline 256), call then returns EINVAL", "run.2.log: '33494/33296 mbuf clusters in use', process 'D3 objcache', survives kill -9, SYSCALL_RETURNED never printed", "fix_run.log: kernel #2 with fix.diff - 56MB and 256MB both EINVAL instantly, PEAK_CLUSTER_SAMPLE '256/33296'" ], "kernel_refs": [ "sys/kern/uipc_syscalls.c:1617", "sys/kern/uipc_syscalls.c:1625", "sys/kern/uipc_syscalls.c:1637", "sys/kern/uipc_syscalls.c:1645", "sys/kern/uipc_syscalls.c:1654", "sys/kern/kern_subr.c:455", "sys/kern/kern_subr.c:488", "sys/kern/uipc_mbuf.c:2664" ], "poc_changes": "Initial seed had no struct sf_hdtr (taken from sys/socket.h), missing errno.h; header size parameterized so the same binary performs both the bounded and the wedge variant.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1140, "guest_dirty": 0, "build_cmd": "cc -O2 -o sendfile_hdtr_dos sendfile_hdtr_dos.c", "run_cmd": "./sendfile_hdtr_dos 56 ; ./sendfile_hdtr_dos 256", "code_hash": "321a12de8f73eee8f31060d7335f34a91c86c0217e5abf4840806768fcfd2d35", "notes": "Wedge run required guest reset (performed). Contrast case: sosend()/sendmsg self-throttles on ssb_space so a single sendmsg cannot hold more than the socket send-buffer worth of mbufs - the sendfile header path has no equivalent bound. The sbytes=1 printed on failing calls is the already-filed DF-2695 uninit stack copyout, not re-reported.", "recommended_fix": "Cap sendfile hdtr header and trailer totals (SF_HDTR_MAXBYTES = 1MB) immediately after iovec_copyin, before m_uiomove (uipc_syscalls.c:1629/1659).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2: Mon Aug 31 04:55:00 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Applied fix.diff (SF_HDTR_MAXBYTES caps) to the guest's /usr/src, make nativekernel + installkernel (kernel #2, also carrying the DF-2713 fix - independent file), rebooted, re-ran the identical PoC at 56MB and 256MB: both return EINVAL immediately and the 100ms cluster sampler recorded a peak of 256/33296 (idle baseline). No allocation spike, no wedge.", "fix_evidence": [ "fix_run.log", "fix.diff" ] } |