# DF-2477 verdict -- md MOD_UNLOAD refcnt TOCTOU frees md_s under a racing mdopen

status: reproduced   reproduced: 1   impact: corruption
confidence: certain   attempts: 3   runtime_sec: 900

The verbatim md_drvcleanup (md.c:466-496) takes one unlocked atomic_fetchadd_int(&refcnt, 0) snapshot and then frees every md_s (kfree(sc->secp) :492, kfree(sc) :493) with no lock mdopen/mdclose hold (they take only sc->tok, which belongs to the freed object) and without clearing dev->si_drv1. The rig drives the exact interleaving: drvcleanup passes the refcnt==0 check, a racing verbatim mdopen completes at the drain boundary (rc=0, refcnt -> 1), then drvcleanup frees the md_s anyway. Result: refcnt==1 on freed memory, dev->si_drv1 dangling, and a read through the dangling pointer after a marker reclaim returns 0xc7c7c7c7 - use-after-free confirmed (the next mdopen/mdclose would take sc->tok on freed memory). md is compiled into the stock kernel (no unload at runtime here), so the trigger is module-config-specific, as filed. Fix v2 (destroy_dev drain first, then re-check refcnt, free only at 0) A/B: drvcleanup returns EBUSY, nothing freed, reclaim-read returns intact values (not the marker) - no UAF.

## Evidence (decisive lines)
DF2477: racing mdopen rc=0 refcnt=1 (open SUCCEEDED against an sc that was then freed)
DF2477: dev->si_drv1 was NEVER set to NULL after drvcleanup; read through the dangling pointer after reclaim: sc->unit=0xc7c7c7c7 sc->nsect=0xc7c7c7c7 (reclaimed marker 0xC7)
DF2477: RESULT=UAF CONFIRMED (freed md_s reclaimed by unrelated allocation; next mdopen/mdclose takes sc->tok on freed memory)
DF2477: log: mdcreate_malloc(auto-create) [after open+close refcnt=0] disk_destroy mdcreate_malloc(auto-create) [racing mdopen rc=0 refcnt=1] devstat_remove
--- fixed source A/B ---
DF2477: log: ... destroy_dev(drain) [racing mdopen rc=0 refcnt=1]
    (log ENDS here: EBUSY returned, nothing freed, sc intact - reclaim read returns non-marker values)




## Exploit chain / privesc assessment
root-config path (module unload while opening); privesc ceiling: use-after-free on privileged-triggered unload; no unpriv trigger.

## Fix + A/B
fix.diff drains the cdev (destroy_dev) and re-checks refcnt before any free; A/B on the same rig shows EBUSY and no free.


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