# DF-2308 — VERDICT: not reproduced (false positive: the "short successful read" premise is unreachable in-tree)

## Bottom line

The I2CREAD copyout at `sys/bus/iicbus/iic.c:347` does use the requested
length (`s->count`) instead of the actual-read length (`count` set at
:344) — that part of the claim is accurate source reading. But the leak
requires `iicbus_read()` to return **success with a short fill**, and no
in-tree backend can do that:

1. **iicbb** (`sys/bus/iicbus/iicbb.c:409-421`, the only backend on the
   guest and the only one reachable without specific PCF8584 parallel-port
   hardware): the fill loop `while (len) { *buf++ = iicbb_readbyte(...);
   bytes++; len--; }` terminates only at `len == 0`, then `*read = bytes;
   return (0)`. `iicbb_readbyte` (iicbb.c:308-330) has no failure mode —
   it samples the (emulated or real) SDA line 8 times and ACKs/NACKs per
   `last`; a hung bus yields 0xFF *bytes*, not *fewer* bytes. Hence on
   every successful I2CREAD, `count == s->count` and the copyout moves
   exactly the bytes the driver wrote.
2. **pcf** (`sys/bus/iicbus/pcf/pcf.c:586-640`): its only short-read exit
   is `pcf_wait_byte()` failure → `pcf_stop(); goto error;` returning
   `error != 0`, and `iic.c:345-346` breaks to the epilogue **without any
   copyout** on error. (Also unreachable on this guest: PCF parallel-port
   hardware.)

## Live verification (DragonFly 6.5-DEVELOPMENT guest, production KLDs)

Rig: the DF-5539 family harness — real in-tree `iicbus.ko`/`iicbb.ko`/
`iic.ko` on a fake GPIO bit-bang parent `fakebb0` (nexus), emulated slave
ACKs any address byte, read TX pattern 0xc0,0xc1,…. Two disclosed setup
patches (the fixes for sibling findings DF-5559/DF-5560) were required to
make `/dev/iic0` attach and not NULL-deref on first ioctl; **the I2CREAD
logic under test is stock** (verified: only iicbus.c/iic.c attach-path
setup hunks applied; module disasm `stock-vs-setup.disasm.txt` shows the
DF-5560 store in iic_attach and nothing else).

The decisive experiment — same groom that proved chunk recycling for the
sibling I2CRDWR leak (DF-5558, marker returned 12/12 on this same rig):
`groom5558.ko` kmalloc+fill+kfree's an M_TEMP chunk of exactly the I2CREAD
size immediately before each ioctl, so `iic.c:343`'s kmalloc hands I2CREAD
the stale marker chunk itself. Result over 3 runs, sizes
64/256/512/1024/2048 groomed+ungroomed and 16384 ungroomed (51 attempts
per run):

* `rc=0`, `pattern_bad=0` — every byte equals the slave TX pattern
  `(0xc0 + i) & 0xff`
* `marker=no` (searched "ALEHEAP!" anywhere in the buffer — the marker
  tail that survives the allocator's 8-byte chunk header)
* `kptr-qwords=0` (no 0xffff-prefixed kernel pointers)

i.e. **zero bytes of the fresh chunk survive to userspace**; there is no
oracle to harvest, no partial-fill path to exploit.

Error paths (all leaving the 0xAA-prefilled user buffer 100% untouched):
* no-start I2CREAD → EINVAL (iiconf.c:254-259 wrapper gate, `!sc->started`)
* wrong-direction (started byte even) I2CREAD → EINVAL (`!(sc->started &
  LSB)` gate at iiconf.c:259) — this also covers the after-NACK case
  (a NACKed START leaves `started` cleared)

### Probe annotations (rig quirks, not kernel behavior)

* `E2 I2CSTART(0x6e)` returned rc=0 where the probe predicted ENOACK: the
  harness arms `nack_this` at the **falling edge** of the address-ACK
  clock (iicbb_harness.c fakebb_setscl byte-slot-done branch), one
  half-clock after `iicbb_ack`'s rising-edge sample (iicbb.c:271-285
  samples at the 9th clock high). A bare I2CSTART therefore ACKs; the
  NACK surfaces on the following data byte's ACK slot — exactly how
  DF-5539/DF-5558 observed IIC_ENOACK via block writes. Consequence: the
  after-NACK I2CREAD exercised the wrong-direction EINVAL gate instead of
  a post-ENOACK state; equally a no-copyout error path.
* `E3 I2CSTART(0x50)` then returned EIO-class failure because E2 left the
  bus started (iicbus_start wrapper refuses when already started,
  iiconf.c:169-180).

### Incidental observations (notes only, not new findings)

* During rig bring-up, exercising `/dev/iic0` with a **stale unpatched**
  iic.ko re-panicked the kernel exactly as DF-5560 documents
  (`device_get_parent(NULL)` at iic.c:289) — capture kept in
  `panic-setup-df5560-incidental.txt`.
* DragonFly's `patch(1)` assumes `-R` non-interactively on already-applied
  diffs, which silently reverted the setup patches in one intermediate
  round — run.sh now uses source-marker guards.

## Conclusion

`status: not_reproduced`, `reproduced: 0`, `impact: none`,
`confidence: certain`. The finding is a true statement about a latent API
misuse but an incorrect statement about exploitable behavior on this tree.
Recommended hardening (not a security fix): make the copyout honor the
backend's actual count — `error = copyout(buf, s->buf, count);` — so a
future backend honoring the short-read contract cannot reintroduce the
disclosure. No fix.diff authored (pure false positive per contract).

## 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.

(No fix.diff for this item: documented recommendation only - see Fix above.)
