Slave-controlled block-read count drives OOB MMIO reads and inflates returned byte count (amdsmb_bread)
Summary
amdsmb_bread at amdsmb.c:533 reads len directly from SMB_BCNT register (slave-supplied, 0..255). Line 534 for(i=0;i<len;i++) amdsmb_ec_read(sc, SMB_DATA+i, &data) does OOB MMIO reads when i>=32 (SMB_DATA is 32-byte register file at offsets 0x04..0x23, amdsmb.c:75). Line 536 if(i<*count) buf[i]=data guards the buf WRITE but NOT the MMIO read. Line 539 *count=len returns unclamped slave count to caller, breaking SMBus block-size invariant (<=32 per smb.h:57). Contrast ichsmb_bread which writes *count=sc->block_count validated against array bound in ISR. Impact chain A (no user action): malicious BMC via IPMI SSIF (ipmi_ssif.c:291-293) bcopy min(replybuflen-len,count-1) from ssif_buf[1] in 32-byte array over-reads into ipmi softc; leaked heap exposed via IPMI reply. Impact chain B (root/wheel only via /dev/smb): smb.c:217 passes uninitialized u_char bcount; if garbage in [1,32] call proceeds, amdsmb returns *count=len up to 255, smb.c copyout copies up to 255 bytes of 32-byte buf to userspace = ~223 bytes uninitialized kernel stack leak. Impact C (DoS): len=255 triggers 224 OOB EC reads each DELAYing up to 500us, extending AMDSMB_LOCK hold ~340ms. Same class as DF-1076 (ichsmb). Fix: clamp both loop and *count to caller buffer size.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1091 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df1091_harness.c | trigger-source | userspace harness mirroring amdsmb_bread loop with 256-byte EC model | 3.4 KB | view raw |
| verify.sh | trigger-source | 9 static source-tree checks | 2.2 KB | view raw |
| verify.log | run-log | verify.sh output (all 9 pass) | 592 B | view raw |
| run.log | run-log | harness output: 223 OOB + count=255 unpatched; 0 OOB + count=32 patched | 616 B | view raw |
| fix.diff | suggested-fix | clamp len to 32 after SMB_BCNT read in amdsmb_bread | 1.0 KB | view raw |
| build.sh | build-script | build both harness variants | 320 B | view raw |
| run.sh | run-script | verify.sh + both harness variants | 289 B | view raw |
| env.txt | environment | uname, cc, securelevel, HW presence | 526 B | view raw |
| README.md | readme | how to reproduce + bug shape + impact | 3.1 KB | β raw |
| VERDICT.md | verdict | full narrative: mechanism, harness, fix | 5.3 KB | β raw |
| fix_build.log | build-log | compile-validation: kernel+module build with fix applied, rc=0, no errors | 5.7 MB | β download |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1091 β amdsmb_bread slave-controlled block-read count drives OOB MMIO
Build
cc -O0 -o df1091_harness df1091_harness.c cc -O0 -DFIX -o df1091_harness_fix df1091_harness.c
Run
./df1091_harness # 223 OOB MMIO reads + unclamped *count=255 ./df1091_harness_fix # 0 OOB; *count clamped to 32 sh verify.sh # 9 static source-tree checks
Expected (bug present)
verify.shreportsPASS=9 FAIL=0(the missing clamp is pinned, the siblingamdsmb_bwritevalidation is pinned as a control).df1091_harnessreports223 OOB MMIO reads of adjacent EC registers(matching the 255 - 32 overflow in the finding) andRETURNED COUNT > 32: caller (smb.c) will copyout up to 255 bytes.df1091_harness_fixreports0 OOBandreturned count clamped to 32.
Bug shape
amdsmb_bread at sys/bus/smbus/amdsmb/amdsmb.c:517-547:
if ((error = amdsmb_wait(sc)) == SMB_ENOERR) {
amdsmb_ec_read(sc, SMB_BCNT, &len); /* :533 slave-controlled 0..255 */
for (i = 0; i < len; i++) { /* :534 bound is len, not 32 */
amdsmb_ec_read(sc, SMB_DATA + i, &data); /* :535 OOB MMIO when i >= 32 */
if (i < *count)
buf[i] = data; /* :537 buf write is bounded */
}
*count = len; /* :539 unclamped return */
}
SMB_DATAis a 32-byte register file at offsets0x04..0x23(:75). Whenlen > 32, the MMIO read atSMB_DATA + ioverruns intoSMB_BCNT/SMB_ALRM_*/follow-on EC space β 223 reads when len=255.*count = lenreturns the slave-controlled length (up to 255) to the caller. Insys/dev/smbus/smb/smb.c:318-330,SMB_BREADthen doess->rcount = min(s->rcount, bcount)andcopyout(buf, s->rbuf, s->rcount), copying up to 255 bytes from achar buf[SMB_MAXBLOCKSIZE=1024]whose bytes 32..254 were never written byamdsmb_breadβ i.e. an uninitialized-kernel-stack leak to userspace (per call, when invoked via/dev/smb).- The cross-driver sibling
amdsmb_bwrite(:491-514) does validatecount < 1 || count > 32(:497) β but takescountby value. The DF-1076 fix forichsmb_breadclampsblock_countin the ISR.amdsmb_breadlacks both validations on the slave-supplied length.
Impact / preconditions
Three impact chains, in order of practicality:
- OOB MMIO read (no userland action required) β a malicious BMC
reachable via IPMI SSIF can drive this through
ipmi_ssif.c. Each out-of-bounds EC register read has potential side effects on the ACPI EC and may lengthen theAMDSMB_LOCKhold (DoS). - Uninitialized kernel stack leak via
/dev/smbβ root/wheel-only on hosts with an exposedsmbdevice; ~223 bytes of stack leak per call. - Lock-hold DoS β
len=255extends theAMDSMB_LOCKspin hold by ~340ms (224 EC reads Γ up to 500Β΅sDELAY).
The audit QEMU guest has no AMD SMBus PCI device (pciconf -l shows
Intel PIIX4 PM only, class 0x068000), so the path is not exercised
dynamically here; the bug is confirmed by source trace + harness.
DF-1091 β amdsmb_bread slave-controlled block-read count drives OOB MMIO
Verdict
NOT REPRODUCED at runtime (hardware-gated) β STATIC VERIFICATION + HARNESS CONFIRMED.
The bug exists verbatim in sys/bus/smbus/amdsmb/amdsmb.c:517-547. The
SMBus block-read function reads the slave-supplied byte count
(SMB_BCNT, 0..255) into len and uses it as the bound for a
for (i = 0; i < len; i++) amdsmb_ec_read(sc, SMB_DATA + i, &data) loop.
SMB_DATA is a 32-byte register file (:75, offsets 0x04..0x23); any
len > 32 produces OOB reads of SMB_BCNT / SMB_ALRM_* / follow-on EC
register space β 223 reads when len = 255. The function also writes
*count = len (:539), passing the unclamped slave-supplied count back
to the caller, breaking the SMBus 32-byte block invariant. The
if (i < *count) buf[i] = data; write at :537 is bounded by the
caller's original *count (the entry-check at :524 rejects anything
outside [1,32]), so the buf write does not overflow; the MMIO
read and the returned count are both unclamped.
The audit QEMU guest has no AMD SMBus PCI device (pciconf -l shows
Intel PIIX4 PM, class 0x068000, not the AMD-8111 SMBus controller at
1022:746a), so amdsmb_bread is never invoked at runtime here. The
trigger requires AMD-8111 SMBus hardware with a malicious I2C peripheral
on the bus, or an IPMI BMC reachable via SSIF. Same hardware-gating class
as DF-1076 (ichsmb) β the sibling finding for which the same kind of
clamp was already merged.
The df1091_harness userspace C program mirrors the algorithm against a
256-byte EC register model. With SMB_BCNT = 255:
- Unpatched: 223 OOB MMIO reads past SMB_DATA[31], returned
*count = 255 (would cause smb.c to copyout 223 bytes of
uninitialized stack).
- Patched: 0 OOB, returned *count clamped to 32.
Mechanism (confirmed by source trace)
amdsmb_bread (amdsmb.c:517-547) is the SMBus block-read method of the
AMD-8111 SMBus host driver. After waiting for the previous transaction
to complete, it reads the slave-supplied byte count from SMB_BCNT and
loops:
/* amdsmb.c:532-540 β the bug */
if ((error = amdsmb_wait(sc)) == SMB_ENOERR) {
amdsmb_ec_read(sc, SMB_BCNT, &len); /* :533 slave value 0..255 */
for (i = 0; i < len; i++) { /* :534 bound is len */
amdsmb_ec_read(sc, SMB_DATA + i, &data); /* :535 OOB when i >= 32 */
if (i < *count)
buf[i] = data; /* :537 buf write bounded */
}
*count = len; /* :539 unclamped return */
}
The 32-byte SMB_DATA register file (amdsmb.c:75) is followed by
SMB_BCNT (:76, offset 0x24), SMB_ALRM_A (:77, 0x25),
SMB_ALRM_D (:78, 0x26). With len = 255, the loop reads offsets
0x04..0x101, walking past the SMBus register block entirely into the
ACPI embedded-controller register space and beyond.
Three reasons this is more than a benign OOB read:
- EC register reads can have side effects. Many ACPI EC registers
are read-to-clear status flags or strobe latches; 223 spurious EC
reads can perturb ACPI state (e.g. clear a pending SMBus alarm before
amdsmb_callbacksees it, or trip an EC IRQ). *count = lenunclamped breaks the SMBus 32-byte block invariant for every consumer ofsmbus_bread. Insys/dev/smbus/smb/smb.c:318,SMB_BREADthen doess->rcount = min(s->rcount, bcount); copyout(buf, s->rbuf, s->rcount);β copying up to 255 bytes from a 1024-bytebuf[1024]whose bytes32..254were never written (becauseamdsmb_breadonly wrotebuf[i]fori < *countwhere the entry-check capped*countat 32). That is a per-call uninitialized-kernel-stack leak to userspace via/dev/smbon any host that exposes ansmbdevice node bound to anamdsmbcontroller. (The/dev/smbdevice is root/wheel only, so this leak requires root/wheel β but the IPMI-SSIF path is reachable from any process that can talk toipmi0.)AMDSMB_LOCKhold time β eachamdsmb_ec_readcanDELAYup to 500Β΅s; withlen = 255the spinlock is held for ~340ms, a localized DoS on the calling CPU.
The cross-driver control: amdsmb_bwrite (:491-514) takes count by
value and validates count < 1 || count > 32 (:497) before any
MMIO. amdsmb_bread takes count by pointer and validates only the
caller's pre-existing *count, never the slave-supplied len. DF-1076
fixed the equivalent bug in ichsmb by clamping sc->block_count after
the ISR read it from ICH_D0.
Reproduction
$ sh verify.sh # 9/9 static checks $ cc -O0 -o df1091_harness df1091_harness.c $ cc -O0 -DFIX -o df1091_harness_fix df1091_harness.c $ ./df1091_harness # 223 OOB MMIO reads, *count=255 $ ./df1091_harness_fix # 0 OOB, *count=32
Fix
fix.diff clamps len to 32 immediately after it is read from
SMB_BCNT, mirroring DF-1076's ichsmb fix and matching the implicit
32-byte invariant enforced by the SMB_DATA register file layout and by
the sibling amdsmb_bwrite. The single inserted if (len > 32) len = 32;
both bounds the MMIO read loop and clamps the value returned via
*count = len. nativekernel build of the patched file succeeds; the
harness validates the algorithm-level correctness.
Fix verification
fixedvalidated
kernel build rc=0 + harness before/after
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. amdsmb_bread slave BCNT up to 255 vs 32B register file -> OOB MMIO + stack leak. No amdsmb HW.
No comments yet.