# DF-4948 — VERDICT

**REPRODUCED — kernel-heap out-of-bounds READ (info leak), producer half
proven live against the verbatim SMIC transport source, sink half proven
end-to-end 3/3 runs with recognizable kernel-memory content on the stock
kernel, and the one-file fix validated live on a rebuilt kernel
(over-read eliminated, device still functional).**

## Root cause

`sys/dev/misc/ipmi/ipmi_smic.c` — `smic_polled_request()`:

- reply collection loop (ipmi_smic.c:320-336): `i` counts **every** data
  byte the BMC streams before signaling RD_END. Storage is bounded
  (`if (i < req->ir_replybuflen)` at :325) but the count is not.
- `req->ir_replylen = i;` (ipmi_smic.c:341) records the BMC-controlled
  total; the driver even *detects* the condition and proceeds anyway
  ("SMIC: Read short: 1024 buffer, 16000 actual", :346-350).
- The shared consumer `ipmi_ioctl()` IPMICTL_RECEIVE_MSG(_TRUNC)
  (sys/dev/misc/ipmi/ipmi.c:448-468) trusts it: `len = ir_replylen + 1`,
  `len = min(recv->msg.data_len, len)` (user `data_len` up to 65535),
  `copyout(kreq->ir_reply, ..., len - 1)` out of a buffer of exactly
  `IPMI_MAX_RX = 1024` bytes for every ioctl request (ipmi.c:401-403,
  534-546; sys/sys/ipmi.h:35). A BMC streaming > 1024 data bytes
  (trivial for a hostile controller — each byte is one SMIC port
  transaction) turns every RECEIVE ioctl into a heap disclosure of up to
  64 KB past the `kmalloc` chunk, readable by any process in group
  `operator` (/dev/ipmi0 0660 root:operator, ipmi.c:831-832).

This is the exact twin of DF-4777 (ipmi_kcs.c:411-432, KCS backend). The
DF-4777 verification called this line out ("The SMIC backend has the
identical defect (ipmi_smic.c:341) — same fix") and its fix.diff carried a
SMIC hunk, but that fix was validation-only — `sys/` is a read-only audit
target — so both the KCS and SMIC producers and the ipmi.c sink remain
unclamped in the tree (confirmed: ipmi_smic.c:341 and ipmi.c:448 today).

Attack class: CWE-125 (OOB read) across the hostile-management-controller
trust boundary; impact is adjacent-kernel-memory disclosure (info leak),
same as DF-4777.

## How it was reproduced

### Producer (new for this finding — the SMIC transport itself)

QEMU has no SMIC interface emulation (only KCS/BT), so the transport half
was verified with `smicshock.ko`: a root-loaded harness that compiles the
**verbatim** `/usr/src/sys/dev/misc/ipmi/ipmi_smic.c` (copied unmodified
by build.sh, `-DLOCAL_MODULE`) into the module, substituting only INB/OUTB
(via mock `<ipmivars.h>`) with a scripted hostile-BMC SMIC register
emulator that executes each protocol step exactly as the silicon would:
WR_START/WR_NEXT/WR_END accepted, SC status codes returned, reply
netfn/command echoed, then **16000 data bytes streamed** with the last
staged RD_END. (The emulator also accepts any control write in the read
phase as RD_NEXT, which is what tolerates the driver's CC/SC mixup at
ipmi_smic.c:202 — DF-4949.)

Baseline (clean-source snapshot, stock kernel, run.log phase 1):

```
smicshock: SMIC: Read short: 1024 buffer, 16000 actual   <- driver sees it
smicshock: smic_polled_request() = 1                     <- returns success
smicshock: req->ir_replylen   = 16000                    <- unclamped (bug)
smicshock: req->ir_replybuflen = 1024
```

### Sink (end-to-end, shared consumer, unmodified kernel)

With a real `/dev/ipmi0` (QEMU `ipmi-bmc-sim` + `isa-ipmi-kcs`),
`bumpko.ko` (DF-4777's arming module, reused verbatim) sets
`ir_replylen = 16000` on a completed request under IPMI_LOCK — byte-for-
byte the end state the hostile SMIC transport produced above — and the
unprivileged readout (maxx, uid 1001, group operator) runs the stock
RECEIVE path. Observed 3/3 independent runs (run.log, run.2.log,
run.3.log):

```
RECEIVE ok: msg.data_len = 16001 (compcode + 16000 reply bytes)
nonzero in [0,1024):     1          <- the BMC's real (1-byte-ish) reply
nonzero in [1024,16000): 6624-7167  (first at 0x042e, all runs)
marker fragments beyond the victim BMC data: 392-424
```

Disclosed content (leak_sample.txt): `0xfffff8008dcf....` kernel heap
(slab-zone linkage of the adjacent 2048-class chunk) beginning 70 bytes
past the victim chunk end — matching the allocation geometry — plus
hundreds of "DF4777MARK-" fragments from *live* neighboring allocations
(~940 nonzero bytes per 1024-byte stride in the block census).

Honest scope: info **leak**, not a write; uid=0 is not reachable from this
bug alone. The disclosed slab-adjacent contents (kernel pointers, neighbor
allocation data) feed a separate exploitation chain; on this guest (no
KASLR) the pointers directly identify heap geometry.

## Fix validation (done)

`fix.diff` (2 hunks, `patch -p1` clean on the guest's /usr/src):

1. `ipmi_smic.c`: clamp `i` to `req->ir_replybuflen` before storing
   `ir_replylen` (fixes the hostile-BMC path at collection time);
2. `ipmi.c`: defense-in-depth — clamp `len` to `ir_replybuflen + 1` in the
   RECEIVE path (protects against any producer of an inflated length).

Build: `make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel`
(completed Fri Sep 25 08:48:48 UTC 2026, fix_build.log), rebooted into
`DragonFly 6.5-DEVELOPMENT #1: Fri Sep 25 08:44:00 UTC 2026`.

- Producer re-run on the patched source (fix_run.log):
  `req->ir_replylen = 1024` — **clamped**; transaction still succeeds.
- Sink re-run with identical arming: `msg.data_len = 1025`,
  `nonzero in [1024,1024): 0`, `marker fragments: 0` — **over-read gone**;
  the SEND/RECEIVE round trips of the readout program itself prove IPMI
  still functions on the patched kernel.

## kernel_refs

- sys/dev/misc/ipmi/ipmi_smic.c:320-336 (unbounded BMC byte count)
- sys/dev/misc/ipmi/ipmi_smic.c:341 (ir_replylen = i, unclamped)
- sys/dev/misc/ipmi/ipmi_smic.c:346-350 (driver detects short buffer, proceeds)
- sys/dev/misc/ipmi/ipmi.c:448-468 (trusting consumer + copyout OOB read)
- sys/dev/misc/ipmi/ipmi.c:401-403,534-546 (fixed 1024-byte ir_reply alloc)
- sys/dev/misc/ipmi/ipmi.c:831-832 (operator-group device node)
- sys/sys/ipmi.h:35 (IPMI_MAX_RX 1024)
- sys/dev/misc/ipmi/ipmi_kcs.c:411-432 (DF-4777 twin, KCS)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
