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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4349 Β· 17 files
FileTypeDescriptionSize
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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Thu Oct 15 14:56:52 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC (both fixes) / #3 15:11 (4349-only)

Confirmed kernel references

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.