Stale-callout race: callout_stop cannot cancel an in-progress bttimeout, which then fires BDR/hard-reset against a healthy reused CCB
Summary
bccb->timer is a non-lock-aided callout; callout_stop 'does not interlock against a callout that is in-progress'. If bttimeout has fired and is blocked acquiring bt->lock while btdone completes and frees the CCB (callout_stop a no-op against it), the freed BCCB can be re-grabbed and re-armed for a new command before the stale bttimeout acquires the lock. The stale handler sees BCCB_ACTIVE, treats the new command as timed out, freezes the SIM queue, stops every pending timer, and - outbox busy with the new command - escalates directly to a hard bus reset on a healthy bus plus misreported CAM_CMD_TIMEOUT completions. The sili DF-4027 / amr DF-4060 lifecycle-race family. Generation-marker handshake fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4101 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 3.5 KB | β raw | |
| dfbt_rig.c | β | 26.9 KB | view raw | |
| dfbt_ctl.c | β | 8.0 KB | view raw | |
| Makefile | β | 127 B | β download | |
| push_rig.sh | β | 975 B | view raw | |
| run1.sh | β | 3.9 KB | view raw | |
| run.log | β | 11.6 KB | view raw | |
| env.txt | β | 249 B | view raw | |
| fix.diff | β | 6.0 KB | view raw | |
| manifest.json | β | 747 B | view raw | |
| verdict.json | β | 3.2 KB | view raw |
DF-4101 β Stale bttimeout fires BDR/hard-reset on a recycled CCB
Verdict: REPRODUCED (deterministic choreography of the exact race)
Rig class as DF-2429 (see DF-2429/README.md).
Build
./push_rig.sh
Run
sh run1.sh # DF-4101 is the third section
Choreography (mirrors the production race window)
queue ioccb-A (healthy, 60 s timeout) β ACTIVE (flags=0x1).holdβ the ioctl thread takesbt->lock(the lock bttimeout acquires at bt.c:2143).racefire 3β a kthread calls the productionbttimeout(bccb)and blocks acquiring the lock: this is the in-progress callout invocation thatcallout_stopat bt.c:1449 cannot cancel.- Under the lock:
comp 3completes ccb-A (BMBI_OK; the callout_stop inside btdone is a no-op against the blocked handler) andqueue ioccb-B β productionbtgetccbhands out the same BCCB (bccb_kvaidentical), whichbtactionmarks ACTIVE again with a fresh 60 s timer. relβ the stalebttimeoutacquires the lock and proceeds against ccb-B.
Expected output (run.log)
- After
rel: ccb-Bflags=0x7(DEVICE_RESET set by the stale handler),timer=1(stale handler's 2s re-arm), consoleCCB 0xfffff801167df620 - timed outβ printed for a command whose own 60 s timer never expired. ccb-A meanwhile completed CAM_REQ_CMP (comp=1,last_status=0x1). - 2 s later: the stale handler's re-arm fires the hard-reset branch β
vhba_hard_resetsincrements (1β2; the extra hard reset on a healthy bus), and ccb-B completesccb_status=0xb(CAM_CMD_TIMEOUT) β a healthy command misreported as timed out.
The lock choreography makes the production race window deterministic; every step except the timing is genuine production code.
DF-4101 VERDICT β stale bttimeout on a recycled CCB
Bottom line
REPRODUCED β the race mechanism, its preconditions and its full
consequence chain, all on production code. The timing window (a
bttimeout invocation entered but blocked on bt->lock) is exactly the
window callout_stop (bt.c:1449) cannot close; the rig makes it
deterministic by holding the lock while the stale handler runs, which
is what genuine contention does.
Live trace (run.log + console)
[snap 3] ccb-A: bccb=0xfffff801167df620 flags=0x1 timer=1 (healthy, 60s)
[rig] stale handler thread: calling production bttimeout(0xfffff801167df620),
will block on bt->lock <- in-progress callout
[comp 3] ccb-A completed BMBI_OK (callout_stop a no-op against it)
[queue 4] ccb-B: bccb=0xfffff801167df620 <- SAME BCCB recycled by btgetccb
[rel] lock released -> stale bttimeout proceeds on ccb-B
[snap 3] ccb-A: comp=1 last_status=0x1 (CAM_REQ_CMP)
[snap 4] ccb-B: flags=0x7 (DEVICE_RESET set by the STALE handler)
timer=1 (stale handler's 2s re-arm)
console: "CCB 0xfffff801167df620 - timed out" (60s timer never fired)
[snap 4] after 2s: ccb_status=0xb (CAM_CMD_TIMEOUT) β healthy command
misreported; vhba_hard_resets 1 -> 2 (extra HARD_RESET(0x80)
write on a healthy bus)
Every claim of the finding is visible: stale handler runs after the CCB was completed and freed; same BCCB recycled for a new command; stale handler sees BCCB_ACTIVE on the new command, freezes the SIM queue, arms BDR, and 2s later hard-resets a healthy bus; the new command completes CAM_CMD_TIMEOUT through no fault of its own.
Root cause (path:line)
- bt.c:1449 β
callout_stop(&bccb->timer)does not interlock against an in-progressbttimeout(non-lock-aided callout; the handler is entered and blocked at bt.c:2143lockmgr(&bt->lock, ...)). - bt.c:2147 β the
BCCB_ACTIVEcheck cannot distinguish the new command from the one that timed out (no generation handshake).
Impact
Under real contention (timeout firing while the completion path holds
bt->lock), a healthy command is misreported CAM_CMD_TIMEOUT, the SIM
queue is frozen, and the bus takes an unnecessary BDR followed by a
hard reset 2s later β I/O disruption across all targets (DoS-class
reliability bug, sili DF-4027 / amr DF-4060 family).
Rig fidelity note
The only synthetic element is the guarantee that the handler is
mid-execution (the kthread stands in for the fired callout; in
production that requires the lock to be held at fire time β precisely
the race condition). Everything from bttimeout through btdone,
btaction, btgetccb recycling and the hard reset is production code
from the shipped kernel image, and the console output is the driver's
own.
Fix validation
fix.diff implements the generation handshake: bt_ccb gains
gen/timer_gen; btgetccb bumps gen, btexecuteccb snapshots it
into timer_gen when arming, and bttimeout/btdone ignore
invocations whose generation does not match. Patched bt.c+btreg.h
compile clean with the kernel's own flags under -Werror
(FIXED-COMPILES-CLEAN). No runtime A/B kernel build (race/DoS
class, not memory corruption; not mandated).
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_testableobject-compile validation only (race/DoS class, kernel A/B not mandated): patched bt.c+btreg.h compile clean under -Werror with kernel headers
fix.diff; 'FIXED-COMPILES-CLEAN' with bt.fixed.o on the guest
Confirmed kernel references
Detail
Exploit chain
bttimeout fires while a completion holds bt->lock -> handler entered, blocks at bt.c:2143 -> btdone completes+frees the CCB (callout_stop no-op against in-flight handler) -> btaction recycles the same BCCB for a new command -> stale handler acquires lock, sees BCCB_ACTIVE on the new command (bt.c:2147 cannot tell generations apart) -> freezes SIMq, stops all pending timers, arms BDR -> 2s later hard bus reset on the healthy bus + CAM_CMD_TIMEOUT miscompletions
Evidence (decisive lines)
run.log DF-4101 section: snap3/snap4 bccb_kva both 0xfffff801167df620; snap4 flags=0x7 timer=1 after rel with ccb-A comp=1 status=0x1; final snap4 ccb_status=0xb timer=0; console '[rig] stale handler thread: calling production bttimeout(0xfffff801167df620), will block on bt->lock' then 'CCB 0xfffff801167df620 - timed out'
PoC changes
race choreographed with explicit lock hold/release ioctls (the production timing window made deterministic); kthread stands in for the fired callout invocation
Verified recommended fix
Generation handshake: bt_ccb gains gen/timer_gen; btgetccb bumps gen, btexecuteccb snapshots it when arming the callout, bttimeout/btdone ignore generation-mismatched invocations (fix.diff, object-compile validated)
Verdict
REPRODUCED: the stale-callout race mechanism demonstrated deterministically on production code. A production bttimeout entered but blocked acquiring bt->lock (the exact in-progress invocation callout_stop at bt.c:1449 cannot cancel) survives the completion of its CCB (ccb-A, BMBI_OK -> CAM_REQ_CMP), the freed BCCB is recycled by production btgetccb for a new healthy command (identical bccb_kva captured), and once the lock is released the stale handler treats the NEW command as timed out: console 'CCB
No comments yet.