Peer-initiated BLK_OPEN termination strands the open tag on tag_pendq forever: silent tag exhaustion (stale sc->last_error masks it), device wedge, softc/module unload permanently blocked
Summary
When the peer DELETEs the BLK_OPEN transaction while the device is open, xa_sync_completion calls xa_done(tag, 0): async==0 and no waiter exists, so xa_release is never called and the tag is stranded forever. Exhaustion is SILENT because the restart path fails via a LOCAL ENXIO while xa_open returns the stale last_error==0 - opens keep succeeding with no circuit. The non-empty tag_pendq permanently blocks terminate_check: softc/cdev immortal, kldunload EBUSY. Instrumented baseline: 63/64 tags stranded in 80 cycles; fix validated on a patched module (80/80 clean). Release-the-tag fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4382 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| xdpeer.c | β | 15.0 KB | view raw | |
| run.log | β | 326 B | view raw | |
| run.2.log | β | 326 B | view raw | |
| run.patched.log | β | 215 B | view raw | |
| fix_run.log | β | 215 B | view raw | |
| fix.diff | β | 2.3 KB | view raw | |
| env.txt | β | 145 B | view raw |
Fix verification
fixedPatched module: identical 80-cycle PoC executed the leak branch 80 times (counter deltas) with setupnull=0 and all opens succeeding - every tag returned to the freeq. Baseline stranded 63.
run.patched.log / fix_run.log (counter deltas); run.2.log (baseline)
Confirmed kernel references
Detail
Exploit chain
untrusted peer answers BLK_OPEN then DELETEs it while device open, repeat x64 -> all tags stranded -> device silently wedged + kernel memory retained + module unload permanently blocked. Availability only; no corruption.
Evidence (decisive lines)
run.2.log (63 distinct tags across 80 opens, stock); instrumented baseline counters in VERDICT.md (open_done=63, done_noasync=63, release=2, setupnull=17); run.patched.log (deltas: open_done +80, done_noasync +80, setupnull 0)
PoC changes
authored fresh; added 50ms open-hold so the peer DELETE always beats close() (deterministic leak branch); dynamic /dev/xaN discovery because immortal softcs from earlier tests shift unit numbers
Verified recommended fix
release the tag in xa_sync_completion when open_tag==tag: xa_done(tag,0) then xa_release(tag,0) with sc->lk dropped/re-taken; also propagate restart ENXIO into sc->last_error
Verdict
Peer-initiated termination of the BLK_OPEN transaction (DELETE or link flap while the device is open) strands the open tag on tag_pendq forever: xa_done(tag,0) with async==0 and no waiter never releases (xdisk.c:1126-1130, 1006-1017). Baseline: 63/64 tags stranded in 80 cycles (instrumented counters + dmesg tag census), exhaustion masked by xa_open returning stale sc->last_error (xdisk.c:748 vs local ENXUI at 1317), xa_terminate_check permanently blocked -> softc/device immortal and kldunload xdisk EBUSY forever. Fix (explicit xa_release in the open_tag==tag branch) validated: 80/80 leak-path executions, 0 strands (setupnull=0).
No comments yet.