# DF-1076 — Verification verdict

## Verdict: REPRODUCED (at source level + algorithmic harness) — primitive confirmed; runtime-unreachable on this guest (hardware-gated); fix validated as compile+boot-clean and algorithmically correct.

**Severity:** High (kernel heap OOB write with attacker-controlled bytes & length).
**Impact:** `panic` / kernel-heap corruption (DoS) on a system with ichsmb hardware +
a malicious SMBus/I2C slave. **Not** `uid0` — see "Hard blocker" below.

---

## Mechanism (the bug is real, confirmed line-by-line)

In the block-read branch of `ichsmb_device_intr()`:

- `sys/bus/smbus/ichsmb/ichsmb.c:574-577` — on the first `BYTE_DONE_STS`
  interrupt, the slave-supplied count byte is read from `ICH_D0` directly into
  `sc->block_count` with **no clamp**:
  ```c
  if (sc->block_index == 0) {
      sc->block_count = bus_read_1(sc->io_res, ICH_D0);   /* !!! untrusted */
  }
  ```
- `sys/bus/smbus/ichsmb/ichsmb.c:580-585` — the loop bound is `sc->block_count`,
  and the body does an indexed store into the fixed-size array:
  ```c
  if (sc->block_index < sc->block_count) {
      sc->block_data[sc->block_index++] = bus_read_1(sc->io_res, ICH_BLOCK_DB);
  }
  ```
- `sys/bus/smbus/ichsmb/ichsmb_var.h:64-65` — `block_data` is `u_char[32]` and
  the **immediately-following** field is `struct lock mutex`, which the ISR
  holds (`lockmgr LK_EXCLUSIVE` at `ichsmb.c:505`) at the time of corruption.
- `sys/bus/smbus/ichsmb/ichsmb.c:424-427` — caller `ichsmb_bread()` validates
  only the *user-supplied* count and then **zeros** `sc->block_count`, so the
  user-side bound has no effect on the slave-supplied value the ISR later reads.

`ICH_D0` is 8 bits, so a malicious peripheral can return `0..255`. With
`count = 255`, the indexed store writes `block_data[0..254]` — i.e. up to
**223 bytes past the end** of the 32-byte array, all bytes fully
attacker-controlled (they are the data bytes the slave streams). The first 64
of those land in `struct lock mutex`, panicking on the next `lockmgr` op.

The driver also covers I2C pass-through (`ICH_HOSTC_I2C_EN`,
`sys/bus/smbus/ichsmb/ichsmb_reg.h:54`), where the SMBus 32-byte spec limit
does not apply and slaves legitimately send more — so the bug is reachable
even with compliant I2C peripherals.

## Algorithmic harness (demonstrates the primitive without hardware)

The audit QEMU guest has **no ichsmb PCI device** (PCI 0:1:3 is the PIIX4
**ACPI** function, class `0x068000`, not SMBus class `0x0c05`), so the bug
cannot be triggered dynamically here. To prove the primitive and validate
the fix without malicious hardware, `harness.c` reproduces the *exact
algorithmic logic* of the ISR block-read branch in userspace:

- A `struct ichsmb_softc` is laid out byte-for-byte per
  `ichsmb_var.h:47-66`, with a 64-byte mock `struct lock mutex` and a
  192-byte trailing canary immediately after `block_data[32]`.
- `bus_read_1(ICH_D0)` returns 255 (slave's "I'm sending 255 bytes" lie);
  `bus_read_1(ICH_BLOCK_DB)` returns `0xAA` (attacker's payload bytes).
- The harness drives the same loop the ISR drives, once with `CLAMP_FIX`
  undefined (unpatched model) and once with it defined (patched model).

Run as the unprivileged user (`ssh dfbsd-maxx`):

```
=== UNPATCHED ===
post-ISR block_count = 255
post-ISR block_index = 255
payload-bytes (0xAA) found in block_data[32]  : 32 / 32
payload-bytes (0xAA) found in mutex.payload[64]: 64 / 64
payload-bytes (0xAA) found in canary[192]      : 159 / 192
VERDICT: OOB WRITE CONFIRMED — 223 payload-bytes past block_data[31] (64 in mutex, 159 in canary).

=== PATCHED (clamp at ichsmb.c:586) ===
post-ISR block_count = 32
post-ISR block_index = 32
payload-bytes (0xAA) found in block_data[32]  : 32 / 32
payload-bytes (0xAA) found in mutex.payload[64]: 0 / 64
payload-bytes (0xAA) found in canary[192]      : 0 / 192
VERDICT: NO OVERFLOW — writes stayed inside block_data[32].
```

The 223-byte OOB write (32 in `block_data` is in-bounds; 64 in mutex + 159 in
canary = 223 past the end) matches the finding's predicted magnitude exactly.

The original `trigger.c` was also built and run; on this guest it exits with
`open /dev/smb0: No such file or directory` — confirming the live kernel path
is unreachable here (no ichsmb PCI device → no `/dev/smb0`).

## Why not `uid0` — hard blocker (Phase 6)

This is a **device-only reachability** hard blocker:

- On this guest: no ichsmb PCI device at all (`pciconf -lv` has no
  `class=0x0c05` entry), `ichsmb` is not in the running kernel
  (`nm /boot/kernel/kernel | grep -c ichsmb = 0`), and `/dev/smb0` does not
  exist. `kldload ichsmb.ko` as root fails silently (no device to attach).
- Even on a real ichsmb system, the live trigger requires either:
  (a) a malicious SMBus/I2C peripheral (hardware-adjacent threat — supply-chain
  DIMM, malicious laptop battery, hotplug USB-C dock, PCI-passthrough of the
  SMBus controller), or
  (b) `ioctl(/dev/smb0, SMB_BREAD, ...)` as **root** — `/dev/smb0` is `0600`
  root:wheel per `sys/dev/smbus/smb/smb.c:135-141`.
- There is **no unprivileged syscall surface** to ichsmb. The realistic
  worst-case impact is kernel heap corruption → panic (DoS) on a system
  with malicious SMBus hardware. Privilege escalation would require the
  attacker to *already* control a malicious peripheral; that is a
  hardware-adjacent threat, not a clean unpriv→root chain.

This makes the honest impact **panic/corruption (DoS)**, not `uid0`. The
finding's `CVSS:3.1/AV:L/AC:L/PR:L/...` is appropriately scoped to
`AV:L` (local) with `PR:L` (low privs) — but the *live trigger* requires
malicious hardware, which is more restrictive than a pure local-software
privesc. The audit owner may want to revisit severity (the corruption
primitive is real and high-quality, but the trigger precondition is
hardware-adjacent).

## PoC changes

- **Added `harness.c`** — userspace C harness that faithfully reproduces
  the ISR block-read logic and the softc layout, demonstrating both the
  primitive (223-byte OOB write with attacker bytes) and the closure
  (clamp prevents any byte past `block_data[31]`). Necessary because the
  live kernel path is hardware-gated on this guest.
- **Added `build.sh` / `run.sh`** — reproducible build & run.
- **Original `trigger.c`** — kept as-is; it builds cleanly
  (`smb.h` is at `/usr/src/sys/dev/smbus/smb/smb.h`) but is a no-op here
  because `/dev/smb0` does not exist.
- **Added `fix.diff`** — standalone git-apply-able unified diff that
  clamps `block_count` to `sizeof(sc->block_data)` after the slave-supplied
  count read. Matches the finding's recommended fix; tightens it to a
  single clamp + adds a documenting comment.
- **Added `env.txt`, `run.log`, `fix_build.log`, `trigger_build_attempt.log`**.

## Fix validation (Phase 8)

`fix_status: not_testable` — but validated as far as the guest allows:

1. **Diff applies cleanly**: `patch -p1 sys/bus/smbus/ichsmb/ichsmb.c fix.diff`
   → `Hunk #1 succeeded at 570. PATCH_EXIT=0`. Also `git apply --check`
   passes on the host.
2. **Kernel builds**: `make -j6 nativekernel KERNCONF=X86_64_GENERIC` →
   `=== NK_DONE rc=0 ===`. No errors or warnings on `ichsmb.c`.
3. **Kernel boots**: installed
   `/usr/obj/usr/src/sys/X86_64_GENERIC/kernel.stripped` →
   `/boot/kernel/kernel` and rebooted;
   `sysctl kern.version` → `DragonFly 6.5-DEVELOPMENT #1: Wed Jul 15 00:01:56 UTC 2026`.
4. **Patched source verified**: `grep -A3 'block_count = bus_read_1'
   /usr/src/sys/bus/smbus/ichsmb/ichsmb.c` shows the clamp at the right line.
5. **Algorithmic closure verified**: the harness's "PATCHED" variant
   produces zero OOB bytes — the clamp turns the 223-byte OOB into a
   clean 32-byte in-bounds write.

A dynamic before/after on a *running* kernel is not possible on this guest
because there is no ichsmb PCI device and no `/dev/smb0`. The closure is
proven at the algorithmic level (the only place the bug path can be
exercised here), and the patched kernel compiles and boots cleanly. On a
guest with a real (or emulated) ichsmb controller + malicious slave, the
same harness logic would manifest as: panic on `lockmgr` (unpatched) vs
clean transaction completion (patched).

## Recommended fix

Apply `fix.diff` (matches the finding's `## Recommended fix` proposal —
clamp `block_count` to `sizeof(sc->block_data)` immediately after the
`bus_read_1(sc->io_res, ICH_D0)` read at `ichsmb.c:585`). The
finding's alternative one-liner (also bounding the loop test at
`ichsmb.c:580`) is equivalent but slightly less preferred because it
leaves `block_count` itself oversized and may interact with the
`LAST_BYTE` logic at `ichsmb.c:589-596`; the load-time clamp keeps all
downstream math correct.

## Kernel references (confirmed during verification)

- `sys/bus/smbus/ichsmb/ichsmb.c:574-590` — count read (575), clamp site (586-589, post-fix), loop bound (580), indexed write (583)
- `sys/bus/smbus/ichsmb/ichsmb_var.h:64-65` — `block_data[32]` adjacent to `struct lock mutex`
- `sys/bus/smbus/ichsmb/ichsmb.c:424-427` — caller's user-side bound does not constrain slave count
- `sys/bus/smbus/ichsmb/ichsmb.c:505,615` — `lockmgr LK_EXCLUSIVE`/`LK_RELEASE` on the corrupted mutex (panic site)
- `sys/dev/smbus/smb/smb.c:135-141` — `/dev/smb0` mode `0600` root:wheel
