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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4549 Β· 6 files
FileTypeDescriptionSize
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.md
↓ download raw

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:

  1. Genuine timeout (controller stuck with SMBHST_BUSY, or never raising SMBHST_INTR): after all 10000 polls (100 ms), count == -1, !count is false, SMB_ETIMEOUT is never set. viapm_wait() returns SMB_ENOERR (unless stale status bits happen to hold FAILED/COLLID/ERROR), so viasmb_recvb (viapm.c:690-691), viasmb_readb (736-737), viasmb_readw (784-789) and viasmb_bread (849-862) all return "success" and hand the caller stale SMBHDATA0/1 register contents as fresh data (viasmb_bread even reports the stale len byte 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/smbN ioctl SMB_READB/READW/BREAD/TRANS β€” smb.c:272-330) get silently-wrong data with no error.

  2. Success on the final poll (INTR lands exactly on the 10000th iteration): break leaves count == 0, !count is true, and a successful transaction is spuriously failed with SMB_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_testable
baseline no→ patch + rebuild →patched clean

not_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
↓ fix.diffper-fix-DF-4549

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.