DF-5806 / verdict.json
{ "finding_id": "DF-5806", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "soreceive() (sys/kern/uipc_socket.c:1596-1600) takes a zero-progress `goto restart` whenever orig_resid == resid; with MSG_PEEK nothing is ever consumed, so a receive-queue record that delivers zero bytes (AF_UNIX SOCK_STREAM control-only record via SCM_RIGHTS sendmsg with zero-length data, or an AF_UNIX SOCK_SEQPACKET zero-length datagram) makes recv(MSG_PEEK) spin forever in kernel mode. The loop never sleeps, so the thread is unkillable (kill -9 is never delivered) and permanently monopolizes a CPU; empirically a single unprivileged spinner starved ALL userland on the 6-vCPU guest (sshd included - ssh fails at banner exchange) within seconds, 3/3 fresh-boot runs, recovery only by VM power-cycle. AF_UNIX stream lacks PR_ADDR (uipc_proto.c:53) so the orig_resid=0 suppression at uipc_socket.c:1409 does not apply, and controlp==NULL (plain recv) bypasses the suppression at 1446-1449; cc=24 (cmsg) >= lowat=1 keeps the head-of-queue blocking check (1340-1346) from ever sleeping. UDP/dgram (PR_ADDR) and TCP (sorecvtcp; sbcompress drops zero-length mbufs) are not affected.", "exploit_chain": "unpriv user -> socketpair(AF_UNIX, SOCK_STREAM) -> sendmsg(SCM_RIGHTS control, 0-byte data) creating [control][0-len] record -> recv(sv[1], buf, N>=1, MSG_PEEK) with no control buffer -> permanent kernel-mode spin -> all userland starves -> guest-wide permanent DoS (no kernel memory corruption; no uid0 path identified - the loop holds only the per-socket ssb token)", "evidence": "run.log (3 baseline runs: ssh banner-exchange timeouts, vm.sh status down with qemu alive, root realtime console observer frozen mid-loop), fix-run.log (patched kernel: 12 simultaneous spinners all exit instantly, guest stays responsive, load 0.16), VERDICT.md for the full line-cited analysis, fix.diff applied+buildkernel+installkernel BUILD-RC=0/INSTALL-RC=0 with uname confirming kernel #1 2026-10-13", "kernel_refs": [ "sys/kern/uipc_socket.c:1596", "sys/kern/uipc_socket.c:1599", "sys/kern/uipc_socket.c:1340", "sys/kern/uipc_socket.c:1409", "sys/kern/uipc_socket.c:1446", "sys/kern/uipc_socket.c:829", "sys/kern/uipc_socket.c:863", "sys/kern/uipc_socket.c:1926", "sys/kern/uipc_sockbuf.c:196", "sys/kern/uipc_usrreq.c:756", "sys/kern/uipc_proto.c:53", "sys/kern/uipc_socket2.c:673" ], "poc_changes": "PoC authored fresh for this finding (no seed): fork-per-spinner design so the wedging recv runs in children while the parent reports; two variants (STREAM+SCM_RIGHTS control-only record; SEQPACKET zero-length datagram) covering both reachable zero-byte record shapes.", "attempts": 4, "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", "runtime_sec": 2280, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o peekspin peekspin.c", "run_cmd": "./peekspin 0 1", "code_hash": "fb94e95ff59e743fa3787e8bde681ded0ddaba588b205e81588fb7eef38f0a95", "notes": "Guest was reset to the with-src snapshot after validation (guest_dirty=0). Baseline reproduced 3x on fresh boots; on the 3rd run the wedge hit before the launching ssh session could drain output. A root observer at realtime priority also froze once the wedge took hold, demonstrating userland starvation beyond the spinning CPU. kill -9 ineffectiveness is certain from code (the loop never sleeps, signals are only delivered at sleep/AST) and corroborated by the observer freeze. ps-state capture of the spinner itself was not possible because no new process could be scheduled once wedged. Fix validated: nativekernel rebuild of guest /usr/src with fix.diff applied, installkernel, reboot into kernel #1 2026-10-13 00:10:42, exact same PoC (1 spinner and 12-spinner barrage) - all children exit instantly and the guest stays fully responsive.", "recommended_fix": "In soreceive() and sorecvtcp(), never take the zero-progress restart when MSG_PEEK is set: add `(flags & MSG_PEEK) == 0` to the restart conditions (uipc_socket.c:1596-1600 and 1926-1930) so a zero-byte peek returns a short read instead of spinning; see evidence-pack fix.diff (validated: baseline wedges, patched returns instantly).", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Baseline on stock kernel: single unprivileged spinner permanently wedged the whole guest (3/3 fresh-boot runs; ssh banner-exchange timeouts; realtime-priority root observer frozen). Patched kernel (guest /usr/src + fix.diff, make nativekernel + installkernel, kernel #1 2026-10-13): the identical PoC - single spinner and a 12-spinner barrage of both variants - exits instantly every time and the guest remains fully responsive (load 0.16). The livelock is eliminated with no behavioral regression observed (non-PEEK paths untouched; every non-PEEK restart still consumes at least the soname/control mbufs of the scanned record, so no new spin is possible).", "fix_evidence": "findings/poc/DF-5806/fix-run.log (verbatim session output of patched-kernel runs), fix.diff (git-apply checked), VERDICT.md fix section; build+install RC=0 lines and patched-kernel uname recorded in fix-run.log" } |