Unknown mailbox comp_code leaks the BCCB, dmamap and CAM command forever β btdone's comp_code switch has no default case after the timer was stopped
Summary
comp_code is a device-fed u8 holding any of 256 values, but the switch only handles 0x0-0x4 with no default. callout_stop already executed, so when the HBA writes comp_code >= 5 the function falls off the switch and returns: the CCB stays on pending_ccbs flagged ACTIVE, its dmamap stays loaded, and no CAM completion is ever delivered - the command hangs forever with no timeout, and the BCCB is permanently lost. Repeating for each of the adapter's CCBs wedges all I/O (amr DF-4059 analogue). default-arm-fails-CCB fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4099 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 2.6 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 | β | 660 B | view raw | |
| verdict.json | β | 2.8 KB | view raw |
DF-4099 β Unknown mailbox comp_code leaks the BCCB and CAM command forever
Verdict: REPRODUCED (permanent leak + bus-wide wedge)
Rig class as DF-2429 (production handlers, real XPT dispatch, virtual firmware writing completion mailboxes β see DF-2429/README.md).
Build
./push_rig.sh
Run
sh run1.sh # DF-4099 is the final section
Expected output (run.log)
After queuein (CAM_DIR_IN, 512 bytes, real dmamap) and a completion
with the unknown comp_code 0x42 delivered to production btdone:
[snap] id=5 bccb=... flags=0x1 (BCCB_ACTIVE) ccb_status=0x200
(CAM_SIM_QUEUED) on_pending=1 timer=0 comp=0 map=1
three seconds later β and forever after:
flags=0x1: the BCCB is still marked ACTIVE,on_pending=1: still onbt->pending_ccbs,timer=0: its callout was stopped at bt.c:1449 and is never re-armed,comp=0: no CAM completion callback ever fires,ccb_status=0x200(CAM_SIM_QUEUED): the client command hangs forever.
Repeating for every BCCB on the adapter leaks all of them
(active=11 pending=11 captured; exhaustion of the remaining slots)
and the next queue io is CAM_REQUEUE_REQ'd (bccb=0x0, btgetccb
NULL β resource_shortage + simq frozen): all I/O on the bus wedges.
Honest correction
The finding's "dmamap stays loaded" detail is wrong in one respect:
bus_dmamap_sync/bus_dmamap_unload (bt.c:1395-1404) execute BEFORE
the comp_code switch, so a DIR_IN CCB's map is unloaded even on the
fall-through path. The leak that matters β BCCB never freed, CAM
command never completed, timeout already stopped β is fully reproduced.
DF-4099 VERDICT β unknown comp_code leaks BCCB + CAM command permanently
Bottom line
REPRODUCED. When the host adapter completes a mailbox with a
comp_code the switch at bt.c:1451-1608 does not handle (only 0x00-0x04
have cases; there is no default), production btdone falls off the
switch and returns after having already executed callout_stop at
bt.c:1449. Observed live on the production handler:
compx id=5 0x42 (BMBI_ERROR-shaped completion, unknown code 0x42)
[snap 3s later] flags=0x1 (ACTIVE) on_pending=1 timer=0
comp=0 ccb_status=0x200 (CAM_SIM_QUEUED) map=1
The command never completes (comp stays 0 forever), the BCCB never
returns to free_bt_ccbs, and its timeout is already stopped β the
command is leaked irrevocably. Repeating for each BCCB leaks them all;
the exhaustion demo in run.log ends with active=11 pending=11
permanently leaked and the next queue io returning bccb=0x0
(btgetccb NULL β CAM_REQUEUE_REQ + resource_shortage + simq freeze) β
every subsequent I/O on the bus wedges. (Some of the 14 exhaustion
iterations EINVAL'd on the rig side due to devq dispatch pacing; 11
BCCBs leaked is past any doubt, and the mechanism is identical.)
Root cause (path:line)
- bt.c:1451-1608 β
switch (comp_code)with nodefault; comp_code is a device-fedu8(btreg.h:466-472 defines only 0x00-0x04). - bt.c:1449 β
callout_stop(&bccb->timer)already executed, so no timeout will ever rescue the leaked command.
Honest correction to the finding text
bus_dmamap_unload runs at bt.c:1403 BEFORE the switch, so the
dmamap of a data-carrying CCB is unloaded even on the fall-through
path ("dmamap stays loaded" in the summary is incorrect). The BCCB,
the CAM command and the timeout are still leaked exactly as claimed.
Impact
Hostile or buggy HBA firmware (or a compromised HBA) wedges all I/O on the bus with a handful of mailbox bytes β permanent DoS with no recovery except a reboot.
Fix validation
fix.diff adds a default arm that completes the CCB with
CAM_REQ_CMP_ERR via the normal tail (btfreeccb + xpt_done). Patched
bt.c+btreg.h compile clean with the kernel's own flags under -Werror
(FIXED-COMPILES-CLEAN). No runtime A/B kernel build (leak/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 (leak/DoS class, kernel A/B not mandated): patched bt.c compiles clean under -Werror with kernel headers
fix.diff; 'FIXED-COMPILES-CLEAN' with bt.fixed.o on the guest
Confirmed kernel references
Detail
Exploit chain
hostile/buggy HBA completes a mailbox with comp_code>=0x05 -> bt_intr_locked -> btdone: callout_stop (1449) then switch falls through with no default -> BCCB stays ACTIVE on pending_ccbs, never freed, xpt_done never called -> repeat per BCCB -> btgetccb NULL -> CAM_REQUEUE_REQ + simq freeze -> permanent I/O wedge on the bus
Evidence (decisive lines)
run.log DF-4099 section: [snap] flags=0x1 on_pending=1 timer=0 comp=0 ccb_status=0x200 at T+3s; exhaustion tail: active=11 pending=11, queue id=20 -> bccb=0x0 (REQUEUE)
PoC changes
queue->btaction dispatch is asynchronous through the devq, so the run sleeps+snapshots after each queue; exhaustion loop leaked 11 of 15 (rig-side EINVAL on a few iterations due to pacing), which is sufficient
Verified recommended fix
Add a default arm to the comp_code switch that completes the CCB with CAM_REQ_CMP_ERR (device_printf + btfreeccb + xpt_done) -- fix.diff, object-compile validated
Verdict
REPRODUCED: production btdone falls off the comp_code switch (no default, bt.c:1451; only 0x00-0x04 handled of a device-fed u8) after callout_stop already ran (bt.c:1449). Live: 3s (and permanently) after an unknown 0x42 completion the BCCB is still flags=0x1 ACTIVE on pending_ccbs, timer stopped, comp=0 callbacks, ccb_status=0x200 CAM_SIM_QUEUED -- the BCCB and the CAM command are leaked forever with no timeout to rescue them. Exhaustion run leaves 11 BCCBs permanently leaked and further commands CAM_REQUEUE_REQ'd (btgetccb NULL, resource_shortage, simq frozen): all I/O on the bus wedges. One honest correction: bus_dmamap_unload runs at bt.c:1403 BEFORE the switch, so the map of a data-carrying CCB is unloaded even on the fall-through; the 'dmamap stays loaded' detail in the summary is wrong, the BCCB/command leak is real.
No comments yet.