Detach/shutdown lifetime: self-rescheduling mly_periodic can survive callout_stop and in-flight commands are never drained β use-after-free after mly_free()
Summary
mly_periodic re-arms itself at its tail; a racing RESET 'automatically overrides a STOP', so callout_stop in mly_shutdown can return with the callout armed; mly_free then frees everything while the 1s tick and undrained completions may still fire. Admin operation (kldunload/detach/hot-removal) colliding with the periodic tick or in-flight I/O - UAF corruption/panic. Mechanism class demonstrated on this guest kernel by the DF-3715 replica. callout_terminate + EBUSY-drain fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3931 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 989 B | β raw | |
| VERDICT.md | β | 3.4 KB | β raw | |
| fix.diff | β | 869 B | view raw | |
| env.txt | β | 543 B | view raw | |
| verdict.json | β | 2.9 KB | view raw |
DF-3931 β mly: detach/shutdown lifetime β self-rescheduling callout + no in-flight drain β use-after-free
Build / Run (on hardware)
kldunload mly.ko # or device detach / surprise removal ...while mly_periodic runs (every 1s) and/or I/O or event-fetch commands are in flight
Race window per unload attempt is the callback body / completion latency; repeat unloads hit it. The driver's own comment admits the in-flight half: mly.c:514-517 "XXX this applies for I/O, but not status polls; we should beware of the case where a status command is running while we detach."
Expected result
Either (a) mly_periodic fires after mly_free() β operates on freed softc (UAF: reads mly_btl, allocates and DMA-maps commands on destroyed tags), or (b) an in-flight completion taskqueue entry runs mly_complete()/event handlers after teardown β UAF. Not runtime-reproduced (no mly hardware); source-likely. See VERDICT.md.
DF-3931 verdict narrative
Reproduced?
No (hardware-gated + narrow race). Confidence: likely β every ingredient is source-verified on this tree; the mechanism class (self-rescheduling callout defeating stop; teardown without drain) is proven elsewhere on this guest (DF-3715 replica demonstrated that a callback in progress when the stop lands determines whether callout_stop can return, and kern_timeout.c's own comments state "Request a RESET. ... automatically overrides a STOP").
Root cause chain (path:line)
mly_periodicre-arms itself unconditionally at its tail: mly.c:1397callout_reset(&sc->mly_periodic, MLY_PERIODIC_INTERVAL * hz, mly_periodic, sc);- Teardown stops it exactly once, asynchronously-to-the-callback:
mly.c:530
callout_stop(&sc->mly_periodic);(in mly_shutdown) mly_free() (mly.c:677-733) never touches the callout. Sequence that escapes: timer dequeues callback on CPU1 β mly_shutdown on CPU0 issues callout_stop (callback already INPROG; DFly callout_stop is sync, kern_timeout.c:1074-1077) β the sync-stop waiter can be released by the callback's own callout_reset, whose "RESET ... overrides a STOP" (kern_timeout.c:840-846, 976-980) leaves the callout ARMED after mly_shutdown returns β mly_detach proceeds to mly_free() β 1s later mly_periodic(sc) runs on freed memory: it walks sc->mly_btl[], calls mly_rescan_btl β mly_alloc_command on destroyed queues, bus_dmamap_load on destroyed tags, mly_start on unmapped registers β use-after-free. - In-flight commands are not drained either: mly_shutdown's own XXX
(mly.c:514-517) concedes status polls can be running at detach.
mly_free() destroys the CAM sims, frees command packets
(bus_dmamem_free, mly.c:1768-1772) and tears down the interrupt while
mly_busymay be non-empty; the completion task (taskqueue_swi β mly_complete, mly.c:1590) then invokes mc_complete (e.g. mly_cam_complete β xpt_done on a CCB whose sim is gone, mly_release_command touching freed packets). - There is no production timeout at all: MLY_CMD_TIMEOUT handling exists
only under
#ifdef MLY_DEBUG(mly.c:119, 322-324, 2971-2991) and even then only prints. mly_cam_action has no XPT_ABORT support (falls to default β CAM_REQ_INVALID, mly.c:2160-2163), so a hung command hangs its CCB forever and, per (3), blocks safe detach forever instead β pick your poison.
Threat model
Requires admin action (kldunload/detach) or hot-removal on a live mly controller with I/O/events in flight β privilege-gated, but it converts a routine management operation into a UAF.
Impact
Use-after-free of the softc, command packets and DMA machinery after detach: memory corruption / panic.
Fix
- callout_terminate() for the periodic (prevents the re-arm override), and
- refuse shutdown/detach while mly_busy/mly_complete are non-empty (fix.diff). A production command timeout would be the deeper fix.
Fix validation
Not possible on this guest (no mly hardware); fix is mechanical (termination + EBUSY drain gate) and cannot regress the non-error path.
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 not validated on the guest: the trigger path requires mly/age hardware absent from the audit guest. fix.diff is mechanical (bounds/termination-only) and authored post-verification against the cited lines.
findings/poc/DF-3931/fix.diff + VERDICT.md
Confirmed kernel references
Detail
Exploit chain
kldunload/detach racing the 1s periodic or an in-flight completion -> callout/completion task fires after mly_free -> UAF of softc, packets, DMA tags
Evidence (decisive lines)
findings/poc/DF-3931/VERDICT.md (incl. kern_timeout.c RESET-overrides-STOP citations); fix.diff (callout_terminate + EBUSY drain gate)
PoC changes
No seed PoC; trigger is kldunload/detach under load on real hardware.
Verified recommended fix
callout_terminate() the periodic and refuse shutdown/detach while commands are in flight.
Verdict
Hardware-gated + narrow race; confidence likely (all ingredients source-verified; mechanism class proven on this guest by DF-3715's replica). mly_periodic re-arms itself unconditionally (mly.c:1397); mly_shutdown stops it once with callout_stop (mly.c:530) and mly_free never touches it (mly.c:677-733). kern_timeout.c's own comments (840-846, 976-980) state a RESET requested by a racing callback 'automatically overrides a STOP', so a callback in flight when the stop lands can leave the callout ARMED after mly_shutdown returns; mly_detach then frees the softc/packets/DMA and the 1-second tick fires on freed memory (mly_btl walk, mly_rescan_btl -> alloc/map/start on destroyed queues). Compounding: no drain of mly_busy/mly_complete before free (driver's own XXX at mly.c:514-517), and no production timeout exists at all (MLY_CMD_TIMEOUT handling is #ifdef MLY_DEBUG only, mly.c:119/322-324/2971-2991; no XPT_ABORT support in mly_cam_action) so hung commands block safe detach indefinitely. Admin-gated (kldunload/detach/hot-remove); impact is post-free corruption. Not runtime-reproduced (hardware absent).
No comments yet.