β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5522

SMBus block count byte is omitted in both directions: every Block Write through iicsmb is malformed on the wire and every Block Read returns one-byte-shifted data

Summary

bwrite writes [cmd][data...] skipping the count byte; bread transfers *count raw bytes without consuming the device's count - wire-proven on the production chain (stock modules): 'WRITE len=1: 07' then 'WRITE len=5: de ad...' with no 0x05, and spec-compliant replies land shifted (data[0] = the count). Every other in-tree smbus driver does both. ipmi_ssif (the only kernel block consumer) cannot work over this bridge. Count-in-write + read-count-first fix validated (wire [07][05][data], rcount=8 unshifted, deterministic 6x).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5522 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
df5521_rig.c β€” 7.9 KB view raw
df5521_poc.c β€” 6.8 KB view raw
build.sh β€” 83 B view raw
load_chain.sh β€” 294 B view raw
build.log β€” 5.4 KB view raw
env.txt β€” 980 B view raw
run.baseline.log β€” 1.3 KB view raw
run.fixed.log β€” 1.3 KB view raw
fix.diff β€” 1.7 KB view raw
manifest.json β€” 692 B view raw
verdict.json β€” 3.8 KB view raw

DF-5522 β€” iicsmb block read/write omit the SMBus count byte in both directions: every Block Write is malformed on the wire and every Block Read returns one-byte-shifted data

Files

Same rig/PoC as DF-5521 (shared evidence pack artifacts copied here for self-containment):

df5521_rig.c software iicbus controller; the emulated slave is SPEC-COMPLIANT: within one transaction its byte stream is [count][data...] and it continues across reads df5521_poc.c proto mode drives SMB_BWRITE + SMB_BREAD on /dev/smb0 run.baseline.log stock-module wire capture (no count byte; shifted data) run.fixed.log patched-module wire capture (count byte present/consumed) fix.diff the count-byte fix (also carries the [1,32] validation of DF-5523, which the rewritten bread needs anyway)

Reproduce

  1. build + load chain (see build.sh / load_chain.sh; same as DF-5521)
  2. /root/df5521_poc proto
  3. compare dmesg | grep df5521-wire with the two logs here.

Baseline: WRITE len=1 bytes: 07 is followed directly by WRITE len=5 bytes: de ad be ef 99 β€” no count byte. SMBus 2.0 Block Write is [addr/W][cmd][count][data...]; ichsmb/intpm/amdsmb all transmit the count (ichsmb: bus_write_1(sc->io_res, ICH_D0, count)). On read the bridge never consumes the slave's count byte: it transfers *count raw bytes, so a compliant slave's count byte lands in the caller's data[0] and every reply is mis-parsed one byte to the right.

VERDICT.md
↓ download raw

DF-5522 VERDICT β€” REPRODUCED (wire-protocol conformance bug, both block directions)

The bug

iicsmb_bwrite() (sys/bus/iicbus/iicsmb.c:463-482) writes [cmd] then count raw data bytes β€” it never transmits the SMBus byte-count that the Block Write protocol places between the command and the data (SMBus spec 2.0 Β§"Block Write": Write, Command, Byte Count, Data...). iicsmb_bread() (iicsmb.c:485-507) symmetrically never consumes the count byte the slave sends after the repeated start β€” it transfers *count raw bytes and reports the raw transfer length.

Every other in-tree smbus controller driver does both: ichsmb_bwrite programs ICH_D0 = count and the ICH hardware transmits it (sys/bus/smbus/ichsmb/ichsmb.c, ichsmb_bwrite), ichsmb_bread returns sc->block_count read off the wire; intpm/amdsmb are equivalent. ig4's write-side variant of this exact mistake was filed as DF-5385 (Low).

Consequences for real consumers: /dev/smb block ioctls through an iicsmb-backed bus put the device's count byte into the caller's data (stream shifted one byte, length wrong); ipmi_ssif (the only in-tree kernel block-transfer consumer) mis-parses every SSIF reply (ssif_buf[0] is the BMC's count byte, not the multi-part indicator it tests) and its Block Writes are NAKed by any spec-compliant BMC β€” IPMI-over-SSIF through this bridge cannot work.

Verification

Rig (see DF-5521 pack): software controller whose slave is deliberately SPEC-COMPLIANT (sends [count][data...], continues across reads). Stock modules β€” run.baseline.log:

WRITE len=1 bytes: 07 WRITE len=5 bytes: de ad be ef 99 <- no count byte between them

Fixed modules β€” run.fixed.log:

WRITE len=1 bytes: 07 WRITE len=1 bytes: 05 <- count byte on the wire WRITE len=5 bytes: de ad be ef 99 ... READ req_len=1 (consumes the device's count byte = 08) READ req_len=8 (reads exactly the device-declared block) => rcount=8, rbuf=a0 a1 a2 a3 a4 a5 a6 a7 (unshifted, deterministic)

No memory-safety impact by itself (lengths stay bounded by the callers); correctness/data-integrity class β€” severity Low, consistent with DF-5385.

Fix

fix.diff: transmit the count byte in bwrite; in bread read the device count first, clamp it to the caller's *count and to the 32-byte SMBus block limit, then transfer exactly that many bytes and report it. Note: the new bwrite error path (goto error on count-byte write failure) inherits DF-1095's known started-state issue until that fix lands; the rewritten bread STOPs on all paths as before.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Stock: wire shows no count byte on Block Write and no count consumption on Block Read (data one-byte-shifted). Patched (fix.diff): wire shows [cmd][count][data]; reads consume the count (req_len=1) then honor the device block length (req_len=8, rcount=8, rbuf=a0..a7 unshifted); behavior deterministic across 6 consecutive ioctls. Validated as part of the combined fixed-module build shared with DF-5521/5523/5525.

run.fixed.log vs run.baseline.log; fix.diff
↓ fix.diffper-fix-DF-5522

Confirmed kernel references

Detail

Evidence (decisive lines)

run.baseline.log (stock: 'WRITE len=1 bytes: 07' followed directly by 'WRITE len=5 bytes: de ad be ef 99' -- no count byte; reads shift the slave count into data); run.fixed.log (patched: 'WRITE len=1 bytes: 05' count byte present; 'READ req_len=1' consumes count then 'READ req_len=8' -- rcount=8, rbuf=a0..a7 unshifted); fix.diff

PoC changes

shared rig with DF-5521; the rig's slave was made spec-compliant on purpose (byte stream [count][data...] continuing across reads within a transaction) so the bridge's wire behavior is directly observable

Verified recommended fix

bwrite: transmit the count byte between cmd and data; bread: read the device's count byte first, clamp to min(device count, count, 32), transfer exactly that many bytes and report it as count. See fix.diff (validated live).

Verdict

REPRODUCED (protocol conformance, no memory-safety impact): iicsmb_bwrite (iicsmb.c:463-482) never transmits the SMBus block byte-count after the command byte and iicsmb_bread (iicsmb.c:485-507) never consumes the slave's count byte after the repeated start -- it transfers *count raw bytes. Live wire capture with the production chain (isa->dfa_iic0->iicbus0->iicsmb0->smbus0->smb0) against a deliberately spec-compliant emulated slave: stock modules put 'de ad be ef 99' immediately after '07' with no count byte, and block reads returned the slave's count byte shifted into data[0]; with fix.diff the wire is [07][05][data] and reads consume the device count (req_len=1 then req_len=8, rcount=8, unshifted data a0..a7, deterministic). Every other in-tree smbus driver does both halves (ichsmb programs ICH_D0=count; intpm/amdsmb equivalent) -- the bridge is the odd one out, and ipmi_ssif (the only in-tree kernel block consumer) cannot work over it (reply parsing shifts one byte; Block Writes malformed). Severity Low per family precedent DF-5385 (ig4 write-side variant). FIX VALIDATED live (run.fixed.log).