All three amdsmb poll-loop timeouts are dead code (post-decrement off-by-one): wedged EC/SMBus controller is reported as SMB_ENOERR success with the PREVIOUS transaction's data and count after a 5.4s lock-held busy spin; SMB_ETIMEOUT is unreachable
Summary
All three wait loops use while(x-- && cond) + if(x==0): exhaustion leaves x==-1 so the timeout branch NEVER fires - a wedged controller busy-spins 5.44s holding the exclusive lock then reads STS 0x00 and returns SUCCESS with the stale previous data and count (cross-transaction confusion); the EC-level variants accept a stale OBF byte as any register (e.g. BCNT). The dead branches are the ONLY failure returns, so the engine's ignored error codes are moot end-to-end. Production-code KLD rig with a stateful AMD-8111 emulator: T1 wedge -> rc=0 after 5440ms/10002 polls returning previous data+count; T2/T2b EC wedges -> rc=0 past budget; fix.diff -> SMB_ETIMEOUT fires, zero data copied, healthy T0 PASS. ichsmb DF-5461 / ig4 DF-5386 family.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5506 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.3 KB | β raw | |
| VERDICT.md | β | 4.7 KB | β raw | |
| amdh_harness.c | β | 12.2 KB | view raw | |
| amdsmb.c | β | 14.2 KB | view raw | |
| Makefile | β | 311 B | β download | |
| build.sh | β | 90 B | view raw | |
| run.sh | β | 108 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| build.log | β | 1.4 KB | view raw | |
| build_fixed.log | β | 1.4 KB | view raw | |
| run.log | β | 2.3 KB | view raw | |
| run_fixed.log | β | 2.2 KB | view raw | |
| env.txt | β | 340 B | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-5506 PoC β amdsmb poll state machine: all three timeout branches are dead code; wedged EC/controller is reported as SUCCESS with stale data
Root cause
Every polling loop in sys/bus/smbus/amdsmb/amdsmb.c detects its timeout
with if (x == 0) after a loop whose exit-on-exhaustion leaves x == -1
(post-decrement idiom while (x-- && cond)):
amdsmb_ec_wait_write(amdsmb.c:212-221):while (timeout-- && ...IBF)thenif (timeout == 0)β when the 500-iteration budget is exhausted,timeout == -1, the branch never fires, the function returns 0 ("success") and the caller writes the next EC byte anyway against a wedged input path.amdsmb_ec_wait_read(amdsmb.c:224-235): identical, OBF side β after 500 polls the caller readsEC_DATAand accepts the stale byte from the previous EC data read as the requested register's value.amdsmb_wait(amdsmb.c:277-294):do { DELAY(500); ... } while (temp != 0 && count--)thenif (count == 0) return (SMB_ETIMEOUT)βcountexits as -1, soSMB_ETIMEOUTis unreachable dead code. A controller whoseSMB_PRTCLnever returns to 0 (wedged transaction) makes the driver busy-spin 10001 Γ 500 Β΅s β 5.45 s while holding the exclusivesc->lock, then readSMB_STS, see 0x00 (OK) and returnSMB_ENOERRsuccess β whereuponamdsmb_breadreturns the stale data and byte count of the previous transaction as the result of the new one.
Compounding it, every amdsmb_ec_read/amdsmb_ec_write return value in
the request engine is ignored (amdsmb.c:350-351, 368-370, 388-389, 408-411,
429-431, 450-453, 473-475, 501-506, 528-530) β but since the timeout
branches are dead code those functions can never report failure anyway:
the error plumbing of the whole driver is unreachable.
Hardware-gated (AMD-8111, PCI 1022:746a) and reached through /dev/smbN
(root-only on stock installs), same evidence class as the ichsmb/ig4
batches.
Evidence method (production-code KLD harness)
No QEMU machine model contains an AMD-8111 SMBus controller, so the rig
compiles the real, unmodified production source (amdsmb.c, asserted
byte-identical to /usr/src in build.log:1) into a KLD and redirects
bus_read_1/bus_write_1 (#undef + re-#define before the
#include) into a stateful emulation of the AMD-8111 ACPI-EC / SMBus 2.0
register interface (full EC_CMD_RD/WR handshake, IBF/OBF status flags,
SMB register bank 0x00-0x27). The file's own AMDSMB_ECINB/ECOUTB macros
expand at their use sites, so the production code executes verbatim,
in-kernel, against the emulated controller.
Observed (run.log, baseline)
T1 wedge_prtcl:amdsmb_breadon a controller whose PRTCL never clears β rc=0 (SMB_ENOERR) after 5440 ms / 10002 polls, returning the previous transaction's data (0xB0..) and count (32) as the new transaction's result.SMB_ETIMEOUT(0x20) never returned.T2 wedge_ibf:amdsmb_ec_readafter 502 EC_SC polls (budget 500) β rc=0 "success"; the write proceeded against the wedged EC.T2b wedge_obf:amdsmb_ec_readof SMB_BCNT after 503 polls β rc=0 with the stale EC data byte 0x5C accepted as the register value.
Observed (run_fixed.log, fix.diff applied to the harness copy only)
T1: rc=32 (SMB_ETIMEOUT), no data copied (buffer untouched). The 5.4 s busy-wait remains (the EC interface offers no abort/KILL register; the fix detects, it cannot cancel) β noted as residual.T2/T2b: rc=1 β the previously-deaddevice_printftimeout messages now actually fire ("timeout waiting for IBF to clear" / "for OBF to set").- Sanity
T0(healthy path) unchanged: rc=0, correct data.
Build & run
guest# cd /root/amdh && sh build.sh # asserts amdsmb.c == /usr/src copy, builds amdh.ko (-Werror clean) guest# sh run.sh # kldload ./amdh.ko; tests run at MOD_LOAD; output via dmesg guest# sh build.sh fixed && sh run.sh # fix.diff applied to the local copy, same tests
Fix
fix.diff: make all three poll loops detect exhaustion (x-- > 0 +
if (x < 0)), and propagate amdsmb_ec_read failures in amdsmb_wait
as SMB_EBUSERR (plus the DF-5507/DF-1091-family clamp in
amdsmb_bread). Authored against the read-only sys/ tree; validated in
the fixed harness build above; never applied to sys/.
DF-5506 VERDICT β REPRODUCED (in-kernel, production source, emulated AMD-8111 EC); fix validated in the same rig
Bottom line
All three timeout detectors in the amdsmb poll state machine are dead
code β the while (x-- && cond) / if (x == 0) post-decrement idiom
leaves the counter at -1 when the budget is exhausted, so exhaustion is
never detected:
amdsmb_ec_wait_write(sys/bus/smbus/amdsmb/amdsmb.c:214-219): 500-poll IBF budget β returns 0 ("go ahead") on exhaustion.amdsmb_ec_wait_read(amdsmb.c:228-234): 500-poll OBF budget β returns 0 on exhaustion; caller then reads a stale EC_DATA byte as register data.amdsmb_wait(amdsmb.c:287-293): 10000 Γ DELAY(500) PRTCL budget βSMB_ETIMEOUTat amdsmb.c:293 is unreachable; the driver proceeds to read SMB_STS of a still-busy/wedged controller and maps whatever it finds (0x00 βSMB_ENOERR) onto the current transaction.
Consequences, all demonstrated by executing the unmodified production source in-kernel against a datasheet-faithful AMD-8111 EC register emulator (T1/T2/T2b in run.log):
- A wedged SMBus transaction is reported as SUCCESS after a 5.44 s
busy-spin under the exclusive
sc->lock, returning the PREVIOUS transaction's data and byte count as the new transaction's result (cross-transaction data confusion; every smbus op is affected, not just block reads). - A wedged EC input path (IBF stuck) never stops the driver: it keeps writing CMD/address/data bytes into a controller that is not consuming them.
- A wedged EC output path (OBF never sets) yields stale EC data bytes accepted as arbitrary SMB register values (e.g. SMB_BCNT β feeding the known DF-1091 count path with a stale rather than slave-controlled value).
Because the same dead branches are the ONLY failure returns of
amdsmb_ec_read/amdsmb_ec_write, the drivers' ignored-return-codes
issue (every call site in amdsmb.c:350-530) is moot until this is fixed β
the error plumbing is unreachable end-to-end.
Impact ceiling: local DoS (a /dev/smbN opener β root on stock installs;
operator-group on some β can serialize 5.4 s lock-held busy-spins per
ioctl against stuck hardware) plus silent data corruption for legitimate
SMBus clients (sensors, EDID, battery). No memory-safety impact: the
stale values feed bounded paths (the unbounded one is known DF-1091).
Severity Low, matching the ichsmb sibling (DF-5461, timeout/abort family)
and ig4 DF-5386 (silent success family).
Reproduction honesty
The AMD-8111 SMBus 2.0 controller (PCI 1022:746a) has no QEMU machine model β no stock or buildable rig executes this driver on emulated hardware. The evidence class is the production-code KLD harness used by the ichsmb/ig4 batches, taken one step further: the harness #includes the real amdsmb.c (byte-identity asserted at build time, build.log:1) and interposes a stateful register-level emulation of the EC interface the driver actually drives (EC_CMD_RD/WR handshake with IBF/OBF flags). The executed code is the production text verbatim; only the ultimate port I/O is emulated. The observed behavior (10002 polls / 5440 ms / rc=0) is deterministic C semantics, not emulator timing: the dead-branch proof is static (the counter provably exits at -1), the run makes it visible.
Fix validation (same rig, fix.diff applied to the harness copy)
- Baseline run.log T1: rc=0 (fake success), stale data+count, 5440 ms. Fixed run_fixed.log T1: rc=32 (SMB_ETIMEOUT), zero bytes copied.
- Baseline T2/T2b: rc=0 with 502/503 polls ("success" past exhaustion). Fixed: rc=1, and the previously-dead device_printf timeout messages now execute (they print "unknown:" because the harness fake device_t is minimal β cosmetic, noted).
- Healthy-path regression: T0 PASS in both builds.
- Residual (documented, not claimed fixed): the fixed driver still busy-waits the full 5.4 s before reporting β the EC register interface exposes no transaction-abort (KILL) mechanism; converting the spin to a sleep/timeout scheme is an upstream design decision.
Kernel references
sys/bus/smbus/amdsmb/amdsmb.c:212-221 (wait_write), :224-235 (wait_read), :277-294 (wait; dead SMB_ETIMEOUT at :293), :296-324 (STS read of wedged controller mapped to SMB_ENOERR), :349-356 et al. (lock-held across the entire spin), sys/dev/smbus/smb/smb.c:206-359 (reachability: /dev/smb ioctls, root-gated device).
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
fixedBaseline reproduced (rc=0 fake success with stale data after 5440ms; dead SMB_ETIMEOUT). Patched build (fix.diff applied to harness copy, /usr/src untouched): T1 returns SMB_ETIMEOUT with zero data copied, T2/T2b return rc=1 with the timeout messages now firing, healthy-path T0 unchanged. The bad behavior is gone.
run.log vs run_fixed.log (T1/T2/T2b lines); build_fixed.log; fix.diff
Confirmed kernel references
- sys/bus/smbus/amdsmb/amdsmb.c:212
- sys/bus/smbus/amdsmb/amdsmb.c:214
- sys/bus/smbus/amdsmb/amdsmb.c:216
- sys/bus/smbus/amdsmb/amdsmb.c:228
- sys/bus/smbus/amdsmb/amdsmb.c:230
- sys/bus/smbus/amdsmb/amdsmb.c:287
- sys/bus/smbus/amdsmb/amdsmb.c:291
- sys/bus/smbus/amdsmb/amdsmb.c:293
- sys/bus/smbus/amdsmb/amdsmb.c:297
- sys/dev/smbus/smb/smb.c:318
Detail
Evidence (decisive lines)
run.log T1/T2/T2b (baseline: rc=0 fake success, 5440ms/10002 polls, stale 0xB0.. data accepted, stale 0x5C accepted as SMB_BCNT), run_fixed.log (rc=32 SMB_ETIMEOUT, rc=1, no data copied, dead printfs now fire), build.log:1 (production-source byte-identity assertion), amdh_harness.c (rig: #undef/#define bus_read_1/bus_write_1 before #include of the real amdsmb.c + EC/SMB register emulator), fix.diff, VERDICT.md
PoC changes
no seed existed (pass-2 fresh finding); authored the production-code KLD harness: #include of the real amdsmb.c with bus_read_1/bus_write_1 redirected into a stateful AMD-8111 EC register emulator, fake device_t/resource/softc, tests T0-T4 executed at MOD_LOAD
Verified recommended fix
amdsmb.c: fix all three poll loops to detect exhaustion (while (x-- > 0 && cond) + if (x < 0)), propagate amdsmb_ec_read/write failures in amdsmb_wait as SMB_EBUSERR β exactly fix.diff; validated in-rig.
Verdict
REPRODUCED in-kernel against the unmodified production source: all three amdsmb poll-loop timeouts (ec_wait_write amdsmb.c:214-219, ec_wait_read :228-234, wait :287-293) are dead code β the post-decrement idiom exits with the counter at -1, so if (x == 0) never fires and SMB_ETIMEOUT (:293) is unreachable. A wedged SMB_PRTCL makes amdsmb_bread busy-spin 10002x500us=5.44s under the exclusive sc->lock and then return SMB_ENOERR with the PREVIOUS transaction's data and count (run.log T1); a wedged IBF lets the driver keep writing to a non-consuming EC (T2); a wedged OBF makes it accept a stale EC data byte as any register value, e.g. SMB_BCNT (T2b). Fix.diff (detect exhaustion via x-- > 0 / x < 0 + propagate ec_read failures) validated in the same rig: fixed build returns SMB_ETIMEOUT / rc=1 and copies no data; the previously-dead device_printf timeout messages now execute; healthy path unchanged (T0 PASS both). Residual: 5.4s busy-wait remains (EC interface has no abort/KILL register). Hardware-gated (AMD-8111 1022:746a, absent from the guest and from every QEMU machine model) β evidence class is the production-code KLD harness with a stateful AMD-8111 EC register emulator (amdsmb.c asserted byte-identical to /usr/src at build time). Severity Low like the ichsmb DF-5461 / ig4 DF-5386 siblings.
No comments yet.