⬢ DragonFlyBSD Kernel Audit
DF-4978 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4978",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "REPRODUCED and FIX VALIDATED. The verbatim SSIF transport (ipmi_ssif.c compiled unmodified into the harness) stores the BMC-accumulated multi-part reply total unclamped into req->ir_replylen (ipmi_ssif.c:304, accumulation at :294, single-part :240) -- a protocol-legal hostile BMC sending >33 full 32-byte continuation blocks yields ir_replylen=16023 over the fixed 1024-byte ioctl reply buffer. The shared stock-kernel sink (ipmi.c:448-468) then copyouts 16023 bytes from that buffer: 3/3 end-to-end runs on a real /dev/ipmi0 disclosed 7159/7183/7190 nonzero kernel-heap bytes past offset 1024 (kernel slab pointers at ir_reply[1070]+, 426 recognizable neighbor-chunk markers) to an unprivileged operator-group user; control run with a legal length leaked nothing. Two-line clamp fix (transport + sink) validated on a rebuilt kernel: producer clamps to 1024, sink returns 1025 with zero bytes beyond, IPMI device fully functional. Third transport carrying the DF-4777 (kcs) / DF-4948 (smic) shape.",
  "exploit_chain": "hostile/compromised BMC (or SMBus device at the BMC slave address) on an SSIF interface -> multi-part read with 515 legal 32-byte 0xff continuation blocks + numbered terminator -> ir_replylen=16023 on a completed request -> unprivileged operator-group user issues IPMICTL_RECEIVE_MSG(_TRUNC) with a large data_len -> ipmi.c copyout reads 14999 bytes past the 1024-byte ir_reply heap chunk -> kernel heap disclosure (slab linkage pointers -> kernel-address info; neighboring allocation contents). Info-leak primitive only: no write, no uid=0 from this bug alone.",
  "evidence": "run.log (producer: BUG DF-4978 REPRODUCED, ir_replylen=16023/1024); run.2.log + run.3.log + run.log (sink 3/3: data_len=16024, 7159/7183/7190 nonzero past 1024, first 0x042e, 426 markers); run.control.log (legal arm: 0 bytes beyond); leak_sample.txt (0xfffff8008dcfxxxx kernel pointers at ir_reply[1070]+); fix_run.log (patched kernel: ir_replylen=1024 clamped, data_len=1025, 0 beyond, ioctls functional); fix_build.log (37637-line validated kernel build); VERDICT.md for the full narrative incl. the inverted 0xff break condition at ipmi_ssif.c:297.",
  "kernel_refs": [
    "sys/dev/misc/ipmi/ipmi_ssif.c:304",
    "sys/dev/misc/ipmi/ipmi_ssif.c:294",
    "sys/dev/misc/ipmi/ipmi_ssif.c:240",
    "sys/dev/misc/ipmi/ipmi_ssif.c:291-293",
    "sys/dev/misc/ipmi/ipmi.c:448-468",
    "sys/dev/misc/ipmi/ipmi.c:401-403"
  ],
  "poc_changes": "Seed sketch (none existed -- new pass-2 finding) was authored from scratch modeled on the validated DF-4777/DF-4948 methodology: (1) mock headers live in the module dir because bsd.kmod.mk puts -I. ahead of -Idragonfly, shadowing the real bus/smbus/smbconf.h; (2) first producer run returned ir_replylen=58 because DragonFlys multi-part break condition is INVERTED (breaks on non-0xff at ipmi_ssif.c:297, unlike FreeBSD) -- the hostile script was corrected to send 0xff-marked continuation blocks terminated by one numbered block; (3) bumpko.c/leak.c reused verbatim from DF-4777; (4) control run initially raced a stale sysctl arm value -- redone with explicit disarm first.",
  "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 (baseline runs); fix validation on #1: Fri Sep 25 14:44:16 UTC 2026 same tree + fix.diff",
  "runtime_sec": 1620,
  "guest_dirty": 0,
  "build_cmd": "vm.sh run_root sh /root/ssifshock/build.sh  (compiles verbatim /usr/src ipmi_ssif.c + bumpko.ko + /tmp/leak)",
  "run_cmd": "vm.sh run_root kldload /root/ssifshock/ssifshock.ko (producer); QEMU_EXTRA=-device ipmi-bmc-sim,id=bmc0 -device isa-ipmi-kcs,bmc=bmc0 boot + kldload ipmi + kldload bumpko.ko + unpriv /tmp/leak + sysctl hw.bumpreplylen=16023 (sink)",
  "code_hash": "def4e8bfd3fa98a5ab8ea3d5233d9143ae3b0cf7b514f3fca2eb8ac78cbe2ec0",
  "notes": "Per-block counts all exactly 32 = fully SMBus-legal, distinguishing this from DF-2025 (count>32 stack bcopy over-read) and DF-2026 (unterminated loop) -- pass-2 scope was to answer does SSIF also store an unclamped replylen?: YES. Bonus discovery documented in VERDICT.md: the :297 break condition is inverted vs the SSIF spec/FreeBSD (numbered block terminates, 0xff continues), which also silently truncates compliant multi-part replies; functional bug, not filed separately. size_t->int truncation of len at :304 (negative ir_replylen after ~2^32 accumulation, chained on DF-2026s uncapped loop) is neutralized by the clamp fix.",
  "recommended_fix": "Clamp the stored total to the reply buffer at ipmi_ssif.c:304 and clamp again in the shared ipmi.c sink (len = min(len, ir_replybuflen + 1)) -- exactly fix.diff, live-validated on a rebuilt kernel.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (transport clamp at ipmi_ssif.c:304 + sink clamp at ipmi.c:448) applied to the guest /usr/src, kernel rebuilt (make -j6 nativekernel KERNCONF=X86_64_GENERIC, 37637-line fix_build.log) and booted: producer now reports ir_replylen=1024 (clamped) for the identical 515-block hostile script, and the armed 16023 end-state on the real /dev/ipmi0 yields msg.data_len=1025 with ZERO nonzero bytes beyond 1024 and zero marker fragments; all IPMI ioctls still succeed (device functional). Baseline vs patched delta is conclusive.",
  "fix_evidence": "fix_run.log (patched-kernel producer + sink output), fix_build.log (full kernel rebuild), VERDICT.md Fix validation section with baseline-vs-patched table"
}