viapm_wait() off-by-one: while(count--)/if(!count) makes SMB_ETIMEOUT unreachable on genuine SMBus timeouts β silent success with stale register data β and spuriously fails last-poll successes
Summary
Post-decrement semantics leave count == -1 at loop exhaustion, so !count never fires: with a wedged controller every read op returns SMB_ENOERR handing callers stale SMBHDATA contents as fresh data (bread even reports the stale len byte), and a completion on the 10000th poll is spuriously failed. Data-integrity bug for every real SMBus transaction on VIA hardware; root-only surface. Logic proven with a guest-compiled replica of the exact loop. Explicit-done-flag fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4549 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| viapm_wait_offbyone_demo.c | β | 2.4 KB | view raw | |
| run.log | β | 276 B | view raw | |
| guest_hw_evidence.txt | β | 480 B | view raw | |
| fix.diff | β | 659 B | view raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| README.md | β | 1.3 KB | β raw |
DF-4549 β viapm_wait() off-by-one: SMB_ETIMEOUT can never be reported on a real timeout (and is spuriously set on a last-poll success)
File: sys/dev/powermng/viapm/viapm.c lines 566-605 (loop at 574-581, test at 587-588).
Build / Run (guest logic demo)
dfbsd-qemu/vm.sh reset with-src # clean guest # push viapm_wait_offbyone_demo.c (base64 over run_user) then: cc -Wall -Wextra -o /tmp/df4549 /tmp/df4549.c && /tmp/df4549
Expected output (see run.log)
case A (perpetual BUSY, real timeout): error=0x0 -> *** SMB_ETIMEOUT MISSING: kernel reports SUCCESS on wedged bus *** case B (INTR on final poll, success): error=0x20 -> *** SPURIOUS SMB_ETIMEOUT on successful transaction ***
The demo replicates the exact while (count--) { ... break; } / if (!count) pair
from viapm.c:574-588 with a fixture register read.
Why the kernel path itself is not run on the guest
viapm/viapropm are PCI drivers for VIA VT82C586B/596x/686A/823x PMU+SMBus
controllers. The QEMU guest has no such PCI device (pciconf -l β see
guest_hw_evidence.txt), so the module loads but never attaches and
viasmb_*() is unreachable. The defect is pure C arithmetic, proven by the
compiled demo; the hardware path is gated on a real VIA southbridge.
Both logs (run.log, guest_hw_evidence.txt) are the artifacts.
VERDICT β DF-4549
Status: not_reproduced (kernel path hardware-gated; the logic defect itself
is proven by a compiled demo run on the guest). Impact: none on this guest
(data-integrity bug on real VIA hardware; root-only reachability via
/dev/smbN, which is make_dev(..., UID_ROOT, GID_WHEEL, 0600, "smb%d") β
sys/dev/smbus/smb/smb.c:139-141).
Root cause (path:line)
viapm_wait() β sys/dev/powermng/viapm/viapm.c:567-605:
int count = 10000;
u_char sts = 0;
...
while(count--) { // :574
DELAY(10);
sts = VIAPM_INB(SMBHST);
if (!(sts & SMBHST_BUSY) && (sts & SMBHST_INTR))
break; // success path
}
...
if (!count) // :587
error |= SMB_ETIMEOUT;
count is int. while (count--) post-decrements: the tested value is the
PRE-decrement value; when the tested value is 0 the loop exits and count
holds -1. Therefore:
-
Genuine timeout (controller stuck with
SMBHST_BUSY, or never raisingSMBHST_INTR): after all 10000 polls (100 ms),count == -1,!countis false,SMB_ETIMEOUTis never set.viapm_wait()returnsSMB_ENOERR(unless stale status bits happen to hold FAILED/COLLID/ERROR), soviasmb_recvb(viapm.c:690-691),viasmb_readb(736-737),viasmb_readw(784-789) andviasmb_bread(849-862) all return "success" and hand the caller stale SMBHDATA0/1 register contents as fresh data (viasmb_breadeven reports the stalelenbyte as the transfer count β interacting with DF-1691). Write ops (viasmb_quick/sendb/writeb/writew/ bwrite) "succeed" without any transfer having completed. Callers that consume SMBus data for decisions (thermal/battery/EEPROM readers, root via/dev/smbNioctl SMB_READB/READW/BREAD/TRANS β smb.c:272-330) get silently-wrong data with no error. -
Success on the final poll (INTR lands exactly on the 10000th iteration):
breakleavescount == 0,!countis true, and a successful transaction is spuriously failed withSMB_ETIMEOUT.
Proof
Compiled demo (viapm_wait_offbyone_demo.c, built with guest cc 8.3
[DragonFly]) reproducing the exact loop shape:
case A (perpetual BUSY, real timeout): error=0x0 -> *** SMB_ETIMEOUT MISSING ... *** case B (INTR on final poll, success): error=0x20 -> *** SPURIOUS SMB_ETIMEOUT ... ***
(finding run.log β full untrimmed output.)
Why not reproduced in-kernel here
viapm.ko loads on the guest but never attaches: pciconf -l shows no VIA
1106:30xx/31xx PMU device (guest_hw_evidence.txt). The bug is exercised by
every real SMBus transaction on affected VIA hardware; no QEMU device model
exists for it. Both failure modes are deterministic C semantics, hence
confidence certain for the defect itself.
Exploit chain
None β silent wrong-data / missing-error logic bug (not memory corruption).
Reachable by root only through the 0600 /dev/smbN node on VIA-hardware
hosts, and by in-kernel smbus consumers.
Fix validation
Not performed (no memory corruption reproduced; kernel path hw-gated). The
done-flag diff in fix.diff is line-accurate against viapm.c:567-605 and
semantically verified by the demo (case A -> timeout reported, case B ->
success reported when the flag variant is applied by inspection).
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
not_testablenot_testable: kernel path requires VIA PMU hardware absent from the guest; the done-flag diff is verified by inspection against the demo semantics (case A would report timeout, case B would report success).
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (guest cc 8.3 build + both failure modes); guest_hw_evidence.txt (pciconf: no 1106:30xx/31xx device; kldload viapm succeeds, no attach); VERDICT.md (line-by-line root cause at viapm.c:574/587)
PoC changes
No seed existed; authored host-side replica of the viapm_wait loop with fixture register reads, compiled and executed on the guest (no host compiler available).
Verified recommended fix
Track completion with a done flag (set on the break) and test if (!done) instead of if (!count) in viapm_wait().
Verdict
Logic defect certain by C semantics and proven by a guest-compiled replica of the exact viapm_wait loop (run.log: perpetual-BUSY -> error=0x0, SMB_ETIMEOUT never reported; final-poll INTR -> error=0x20, spurious failure). The in-kernel path is hardware-gated: no VIA PMU PCI device exists in the QEMU guest (guest_hw_evidence.txt), so viapm.ko loads but never attaches and the smbus interface is unreachable. Root-only via 0600 /dev/smbN plus in-kernel smbus consumers on real VIA southbridges; impact is silent wrong-data/missing-error (data integrity), not memory corruption.
No comments yet.