adv_run_doneq completes ccb_infos entries without ACCB_ACTIVE validation: in-bounds stale-slot double completion (double xpt_done/LIST_REMOVE/recycled-dmamap unload) and never-allocated-slot write through uninitialized cinfo->ccb
Summary
The firmware completion walk maps the raw 32-bit LRAM ccb_index to a kernel object with no allocation or state check. Facet A: an index naming an already-completed (FREE) ccb_info double-completes a stale CCB (LIST_REMOVE of a detached entry, stale callout_stop, recycled-dmamap unload, second xpt_done). Facet B: ccb_infos is kmalloc'd WITHOUT M_ZERO and slots are initialized lazily, so an in-bounds-but-never-allocated index writes through garbage (the :1262 comment shows the hazard was known). DF-2413 covers only the array-bounds half. Guest model: double-completion + wild write proven; fix predicate drops both. Validate+M_ZERO fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4349 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df43xx_rig.c | β | 41.5 KB | view raw | |
| advlib.h | β | 25.4 KB | view raw | |
| Makefile | β | 117 B | β download | |
| build.sh | β | 96 B | view raw | |
| prime.sh | β | 1.1 KB | view raw | |
| run.sh | β | 177 B | view raw | |
| build_fixed.sh | β | 1.1 KB | view raw | |
| run.baseline.log | β | 17.6 KB | view raw | |
| panic.facetA.txt | β | 937 B | view raw | |
| panic.facetB.txt | β | 1.3 KB | view raw | |
| run.forensic.log | β | 3.2 KB | view raw | |
| run.bothfixed.log | β | 34.3 KB | view raw | |
| fix_run.log | β | 20.1 KB | view raw | |
| fix_build.log | β | 1.3 KB | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| env.txt | β | 1.1 KB | view raw | |
| verdict.json | β | 6.5 KB | view raw |
Fix verification
fixedfix.diff (predicate + M_ZERO) applied in guest /usr/src with DF-4350's budget fix, nativekernel+installkernel, reboot. Baseline kernel: go=2 panic (double adv_done), go=3 trap 12 at 0x41df41df42f3. Patched kernel: go=2 -> 'adv9: done queue ccb_index 0 not active, dropping completion' (guest alive), go=3 dropped, go=1 control completion still correct, go=4 cycle bounded (276-entry budget). Identical PoC bytes both runs.
run.bothfixed.log: patched-kernel boot (#2), scenario matrix RC=0 with 'dropping completion' messages and 'done queue walk exceeded 276 entries, stopping' then 'adv_intr RETURNED after 4273 data-port services'; panic.facetA.txt / panic.facetB.txt for the baseline reproductions
Confirmed kernel references
- sys/dev/disk/advansys/advansys.c:1106
- sys/dev/disk/advansys/advansys.c:1108
- sys/dev/disk/advansys/advansys.c:1109
- sys/dev/disk/advansys/advansys.c:1127
- sys/dev/disk/advansys/advansys.c:1261
- sys/dev/disk/advansys/advansys.c:1266
- sys/dev/disk/advansys/advansys.c:1296
- sys/dev/disk/advansys/advansys.c:642
- sys/dev/disk/advansys/advlib.c:808
Detail
Exploit chain
Crafted device done-queue entry (duplicated ccb_index, or in-bounds index >= ccb_infos_allocated) -> production adv_run_doneq maps it to a kernel object with no allocation/state check (advansys.c:1106-1109) -> facet A: second adv_done on a stale ccb = stale LIST_REMOVE (kernel list corruption; deterministic panic on INVARIANTS kernels), callout_stop/dmamap_unload on recycled live resources, second xpt_done (CAM UAF family); facet B: write through uninitialized cinfo->ccb (kernel-memory write at a heap-groomed address, demonstrated fault at 0x41df41df42f3). Impact ceiling on this rig: kernel panic from device-fed input; escalation to controlled kernel write requires physical AdvanSys HBA + firmware/hostile-peripheral control of the done-queue bytes.
Evidence (decisive lines)
panic.facetA.txt (double adv_done backtrace), panic.facetB.txt (trap 12 at 0x41df41df42f3, supervisor write, stopped at adv_intr+0x101 movl %edi,0x114(%rsi)), run.baseline.log (self-test PASS + control completion + facetA panic), run.forensic.log (recycled-slot silent enumeration + stale-variant guard panic), run.bothfixed.log (patched kernel: drops, no panic, budget-bounded cycle), build_fixed.sh + fix.diff
PoC changes
Replaced the seed userspace model with a production-handler KLD dispatch rig: fabricated post-attach softc with real bus_dma tag/dmamaps, production-exact ccb_infos kmalloc (M_DEVBUF, no M_ZERO) with deterministic heap grooming, ASC chip emulation (flat register page + LRAM latch/auto-increment protocol via x86 #DB watchpoints with a temporary IDT[1] gate), nm-resolved dispatch of the kernel's own adv_intr/adv_done/adv_copy_lram_doneq, sysctl-driven scenarios with control/selftest/forensic isolation, and real rebuilt-kernel fix validation.
Verified recommended fix
In adv_run_doneq reject ccb_index >= adv->ccb_infos_allocated or !(state & ACCB_ACTIVE) before completing (re-advance the walk and continue); kmalloc ccb_infos with M_ZERO as defense in depth.
Verdict
REPRODUCED against the shipped kernel binary via production-handler KLD dispatch: advansys.c is compiled into /boot/kernel/kernel, so the rig dispatches the real adv_intr()/adv_done() (nm-resolved, no KASLR) over a fabricated post-attach softc (real busdma, production non-zeroed ccb_infos kmalloc) with the ASC LRAM emulated byte-exactly (x86 watchpoint #DB service; self-test proves the production accessors round-trip every crafted field). Facet A: a duplicated done-queue entry naming an already-completed slot drives a second production adv_done - panic 'Bad link elm prev->next != elm' with a double adv_done frame under adv_intr (the stale LIST_REMOVE at advansys.c:1127; the INVARIANTS queue guard converts the filed silent list corruption into a deterministic kernel crash on this debug kernel, and the recycled-slot variant still corrupts silently: live callout stopped, live dmamap unloaded, slot double-returned, early completion -> double-xpt_done family). Facet B: a done entry naming an in-bounds never-allocated slot writes through groomed heap garbage - Fatal trap 12, fault address exactly 0x41df41df42f3, supervisor WRITE, stopped at adv_intr+0x101: movl %edi,0x114(%rsi), which is the ccb->csio.resid store of advansys.c:1108. Control run completes a legit entry cleanly, so only the crafted device bytes differ. Fix validated on a really rebuilt kernel (fix.diff applied in guest /usr/src, nativekernel+installkernel): both facets dropped, no panic, control still works, guest alive. Device-fed trigger (buggy/hostile firmware or corrupt LRAM) - no unprivileged-user or remote path; Medium severity confirmed.
No comments yet.