⬢ DragonFlyBSD Kernel Audit
DF-4777 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4777",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "REPRODUCED: kcs_polled_request() records the BMC-controlled reply byte count unclamped (ipmi_kcs.c:432, i counts every byte the BMC streams in READ state) while storage is bounded at ir_replybuflen=IPMI_MAX_RX=1024; ipmi_ioctl() IPMICTL_RECEIVE_MSG(_TRUNC) then copyouts ir_replylen+1 bytes from the fixed 1024-byte heap buffer (ipmi.c:448-468) -> kernel-heap OOB read of up to 64KB past the kmalloc chunk into an operator-group process. Deterministic 3/3 runs: data_len=16001, 7136+ nonzero bytes beyond offset 1024 including 0xfffff8008dcfc8c8-style slab-linkage kernel pointers (first at chunk+2050, exactly past the 2048-class chunk end) and 424 recognizable marker fragments from live neighbor chunks; control run with a legal <1024 reply shows zero over-read. Transport caveat (documented): the stock QEMU ipmi-bmc-sim caps BMC replies at MAX_IPMI_MSG_SIZE=255 (QEMU hw/ipmi/ipmi_kcs.c ipmi_kcs_handle_rsp), so the transport end-state ir_replylen>1024 - exactly what a hostile BMC produces - was emulated by a root-loaded harness module setting ir_replylen on the completed request; the entire sink (length math + copyout) ran in the unmodified stock kernel and the readout was the unprivileged operator-group user. Fix (clamp in kcs+smic at collection, clamp at ipmi.c sink) built, installed, rebooted: same PoC + same arming -> data_len=1025, zero bytes beyond the buffer, 2/2 runs; driver still functional. Info leak only - no write primitive, no uid=0 from this bug alone.",
  "exploit_chain": "hostile/compromised BMC streams >1024 KCS READ data bytes -> ir_replylen unclamped -> operator-group user RECEIVE_MSG_TRUNC with 65535-byte buffer -> copyout reads up to 65535 bytes from 1024-byte heap chunk -> adjacent-kernel-memory disclosure (slab pointers + neighbor allocation contents), repeatable per ioctl",
  "evidence": "run.log/run.2.log/run.3.log (3 baseline reproductions, block census + marker counts), leak_sample.txt (kernel pointers + pattern at first foreign offset 0x42e), run.control.log (legal reply: no over-read), fix_run.log (patched: data_len=1025, 0 beyond), fix_build.log (BUILD-OK), VERDICT.md (geometry + caveats)",
  "kernel_refs": [
    "sys/dev/misc/ipmi/ipmi_kcs.c:411",
    "sys/dev/misc/ipmi/ipmi_kcs.c:420",
    "sys/dev/misc/ipmi/ipmi_kcs.c:432",
    "sys/dev/misc/ipmi/ipmi.c:448",
    "sys/dev/misc/ipmi/ipmi.c:461",
    "sys/dev/misc/ipmi/ipmi.c:467",
    "sys/dev/misc/ipmi/ipmi.c:534",
    "sys/dev/misc/ipmi/ipmi.c:831",
    "sys/dev/misc/ipmi/ipmi_smic.c:341",
    "sys/sys/ipmi.h:35"
  ],
  "poc_changes": "leak.c self-contained: two fds (SEND purges only the senders completed queue), groom phase (30 same-size-class patterned chunks, free oldest 20 so the victim reuses a chunk below live patterned neighbors), sysctl-poll arming, block census + marker-fragments analysis. bumpko.c KLD (new): devclass walk to ipmi_softc, hot-patch ipmi0 dev_ops.d_ioctl with a wrapper that sets head kreq->ir_replylen under IPMI_LOCK before the original ioctl - substitutes the hostile-BMC transport half that QEMUs 255-byte-capped sim cannot produce.",
  "attempts": 5,
  "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 (baseline) / #1: Wed Sep 23 15:29:40 UTC 2026 (fix validation)",
  "runtime_sec": 2400,
  "guest_dirty": 1,
  "build_cmd": "see build.sh (kmod via bsd.kmod.mk + cc -O2 -o /tmp/leak leak.c); fix: make nativekernel KERNCONF=X86_64_GENERIC && make installkernel",
  "run_cmd": "see run.sh (run_user: /tmp/leak & ; run_root: sysctl hw.bumpreplylen=16000 ; cat /tmp/leak.out)",
  "code_hash": "138e731a895e6f288de0d299ead501a84eb27a7e438d7a4d843d33250353545a",
  "notes": "Pass-2 new finding, deeper consequence of the same reply loop as DF-1865 (which covers the infinite-loop hang; this covers the COMPLETING case i>buflen -> memory disclosure - distinct sink and impact). SMIC twin ipmi_smic.c:341 included in fix. Severity Medium: hostile-BMC precondition + operator-group readout; leak size fully attacker-chosen up to 64KB per ioctl, repeatable.",
  "recommended_fix": "Clamp the reply count to ir_replybuflen at collection (kcs+smic) and clamp len to ir_replybuflen+1 in the ipmi.c RECEIVE sink (fix.diff, validated live)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to guest /usr/src, nativekernel+installkernel BUILD-OK, rebooted into #1 Wed Sep 23 15:29:40 UTC 2026; identical PoC+arming on the patched kernel returns data_len=1025 with ZERO bytes beyond the 1024-byte buffer (vs 16001/7136-nonzero/424-markers baseline), 2/2 runs, driver still functional. In the module-assisted flow the ipmi.c sink clamp is the active defense; the kcs/smic collection clamps cover the real hostile-BMC path.",
  "fix_evidence": "fix.diff; fix_build.log (BUILD-OK); fix_run.log (data_len=1025, 0 beyond); VERDICT.md fix section"
}