# DF-4948 — SMIC twin of the DF-4777 unclamped-reply-length heap over-read

**File:** `sys/dev/misc/ipmi/ipmi_smic.c` · **Class:** CWE-125 OOB read →
kernel-heap info leak (CWE-909) · **Severity:** Medium (kernleak) ·
**Verdict: REPRODUCED (3/3 sink runs + live producer proof) and FIX
VALIDATED on a rebuilt kernel.**

## The bug

`smic_polled_request()` counts **every** reply data byte the BMC streams
(ipmi_smic.c:320-336); storage into `req->ir_reply[]` is clamped
(ipmi_smic.c:325) but the stored count is not:

```c
i = 0;
while (state == 1) {                       /* until BMC says RD_END */
        state = smic_read_byte(sc, &data);
        ...
        if (i < req->ir_replybuflen)
                req->ir_reply[i] = data;   /* clamped store */
        i++;                               /* unclamped count */
}
if (!smic_read_end(sc))
        return (0);
req->ir_replylen = i;                      /* ipmi_smic.c:341 — BMC-controlled */
```

The shared consumer `ipmi_ioctl()` (ipmi.c:448-468) trusts `ir_replylen`:
`len = ir_replylen + 1; len = min(user data_len, len); copyout(ir_reply,
..., len - 1)` — over a buffer that is always exactly `IPMI_MAX_RX = 1024`
bytes for ioctl requests (ipmi.c:401-403, 534-546). A hostile BMC that
streams > 1024 data bytes before RD_END turns every
`IPMICTL_RECEIVE_MSG(_TRUNC)` into a kernel-heap disclosure of up to 64 KB
to any process in group `operator` (`/dev/ipmi0` 0660 root:operator,
ipmi.c:831-832). Exact twin of DF-4777 (ipmi_kcs.c:432), which called out
this line; DF-4777's fix was validation-only and never applied to `sys/`.

## How to reproduce

Phase 1 (producer, any boot): `smicshock.ko` compiles the **verbatim**
`/usr/src/sys/dev/misc/ipmi/ipmi_smic.c` (copied by build.sh, never edited)
with `-DLOCAL_MODULE` against mock `<ipmivars.h>` whose only substitution
is INB/OUTB → a scripted hostile-BMC SMIC register emulator (protocol-legal
transactions; 16000 reply data bytes; last byte staged RD_END).

```sh
# host
dfbsd-qemu/vm.sh run_root 'sh /root/smicshock/build.sh'     # BUILD-OK
dfbsd-qemu/vm.sh run_root 'kldload /root/smicshock/smicshock.ko; kldunload smicshock'
# expected: req->ir_replylen = 16000, ir_replybuflen = 1024, BUG REPRODUCED
```

Phase 2 (sink, end-to-end; QEMU needs the IPMI sim for a real /dev/ipmi0):

```sh
dfbsd-qemu/vm.sh down
QEMU_EXTRA="-device ipmi-bmc-sim,id=bmc0 -device isa-ipmi-kcs,bmc=bmc0" dfbsd-qemu/vm.sh up
# guest root: kldload ipmi && pw groupmod operator -m maxx
#             kldload /root/bumpko/bumpko.ko ; cc -O2 -o /tmp/leak /root/smicshock/leak.c
# guest maxx (background): /tmp/leak
# guest root: sysctl hw.bumpreplylen=16000   # arm the end state the SMIC transport produced
# expected: RECEIVE ok: data_len = 16001; ~6.6-7.2k nonzero kernel bytes past
#           offset 1024 (first at 0x042e); kernel pointers 0xfffff8008dcfxxxx;
#           hundreds of "DF4777MARK-" fragments from live neighbor chunks.
```

Phase 3 (fix validation): `patch -p1 < fix.diff` in the guest `/usr/src`,
`make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel`,
reboot, repeat phases 1+2 → `ir_replylen = 1024` (clamped) and
`msg.data_len = 1025, nonzero beyond 1024: 0`.

## Artifacts

| file | what |
|---|---|
| `smicshock.c`, `mock/`, `Makefile` | producer harness (verbatim code-under-test + scripted BMC) |
| `bumpko.c`, `leak.c` | sink arming + unprivileged readout (reused verbatim from DF-4777's pack) |
| `build.sh`, `run.sh` | exact build/run commands |
| `build.log`, `run.log`, `run.2.log`, `run.3.log` | full logs, baseline |
| `fix.diff`, `fix_build.log`, `fix_run.log` | fix + validated kernel build/run |
| `leak_sample.txt`, `env.txt`, `dmesg.txt` | disclosed bytes, environment |
| `VERDICT.md`, `manifest.json`, `verdict.json` | narrative + machine verdicts |
