# DF-4978 — SSIF twin of the DF-4777/DF-4948 unclamped-reply-length heap over-read

**File:** `sys/dev/misc/ipmi/ipmi_ssif.c` · **Class:** CWE-125 OOB read →
kernel-heap info leak (CWE-909) · **Severity:** Medium (kernleak) ·
**Verdict: REPRODUCED (verbatim-source producer 16023/1024 + real
`/dev/ipmi0` sink 3/3 runs leaking ~7.2 KB kernel heap to an unpriv
operator user) and FIX VALIDATED on a rebuilt kernel (0 bytes past
1024, device functional).**

## The bug

`ssif_polled_request()` copies reply payload into `req->ir_reply[]`
clamped to `ir_replybuflen` (ipmi_ssif.c:241-242, 253, 291-293) but
accumulates the BMC-controlled total **unclamped** — `len += count - 1`
per READ_CONT block (ipmi_ssif.c:294), single-part `len = count - 3`
(ipmi_ssif.c:240) — and stores it verbatim:

```c
	req->ir_replylen = len;			/* ipmi_ssif.c:304 */
```

The shared consumer `ipmi_ioctl()` (ipmi.c:448-468) trusts it:
`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,
sys/sys/ipmi.h:35). Every per-block count in the PoC is exactly 32
(fully SMBus-legal), so this is neither DF-2025 (count > 32 stack
over-read of `ssif_buf`) nor DF-2026 (unterminated loop). Exact twin of
DF-4777 (ipmi_kcs.c:432) and DF-4948 (ipmi_smic.c:341) — the third
transport, unfixed.

Note: DragonFly's multi-part loop termination is **inverted** relative
to its own comment, the SSIF spec, and FreeBSD — `if (ssif_buf[0] !=
0xff) break;` (ipmi_ssif.c:297) — so numbered blocks terminate and
0xff-marked blocks *continue*. The hostile script therefore sends 515
0xff-marked full 32-byte continuation blocks and terminates with the
block number the driver expects.

## How to reproduce

Phase 1 (producer, any boot): `ssifshock.ko` compiles the **verbatim**
`/usr/src/sys/dev/misc/ipmi/ipmi_ssif.c` (copied by build.sh, never
edited) with mock transport headers whose only substitution is the
SMBus layer (mock `bus/smbus/smbconf.h` redirects `smbus_bread/bwrite`
to a scripted hostile-BMC emulator).

```sh
# host
dfbsd-qemu/vm.sh run_root 'sh /root/ssifshock/build.sh'      # BUILD-OK
dfbsd-qemu/vm.sh run_root 'kldload /root/ssifshock/ssifshock.ko'
# expected dmesg: "BUG DF-4978 REPRODUCED: unclamped ir_replylen=16023
#                  over 1024-byte buffer (14999 bytes past end)"
```

Phase 2 (sink, end-to-end; QEMU needs the IPMI sim for /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: sh /root/ssifshock/build.sh; kldload ipmi
#             pw groupmod operator -m maxx; kldload /root/bumpko/bumpko.ko
# guest maxx (background): /tmp/leak
# guest root: sysctl hw.bumpreplylen=16023   # the SSIF-produced end state
# expected: RECEIVE ok: data_len = 16024; ~7.2k nonzero kernel bytes past
#           offset 1024 (first at 0x042e); kernel pointers 0xfffff800...;
#           ~426 "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 → producer prints `ir_replylen = 1024`
(clamped) and the sink returns `msg.data_len = 1025, nonzero beyond
1024: 0` with the IPMI device fully functional.

## Artifacts

| file | what |
|---|---|
| `ssifshock.c`, `ipmivars.h`, `bus/smbus/smbconf.h`, `dev/smbus/smb/smb.h`, `smbus_if.h`, `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` | FULL producer build + decisive run (baseline) |
| `run.2.log`, `run.3.log` | sink stress runs (3/3 determinism) |
| `run.control.log` | control run (legal length 200 → 0 bytes beyond) |
| `fix.diff`, `fix_build.log`, `fix_run.log` | fix + full 37k-line validated kernel build + patched-kernel runs |
| `leak_sample.txt`, `env.txt` | disclosed bytes, guest environment |
| `VERDICT.md`, `manifest.json`, `verdict.json` | narrative + machine verdicts |
