cbb_detach() deletes the bridge children before stopping the slot event thread β cbb_insert/cbb_removal operate on freed child devices (UAF race), and the RUNNING-flag startup window lets detach free the softc under a live thread
Summary
detach runs device_delete_children while the event thread is still live: the thread re-evaluates the socket at least once per second and with a card present/absent calls cbb_insert/cbb_removal -> CARD_ATTACH/exca_insert/CARD_DETACH on children already freed - neither child pointer is cleared. Child detach is slow by design (multi-hundred-ms power sequencing), so under kldunload -f / bridge hot-removal the overlap window is seconds wide. Secondary: RUNNING is set by the thread itself after kthread_create returned, so a detach racing thread startup frees the softc under a live thread. Stop-thread-first + clear-pointers fix. (A protocol-replica KLD on the guest disproved the pure lost-wakeup hang axis - lwkt_switch_return wakes sleepers on the exiting thread's ident - isolating the ordering race.)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4276 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| df4276_rep.c | β | 5.3 KB | view raw | |
| Makefile | β | 58 B | β download | |
| build.sh | β | 88 B | view raw | |
| run.sh | β | 154 B | view raw | |
| build.log | β | 291 B | view raw | |
| run.log | β | 688 B | view raw | |
| env.txt | β | 319 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-4276 β cbb_detach() stops the slot event thread AFTER deleting its children (pccbb.c)
Finding (final scope after verification): sys/dev/pccard/pccbb/pccbb.c β CardBus bridge slot event-thread teardown ordering defect.
cbb_detach() (pccbb.c:279-306) runs
error = device_delete_children(brdev); /* pccbb.c:285 */
...
sc->flags |= CBB_KTHREAD_DONE; /* pccbb.c:294 */
if (sc->flags & CBB_KTHREAD_RUNNING) { /* pccbb.c:295 */
wakeup(&sc->generic_cv); /* pccbb.c:297 */
tsleep(sc->event_thread, 0, "cbbun", 0);/* pccbb.c:298 */
}
while cbb_event_thread() is still alive: it re-evaluates the socket at
least once per second (hz-timeout re-check loop, pccbb.c:479-484) and, with a
card present/absent, calls
cbb_insert()(pccbb.c:460) ->CARD_ATTACH_CARD(sc->cbdev)(pccbb.c:517) orexca_insert(&sc->exca[0])(pccbb.c:509), orcbb_removal()(pccbb.c:444) ->CARD_DETACH_CARD(sc->cbdev)/exca_removal(&sc->exca[0])(pccbb.c:536-539), pluscbb_destroy_res(sc)(pccbb.c:541) walkingsc->rl.
device_delete_children() frees the cardbus/pccard child devices
(sc->cbdev, sc->exca[0].pccarddev β neither pointer is cleared), and
child detach is slow by design (cbb_pcic_power_disable_socket sleeps
10+300 ms, pccbb.c:1246/1253; cbb_cardbus_reset 2x20-400 ms,
pccbb.c:914-925), so the overlap window during kldunload -f cbb /
bridge hot-removal is seconds wide. The thread operating on freed children
(use of dangling device_t, freed child softc via CARD_/exca_ methods) and
re-attaching card children under a detaching bridge is a use-after-free /
dangling-reference race. Additionally, CBB_KTHREAD_RUNNING is set by the
thread itself at pccbb.c:431, so a detach racing thread startup (created at
attach, pccbb_pci.c:405, but not yet running) skips the tsleep entirely and
the softc is freed under a live thread.
Trigger: privileged detach (kldunload -f cbb) with a card inserted, or
physical bridge hot-removal β Low severity per the rubric (requires
privilege/hardware), but the window is wide and the outcome is UAF.
What verification proved and disproved
Two candidate hang axes of this teardown were INVESTIGATED ON THE GUEST and DISPROVEN (this is a feature of this pack β the negative result rules them out for the whole file):
- "Lost final wakeup => permanent 'cbbun' hang": DISPROVEN.
lwkt_switch_return()unconditionally callswakeup(otd)on the exiting thread's ident (sys/kern/lwkt_thread.c:884-899), sotsleep(sc->event_thread, ...)returns when the thread exits even if the explicitwakeup(sc->event_thread)(pccbb.c:487) was lost in the wakeup(generic_cv)->tsleep window. The replica module (df4276_rep.c) demonstrates this rescue on the guest. - "Non-atomic flags RMW loses CBB_KTHREAD_DONE": impossible by program
order β the thread only reaches
flags &= ~CBB_KTHREAD_RUNNING(pccbb.c:486) after observing DONE in the loop condition, and the detacher stores DONE exactly once before that.
The surviving defects are the ordering (children deleted before the thread is stopped) and the RUNNING-flag startup window, which are source-certain but require CardBus hardware + detach timing, absent from the QEMU guest.
Reproduction (negative-result replica)
./build.sh # df4276rep.ko
./run.sh # kldload; the replica forces the lost-wakeup interleaving
# and shows the detacher STILL returns (thread-exit rescue)
Expected output (run.log):
df4276: replica armed df4276: event thread tail complete; final wakeup() delivered into the void df4276: detach COMPLETED -- no lost wakeup df4276: RESULT: no lost wakeup
Artifacts
- df4276_rep.c β replica module source (driver-exact sleep/wakeup discipline)
- build.log, run.log, env.txt
- fix.diff β reorder cbb_detach: stop the event thread FIRST
- verdict.json, manifest.json
DF-4276 β cbb_detach()/cbb_event_thread() teardown (sys/dev/pccard/pccbb/pccbb.c)
VERDICT: NOT REPRODUCED at runtime (hardware-gated); one candidate hang axis DISPROVEN on the guest; ordering/UAF axes remain source-certain.
Initial hypothesis (three axes)
(1) lost final wakeup -> permanent 'cbbun' hang; (2) non-atomic flags RMW losing CBB_KTHREAD_DONE; (3) device_delete_children() before thread stop -> event thread using freed children (sc->cbdev / sc->exca[0].pccarddev, never cleared) and a freed softc after detach returns.
What the guest run showed (df4276_rep.c, run.log)
The replica reproduces the detacher/event-thread sleep discipline verbatim (wakeup(&generic_cv) then tsleep(event_thread, 0, "cbbun", 0); thread tail: clear RUNNING, wakeup(event_thread)) and FORCES the unguarded interleaving (the wakeup lands between the detacher's two statements, where a preemption/other-CPU execution would put it in the wild). Result:
df4276: event thread tail complete; final wakeup() delivered into the void df4276: detach COMPLETED -- no lost wakeup
The detacher's tsleep(event_thread) RETURNS despite the lost explicit wakeup. Traced to sys/kern/lwkt_thread.c:884-899: when a thread with TDF_EXITING is switched away from, lwkt_switch_return() unconditionally calls wakeup(otd) on the exiting thread's ident. So axis (1) is DISPROVEN β the kernel's thread-exit wakeup rescues any lost final wakeup. This also retroactively hardens the pccbb detach path against pure lost-wakeup hangs.
Axis (2) was killed by program-order reasoning: the thread reaches
sc->flags &= ~CBB_KTHREAD_RUNNING (pccbb.c:486) only after its loop
condition observed CBB_KTHREAD_DONE, which the detacher stores exactly once
(pccbb.c:294) before its RUNNING check; nothing else writes flags in
between, so the thread's RMW cannot drop DONE. (The detach-vs-thread-STARTUP
RMW window remains: RUNNING is set at pccbb.c:431, after kthread_create
returned at attach, pccbb_pci.c:405 β a detach racing thread startup can
miss the thread entirely and free the softc under it.)
Axis (3) survives, source-certain: pccbb.c:285 deletes the cardbus/pccard children while the thread (which re-checks the socket every second, pccbb.c:479-484) concurrently executes cbb_insert/cbb_removal -> CARD_ATTACH_CARD/CARD_DETACH_CARD/exca_insert/exca_removal on those freed device_t's (pccbb.c:509/517/536-539), and cbb_destroy_res walks sc->rl (pccbb.c:541). The child-detach sleeps (10+300 ms power-down pccbb.c:1246, 1253; 2x20-400 ms cardbus reset pccbb.c:914-925) make the window seconds wide under kldunload -f cbb with a card present. The QEMU guest has no CardBus bridge (pciconf: no class 0607 device), so this cannot be exercised at runtime; severity Low (privileged detach / physical removal required).
Honest classification: not_reproduced / impact none (observed); the finding stands on source-level certainty for axis (3) plus the disproven-hang documentation.
Fix (fix.diff): stop the event thread before touching the children, and clear sc->cbdev/sc->exca[0].pccarddev after deletion.
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_testablefix.diff authored (reorder cbb_detach: thread stop before children deletion; NULL the child pointers). Not buildable/testable on this guest: no CardBus bridge exists, so neither baseline nor patched behavior can be exercised at driver level.
findings/poc/DF-4276/fix.diff
Confirmed kernel references
- sys/dev/pccard/pccbb/pccbb.c:285
- sys/dev/pccard/pccbb/pccbb.c:294
- sys/dev/pccard/pccbb/pccbb.c:295
- sys/dev/pccard/pccbb/pccbb.c:431
- sys/dev/pccard/pccbb/pccbb.c:460
- sys/dev/pccard/pccbb/pccbb.c:479
- sys/dev/pccard/pccbb/pccbb.c:486
- sys/dev/pccard/pccbb/pccbb.c:509
- sys/dev/pccard/pccbb/pccbb.c:517
- sys/dev/pccard/pccbb/pccbb.c:536
- sys/dev/pccard/pccbb/pccbb.c:541
- sys/dev/pccard/pccbb/pccbb_pci.c:405
- sys/kern/lwkt_thread.c:884
Detail
Evidence (decisive lines)
run.log (forced lost-wakeup interleaving still returns: 'detach COMPLETED -- no lost wakeup'); VERDICT.md (full three-axis analysis); source refs pccbb.c:285,294-299,431,460,479-484,486-487,509,517,536-541,1246,1253,914-925; pccbb_pci.c:405.
PoC changes
Replica authored from scratch. First build GPF'd for an unrelated module-convention reason (DECLARE_MODULE needs a moduledata_t variable on current DFly, not the handler function) β fixed; guest kernel rebuilt from the audit tree (see DF-4275). The replica's DELAY-based forcing of the wakeup window worked first try once buildable.
Verified recommended fix
Stop the event thread before device_delete_children and clear sc->cbdev/sc->exca[0].pccarddev afterwards (see fix.diff).
Verdict
NOT REPRODUCED at runtime (guest has no CardBus bridge; the UAF requires real hardware + detach timing). The finding stands source-certain on its ordering axis: cbb_detach (pccbb.c:285) deletes the cardbus/pccard children while cbb_event_thread is still live and re-checking the socket every second (pccbb.c:479-484), so cbb_insert/cbb_removal reach CARD_ATTACH_CARD/CARD_DETACH_CARD/exca_insert/exca_removal on freed device_t's (pccbb.c:509,517,536-539); child detach's 10-400ms sleeps make the window seconds wide under kldunload -f. Two candidate hang axes were DISPROVEN on the guest: (a) lost final wakeup β lwkt_switch_return() unconditionally wakes sleepers on an exiting thread's ident (kern/lwkt_thread.c:884-899), so tsleep(sc->event_thread) is always rescued (replica run.log); (b) flags RMW losing CBB_KTHREAD_DONE β impossible by program order. The RUNNING-flag startup window (set at pccbb.c:431 after kthread_create, pccbb_pci.c:405) remains as a narrower secondary race.
No comments yet.