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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3931 Β· 5 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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)

  1. mly_periodic re-arms itself unconditionally at its tail: mly.c:1397 callout_reset(&sc->mly_periodic, MLY_PERIODIC_INTERVAL * hz, mly_periodic, sc);
  2. 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.
  3. 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_busy may 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).
  4. 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_testable
baseline no→ patch + rebuild →patched clean

Fix 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
↓ fix.diffper-fix-DF-3931

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).