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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4099 Β· 12 files
FileTypeDescriptionSize
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 on bt->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.

VERDICT.md
↓ download raw

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 no default; comp_code is a device-fed u8 (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_testable
baseline reproduced→ patch + rebuild →patched clean

object-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
↓ fix.diffper-fix-DF-4099

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.