DF-4777 / verdict.json
{ "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" } |