DragonFlyBSD Kernel Audit
DF-2694 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2694",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Unprivileged local kernel panic reproduced twice (TCP sorecvtcp KKASSERT at uipc_socket.c:1852; AF_UNIX generic soreceive -> sbunlinkmbuf KKASSERT at uipc_sockbuf.c:552) by racing recv() into sleeping (swap-in) page faults against shutdown(fd, SHUT_RD). Root cause: soshutdown() bypasses ssb_lock by design, and sorflush() frees the receive mbufs holding only the ssb token - but a blocked DFly thread releases its lwkt tokens (lwkt_switch->lwkt_relalltokens), so the moment the copier sleeps in uiomove() its chain is freed underneath it (M_SOLOCKED is honored only by sbcompress, never on the flush/free path). The copier performs uiomove() out of freed clusters (UAF read) and then trips the sockbuf invariants. On non-INVARIANTS production kernels the generic path instead stores a stale freed-memory pointer into sb->sb_mb (sockbuf corruption).",
  "exploit_chain": "unpriv user -> thread A recv() on socket with queued data into pre-swapped (or otherwise cold) user pages -> thread B shutdown(fd, SHUT_RD) while A sleeps in a vm_fault inside uiomove() -> sorflush frees/zeroes so_rcv under A -> A resumes copying freed mbufs -> panic (INVARIANTS) or sb->sb_mb stale-pointer store (production). Escalation to uid=0 blocked on the audited INVARIANTS build: the assert/NULL-deref fires inside the same syscall before any harvestable state is returned; documented as hard blocker.",
  "evidence": [
    "panic.txt (assertion \"m\" failed in sorecvtcp at uipc_socket.c:1852, TCP mode)",
    "panic2.txt (assertion \"sb->sb_mb == m\" failed in sbunlinkmbuf at uipc_sockbuf.c:552, AF_UNIX mode)",
    "run.log (both decisive baseline runs; note on why sleeping faults are required)",
    "fix.diff + fix_build.log + fix_run.log (patched kernel: no panic, rounds complete)"
  ],
  "kernel_refs": [
    "sys/kern/uipc_socket.c:1953-1957",
    "sys/kern/uipc_socket.c:1963-1991",
    "sys/kern/uipc_socket.c:1746-1753",
    "sys/kern/uipc_socket.c:1756-1758",
    "sys/kern/uipc_socket.c:1851-1852",
    "sys/kern/uipc_socket2.c:753-760",
    "sys/kern/uipc_sockbuf.c:451-508",
    "sys/kern/uipc_sockbuf.c:376",
    "sys/kern/uipc_sockbuf.c:548-578",
    "sys/kern/uipc_mbuf.c:m_free",
    "sys/kern/lwkt_thread.c:lwkt_switch/lwkt_relalltokens",
    "sys/kern/uipc_socket.c:1322"
  ],
  "poc_changes": "Initial trigger used usleep() delays (quantized to 10ms ticks at hz=100) and resident/zero-fill receive windows: no hits, because the copier never slept and its lwkt token stayed held. Fixed by (a) spin-barrier reader start + busy-wait delays, (b) SIGPIPE ignore (writer EPIPE killed the process), (c) closing the peer side before joining the blocked writer (deadlock), (d) pre-swapping a pool of receive windows with a 3.4GB memory pig so every uiomove fault sleeps in vm_fault - this alone turned 0/900+ benign rounds into a 2/2 panic rate.",
  "attempts": 9,
  "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": 3900,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -pipe -pthread -o poc_race poc_race.c",
  "run_cmd": "./poc_race 40 3000 3000 0   # and: ./poc_race 60 200 500 1",
  "code_hash": "9e7afec3e66f57614f46f92250cfa49699dfa59a89c9189988df1704bd625e3b",
  "notes": "Two distinct receive paths affected (sorecvtcp for TCP via tcp_usrreq.c:994/1020; generic soreceive for AF_UNIX/UDP/raw). Requires the copier to block mid-copy (swap-in or file-I/O fault on the user receive buffer) - realistic under memory pressure. Upstream DragonFly HEAD is byte-identical (checked via gitweb): bug is live upstream. Fix validated by in-guest nativekernel rebuild. Fix history: v1 (SOLOCKED scan) incomplete for generic soreceive; v2 (SSB_LOCK wait) validated on both paths.",
  "recommended_fix": "sorflush(): after socantrcvmore(), poll (bounded) under the token until no M_SOLOCKED mbufs remain before snapshotting/zeroing/freeing the receive sockbuf",
  "status": "reproduced",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly 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",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix v1 (wait for M_SOLOCKED mbufs) protected only the TCP path - AF_UNIX still panicked at sbunlinkmbuf:552 on the v1 kernel; fix v2 (shipped: wait, bounded 1000x1-tick, for SSB_LOCK to clear in sorflush after socantrcvmore) covers every receiver path: identical triggers that panicked 2/2 on stock now complete cleanly 0/2 (mode 0: 40 rounds, mode 1: 60 rounds), guest stays up, EOF semantics and data integrity preserved.",
  "fix_evidence": [
    "fix.diff (v2)",
    "fix_build.log",
    "fix_run.log",
    "panic.txt",
    "panic2.txt"
  ]
}