⬢ DragonFlyBSD Kernel Audit
DF-3539 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3539",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Unpatched: the SET_CONFIG realloc (cpu0 netisr, ngc_send->ng_send_msg direct dispatch) frees sc->sbuf/sc->abuf while nga_rcv_async/nga_rcv_sync run unlocked in ksocket-upcall ip-input context on other CPUs; the corrupted state then defeats the SYNC_BUF_SIZE(cfg.amru) guard and the decode loop writes attacker bytes without bound past the 8KB sbuf (QEMU-monitor proof: CPU0 spinning in nga_rcv_async with sc->slen=0x5655=22101 vs allocation <=8202; two additional silent hard lockups <20s each). Patched with fix.diff (per-byte spinlock consistency for (cfg,buf,slen), serialized SET_CONFIG, retired frees after unlock): 10-minute soak of the identical trigger plus cycle-mode storm survived with the guest alive and responsive. Root-gated trigger (v1 control socket requires SYSCAP_RESTRICTEDROOT) - Medium severity, memcorrupt bucket.",
  "exploit_chain": "root -> ng control socket -> graph [S]-sync-[async]-async-[ksocket udp] -> 3x UDP flooders drive the decode loop in ip-input context on CPUs 1-5 -> concurrent SET_CONFIG amru 8191<->8192 storm executes kfree(sc->sbuf) mid-decode -> write-after-free corrupts ng_async_private (cfg.amru/slen) -> guard passes with bogus limit -> sc->sbuf[sc->slen++] walks 14KB+ past the heap chunk writing 0x41/A pattern -> kernel heap flattened -> silent hard lockup (no panic banner, no dump; vm.sh DDB-heuristic kill). Escalation to uid0 not pursued: trigger itself requires root (SYSCAP_RESTRICTEDROOT on v1 ng control sockets), so there is no unprivileged entry point on a stock system.",
  "evidence": "df3539_regs.txt + df3539_cpu0.txt (frozen-vCPU registers: RIP in ng_async.ko, RDX=sc->slen=22101); VERDICT.md embeds the disassembly match of the spin site to nga_rcv_async (movq 0x28(%rbx)=sc->sbuf, movl 0x30(%rbx)=sc->slen, movzwl 0x1a(%rbx)=sc->fcs/PPP_FCS); console_run2.log (silent lockup #2); fix.diff + 10-min patched soak",
  "kernel_refs": [
    "sys/netgraph/async/ng_async.c:317",
    "sys/netgraph/async/ng_async.c:325",
    "sys/netgraph/async/ng_async.c:339",
    "sys/netgraph/async/ng_async.c:595",
    "sys/netgraph/async/ng_async.c:600",
    "sys/netgraph/netgraph/ng_base.c:1226",
    "sys/netgraph/netgraph/ng_base.c:1687",
    "sys/netgraph/socket/ng_socket.c:257",
    "sys/netgraph/socket/ng_socket.c:400",
    "sys/netgraph/ksocket/ng_ksocket.c:605"
  ],
  "poc_changes": "Rewrote the seed as a fork()-based harness (race2.c): control driver in one process at 70k msg/s + separate UDP flood processes (the pthread prototype crawled at ~45 msg/s due to a userspace pathology); per-run unique node names and UDP ports (stale graphs from earlier runs otherwise collide); v1 quirks handled: NG_CONTROL protocol number is 2 (inverted vs ng7), absolute node paths need a trailing colon, sockaddr sa_len must be set, ksocket address is encoded in the hook name (inet/dgram/udp), ng_ksocket delivers via socket upcall so rcvdata runs in ip-input context rather than cpu0 netisr.",
  "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",
  "runtime_sec": 1450,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -Wall -o /root/race2 /root/race2.c",
  "run_cmd": "/root/race2 all config   (kldload netgraph ng_socket ng_async ng_ksocket first)",
  "code_hash": "bd82ca472a1ba4a0869bc27b159e2388babe096c40aa10ce91678d501329fd19",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline unpatched: 2x silent hard lockup <20s + 1x captured wedge with register proof of the unbounded overflow. Patched (module rebuilt from fix.diff, /boot/kernel/ng_async.ko swapped): identical trigger survived a 10-minute soak (alive at 2/4/6/8/10 min checks, ssh responsive, mbufs stable) plus a create/destroy cycle-mode storm; bad behavior gone.",
  "fix_evidence": "fix.diff (git-apply-clean against sys/netgraph/async/ng_async.c); patched module build log in session; soak timings in VERDICT.md",
  "notes": "Impact classified panic (machine death without banner); the underlying primitive is unbounded kernel heap corruption - memcorrupt bucket. Residual: nga_shutdown frees sc/node while in-flight rcvdata may hold it - v1-core-wide lifetime issue, documented in VERDICT.md, out of scope for a per-node lock. vm.sh gained a QEMU monitor unix socket (boot-mon.sock) this session for wedged-machine forensics.",
  "recommended_fix": "Serialize ng_async control-path mutation against the data path: per-node spinlock making the decode guard+write and every abuf write atomic vs SET_CONFIGs realloc/cfg swap, with retired buffers freed only after unlock (see fix.diff)."
}