# DF-2024 — size_t underflow in multi-part SSIF write loop

## Verdict
**CONFIRMED (source-trace); NOT REPRODUCED AT RUNTIME — HW-GATED.**
Status: `inconclusive`, reproduced=0. The bug is real and confirmed by a line-by-line
source trace, but it cannot be exercised on this guest because the IPMI SSIF driver
(`ipmi.ko`) only attaches to a real BMC sitting on an SMBus controller (ichsmb/amdsmb),
and this QEMU guest has no BMC and no SMBus block-transfer controller. `ipmi.ko` is not
loaded and the vulnerable function `ssif_polled_request()` is unreachable at runtime.

## Mechanism (confirmed in `sys/dev/misc/ipmi/ipmi_ssif.c`)

The multi-part **write** path fires when `req->ir_requestlen > 30` (single-packet path
at line 95 handles `<= 30`). In the multi-part branch:

- `ipmi_ssif.c:77` — `size_t len;`  (size_t = unsigned 64-bit, confirmed in
  `sys/dev/misc/ipmi/ipmivars.h:46`: `size_t ir_requestlen;`)
- `ipmi_ssif.c:126` — `len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);`  i.e. `requestlen - 30`.
  Safe here because the branch is only taken for `requestlen > 30`, so `len >= 1`.
- `ipmi_ssif.c:128` — `while (len > 0) {`
- `ipmi_ssif.c:135` — `smbus_bwrite(..., min(len, SMBUS_DATA_SIZE), cp);` correctly caps the
  **byte count written to the bus** at ≤ 32.
- `ipmi_ssif.c:143-144` — **THE BUG:** `cp += SMBUS_DATA_SIZE; len -= SMBUS_DATA_SIZE;`

The loop unconditionally subtracts `SMBUS_DATA_SIZE` (32) from `len` even when the last
`bwrite` only consumed `min(len,32) < 32` bytes. Because `len` is `size_t` (unsigned), a
final partial block (e.g. `requestlen == 33` ⇒ `len` goes 3 → `3-32` = `0xFFFFFFFF...E3`)
**underflows to ~2^64**. The `while (len > 0)` condition then stays true forever and each
subsequent iteration calls `smbus_bwrite(..., 32, cp)` reading 32 bytes past the advancing
`cp` — i.e. past the end of the `req->ir_request` kmalloc buffer — feeding kernel heap
data to the SMBus controller. The loop runs until an unmapped page is hit (kernel page
fault / panic ⇒ DoS) or the BMC errors out.

Concrete example: `requestlen = 33` ⇒ `len = 3`; after one iteration `len` underflows;
the driver then reads `req->ir_request[62..]`, `req->ir_request[94..]`, ... all out of
bounds. Effect: **unbounded kernel heap OOB read + guaranteed DoS** (page fault once `cp`
walks off the slab/page). Class: CWE-191 (integer underflow) → CWE-125 (OOB read).

The `min(len, SMBUS_DATA_SIZE)` on line 135 is a red herring — it only caps the *byte count
handed to the controller*, not the *pointer advancement*; the underflow is in the loop
bookkeeping, not the bwrite argument.

## Why it is not reproduced on this guest (HW-gate)
- `kldstat -v` shows no `ipmi` module loaded; `/dev/ipmi*` absent.
- `pciconf -l` shows no IPMI/BMC/SMBus-block-transfer PCI device (the guest's only SMBus
  hint is the `smbus` bus built into GENERIC, with no `ichsmb`/`amdsmb` child controller
  and no BMC slave).
- `ssif_polled_request()` is only callable through the IPMI request queue once `ipmi.ko`
  has attached an SSIF interface to a real BMC. No BMC ⇒ the function is dead code at
  runtime on this guest. There is no syscall/ioctl surface that reaches it from userspace
  without the hardware.

This is a legitimate "valid hard blocker — unreachable at runtime on this guest AND no
userspace harness can exercise it" case (the path requires physical/firmware-level BMC
control, not a local unprivileged syscall). The primitive is a kernel heap OOB read; on
real IPMI-equipped hardware a malicious/compromised BMC (or a BMC behind a host the
attacker controls) is the threat actor. No escalation chain was developed because the
trigger is not reachable from the unprivileged-user syscall surface on this guest.

## PoC changes
None. The original README points at the parent finding; no runtime PoC is possible on a
guest without a BMC. Verification was source-only (line-by-line trace) + fix build.

## Fix (`fix.diff`)
Root-cause fix in `sys/dev/misc/ipmi/ipmi_ssif.c`: rewrite the multi-part WRITE_CONT loop
to (a) only send full 32-byte blocks while `len >= SMBUS_DATA_SIZE`, then (b) send any
remaining 1..31 bytes as the terminating WRITE_CONT, or a single `0x00` byte when the
request length is an exact multiple. `cp` and `len` are now advanced by the amount
actually written, so the unsigned underflow is impossible. Supersedes the finding
markdown proposal (which correctly identified the underflow); this is a complete,
terminator-correct implementation.

## Fix validation (Phase 8)
Combined kernel build (`make -j6 nativekernel KERNCONF=X86_64_GENERIC`) with all four
fixes (DF-2024/2025/2026/2027) applied: **rc=0, 0 warnings, 0 errors under -Werror**.
`ipmi_ssif.c` was recompiled (fresh `ipmi_ssif.o`, `ipmi.ko` relinked). Because the bug
is HW-gated, the PoC cannot be run to demonstrate before/after behavior, so
`fix_status = not_testable` (diff applies + compiles cleanly + traced to close the path).
See `fix_build.log`.
