cyapa_poll_thread reads uninitialized regs.stat when the device stops responding β garbage byte drives the bootloader re-init state machine
Summary
The regs struct is a poll-thread local only written on smbus SUCCESS, but the reset decision reads regs.stat regardless of the transfer error. On the first failing iteration the byte is uninitialized stack and - after the 3s backoff - decides whether init_device re-issues bootloader handshakes every 3 seconds. No leak, no memory unsafety - hardening-class nondeterminism (malicious/glitching firmware makes stale stack data control recovery behavior). Decision-flip harness 2/2 each on the guest. bzero-before-loop fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4230 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| model_uninit_regs.c | β | 3.4 KB | view raw | |
| build.sh | β | 108 B | view raw | |
| run.sh | β | 132 B | view raw | |
| build.log | β | 392 B | view raw | |
| run.log | β | 646 B | view raw | |
| env.txt | β | 576 B | view raw | |
| fix.diff | β | 474 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 1.8 KB | view raw |
DF-4230 β cyapa: cyapa_poll_thread reads uninitialized regs.stat when the device stops responding β garbage byte drives the re-init state machine
Where
- cyapa.c:1164 β
struct cyapa_regs regs;β local, never initialized. - cyapa.c:1178-1184 β
smbus_trans(..., (void *)®s, sizeof(regs), NULL)only writesregson success; on bus error/NAKregskeeps its previous contents (uninitialized on the first failing iteration). - cyapa.c:1191-1193 β the reset decision is evaluated regardless of the
smbus error:
c if (cyapa_reset || ((regs.stat & CYAPA_STAT_RUNNING) == 0 && (unsigned)(ticks - last_reset) > TIME_TO_RESET)) {β reads the uninitializedregs.stat. - cyapa.c:1196 β consequence:
init_device(sc->dev, NULL, sc->addr, 2)is invoked (bootloader handshake hammering on the SMBus) based on garbage.
Impact
Purely a logic/robustness defect: an uninitialized stack byte decides whether the driver spams the bus with bootloader commands every 3 seconds while the device is dead. No info leak (the byte is never printed), no memory unsafety. Info severity, hardening class.
PoC β model_uninit_regs.c
Exact branch model with an smbus-failure stub and stack poisoning (observed on the audit guest, run.log): - poison byte 0x2d (RUNNING clear) β re-init triggered from garbage; - poison byte 0xcd (RUNNING set) β re-init suppressed by garbage. Both decisions flip solely on the uninitialized byte β 2/2 each.
Fix
Zero regs once before the loop (fix.diff): bzero(®s, sizeof(regs));
(or memset) at declaration, giving regs.stat = 0 β RUNNING bit clear β
deterministic re-init behavior.
Build / run
cc -O0 -Wall -o model_uninit_regs model_uninit_regs.c ./model_uninit_regs 0x2d ./model_uninit_regs 0xcd
DF-4230 β VERDICT
Status: untested (kernel-side run requires the trackpad hardware; the uninitialized-read mechanism and its control over the reset decision are demonstrated deterministically by the exact-branch harness on the guest).
Root cause (line-accurate)
struct cyapa_regs regs; /* cyapa.c:1164 uninit */
...
error = smbus_request_bus(bus, sc->dev, SMB_WAIT);
if (error == 0) {
error = smbus_trans(..., (void *)®s, sizeof(regs), NULL);
if (error == 0)
isidle = cyapa_raw_input(sc, ®s); /* only success writes regs */
}
if (cyapa_reset ||
((regs.stat & CYAPA_STAT_RUNNING) == 0 && /* cyapa.c:1192 UNINIT READ */
(unsigned)(ticks - last_reset) > TIME_TO_RESET)) {
cyapa_reset = 0;
last_reset = ticks;
init_device(sc->dev, NULL, sc->addr, 2); /* cyapa.c:1196 */
}
regs is only written on a successful transfer. When the device is
absent/glitching (first iteration fails), regs.stat is uninitialized
stack; after 3 s of failures the garbage bit-7 (CYAPA_STAT_RUNNING)
decides whether init_device() bootloader handshakes are re-issued every
3 s. (isidle at cyapa.c:1168 also goes stale on failure β same family.)
Harness result (audit guest, cc 8.3, -O0)
poison 0x2d (RUNNING clear): after 31 failing iterations: reinited=1 <- from garbage poison 0xcd (RUNNING set) : after 40 failing iterations: reinited=0 <- from garbage
Both outcomes of the decision flip purely on the uninitialized byte (2/2 runs each) β the driver's bus-recovery behavior is controlled by stale stack data.
Why only Info
- The value never leaves the kernel (no printf of regs on the failure path β cyapa_raw_input, which prints, only runs on success).
- No memory-safety consequence; the worst case is unnecessary SMBus bootloader traffic while the device is dead.
Recommended fix
Initialize regs before the poll loop (fix.diff):
bzero(®s, sizeof(regs)); β then stat=0 β RUNNING clear β the
existing 3-second backoff governs re-init deterministically.
Fix validation
Hardware-gated (poll thread unreachable in QEMU). fix_status = not_testable; the fix is a one-line initialization.
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_testableHardware-gated validation; one-line initialization against the traced path, applies cleanly to sys/.
findings/poc/DF-4230/fix.diff (git apply --check clean)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log + build.log (0x2d -> reinited, 0xcd -> not reinited, both from garbage byte), VERDICT.md, fix.diff
PoC changes
n/a - pack authored fresh
Verified recommended fix
bzero(®s, sizeof(regs)) before the poll loop (fix.diff)
Verdict
Source-certain uninitialized stack read: regs (cyapa.c:1164) is only written on smbus success, but the reset decision at cyapa.c:1191-1193 reads regs.stat on failure paths too; harness shows the re-init decision flipping solely on the uninitialized byte (2/2 per branch on the guest). Kernel-side run is hardware-gated (poll thread absent in QEMU). Info severity: no leak (value never printed), no memory unsafety - only nondeterministic bus-recovery behavior while the device is dead.
No comments yet.