aac(4) dereferences adapter-controlled completion/AIF indices with no bounds validation at five sites β malicious or faulty controller gets wild kernel pointers and unlink writes
Summary
Indices taken from adapter MMIO/DMA are used unbounded: aac_new_intr cm = aac_commands + (index>>2); aac_complete cm = aac_commands + fib->Header.SenderData with a dead NULL check; aac_dequeue_fib AIF path &ac_fibs[aq_fib_addr/512] against the 8-slot array; the response path; aac_get_bus_info loops i<BusCount over 10-entry stack arrays. Compromised/faulty AAC controller firmware steers the completion machinery at a forged struct aac_command and gets TAILQ_REMOVE unlink writes, callout_stop, and an indirect cm_complete call at chosen kernel addresses - arbitrary-write ceiling; the BusCount site is an OOB stack read. Bounds-check fix at all five sites.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3820 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.0 KB | β raw | |
| fix.diff | β | 1.8 KB | view raw | |
| env.txt | β | 323 B | view raw | |
| verdict.json | β | 2.5 KB | view raw |
DF-3820 β aac(4) dereferences adapter-controlled FIB/queue indices with no bounds validation (malicious/errant controller β wild kernel pointers)
- File:
sys/dev/raid/aac/aac.c - Lines: 853, 1118, 2221-2223, 2238-2240, 3771-3772
- Class: CWE-129 (improper validation of array index) β OOB pointer formation β kernel memory read/write (device-trust)
- Reach: PCI device DMA / MMIO only (compromised or faulty controller firmware; no unprivileged-software path).
What to look at
No runnable PoC exists without the hardware: every affected value is produced by the adapter (MMIO read or DMA into the common block). The defect is proven by inspection at each site:
aac_new_intr()aac.c:853 βcm = sc->aac_commands + (index >> 2);indexis read straight fromAAC_GET_OUTB_QUEUE()(MMIO). No check againstsc->total_fibs/aac_max_fibs. The subsequentaac_remove_busy(cm),aac_unmap_command(cm),cm->cm_complete(cm)operate on an arbitrary out-of-arraystruct aac_command *.aac_complete()aac.c:1118 βcm = sc->aac_commands + fib->Header.SenderData;βSenderDatalives in the DMA'd FIB the adapter filled. Theif (cm == NULL)guard at :1119 is dead (array offset can never be NULL); there is no range check.aac_dequeue_fib()aac.c:2221-2223 β AIF path:fib_index = aq_fib_addr / sizeof(struct aac_fib); *fib_addr = &sc->aac_common->ac_fibs[fib_index];βaq_fib_addris a 32-bit queue entry the adapter writes;ac_fibshas exactlyAAC_ADAPTER_FIBS(8) slots (aacvar.h:71,217). A 32-bit index up to 0x1FFFFFF yields a pointer ~4 GB past the common block β the AIF is then copied/processed from arbitrary kernel memory.aac_dequeue_fib()aac.c:2238-2240 β response path:cm = sc->aac_commands + (fib_index >> 2); *fib_addr = cm->cm_fib;β same:fib_indexis the adapter-writtenaq_fib_addr, unbounded;cm->cm_fibdereferences the OOB command slot.aac_get_bus_info()aac.c:3771-3772 βfor (i = 0; i < businfo.BusCount; i++) { ... businfo.BusValid[i] ... businfo.InitiatorBusId[i] }βBusCountis an adapter-supplied u32; both arrays haveAAC_SCSI_MAX_PORTS(10) entries (aacreg.h:struct aac_getbusinf). OOB stack read of the localbusinfocopy, up to a wild read whenBusCountis huge.
Impact ceiling
A compromised/faulty AAC controller (or anything able to DMA on its
behalf) turns each site into a controlled kernel write: e.g. pick
SenderData so that the bogus cm aliases chosen memory, then the
completion path performs callout_stop, TAILQ_REMOVE (unlink writes)
and an indirect call through cm->cm_complete. Severity capped at Low
because the precondition is hardware/firmware compromise.
Verdict
inconclusive β no AAC HBA exists on the audit guest
(see ../DF-3819/env.txt); the code paths are reachable only from adapter
DMA/MMIO. Source-level: certain.
VERDICT β DF-3820
Status: inconclusive (device-gated: no AAC controller on the audit guest; nothing can execute these paths without adapter DMA/MMIO). Source-level confidence: certain β every site is a plain, unguarded array-index-to-pointer computation on adapter-supplied data.
The five sites
| # | site | source of index | target object | bound that should hold |
|---|---|---|---|---|
| 1 | aac.c:853 (aac_new_intr) |
AAC_GET_OUTB_QUEUE() MMIO |
sc->aac_commands[] (total_fibs β€ 512/256) |
index >> 2 < sc->total_fibs |
| 2 | aac.c:1118 (aac_complete) |
fib->Header.SenderData (DMA) |
sc->aac_commands[] |
SenderData < sc->total_fibs |
| 3 | aac.c:2221-2223 (aac_dequeue_fib AIF) |
aq_fib_addr (adapter-written queue entry) |
sc->aac_common->ac_fibs[8] |
fib_index < AAC_ADAPTER_FIBS |
| 4 | aac.c:2238-2240 (aac_dequeue_fib resp) |
same | sc->aac_commands[] |
fib_index >> 2 < total_fibs |
| 5 | aac.c:3771-3772 (aac_get_bus_info) |
BusCount (LCT/DMA-derived reply) |
stack businfo.BusValid[10], InitiatorBusId[10] |
BusCount β€ AAC_SCSI_MAX_PORTS |
Not one of them validates. The driver comment at aac.c:2232-2237 even acknowledges the value "was originally provided by the driver" β but a malicious or buggy adapter is free to return anything.
Why these matter even though they are device-gated
Modern threat models treat PCIe devices as untrusted once firmware
compromise or a Thunderbolt-style attach is in play; DragonFly's own
hardening push (IOMMU-less boards included) treats device-driven OOB as a
finding class. Sites 1-4 hand the attacker a fully controlled fake
struct aac_command location (via the 32-bit index scaling) which the
completion machinery then writes through (aac_remove_busy β
TAILQ_REMOVE unlink writes; callout_stop; indirect call
cm->cm_complete(cm)), i.e. arbitrary-write primitives, not just panics.
The upstream FreeBSD ancestor has the same pattern; this finding is filed as shared-hardening.
Guest evidence
The audit guest has no AAC hardware whatsoever:
ls /dev/aacd0β ENOENT;pciconf -l | grep -c "vendor=0x(1044|9005|9004|1028)"β 0 (copied as ../DF-3819/env.txt, identical run).- aac is compiled into X86_64_GENERIC but never attaches.
No kernel path can be driven without the card β inconclusive, honest
per the audit's status set.
Fix
fix.diff adds the five missing bounds checks (print + drop/break), each
against the correct limit: sc->total_fibs (Γ3), AAC_ADAPTER_FIBS,
AAC_SCSI_MAX_PORTS.
Fix validation
not_testable β same hardware precondition as the baseline.
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_testableBaseline and patched runs both require an attached AAC controller; absent on the audit guest, so the fix cannot be exercised. Diff compiles-by-inspection against the current tree.
fix.diff
Confirmed kernel references
Detail
Exploit chain
device-side: craft completion index -> fake struct aac_command at chosen kernel offset -> aac_remove_busy()/aac_unmap_command()/cm->cm_complete(cm) write through it -> kernel code execution. Precondition: compromised/faulty PCIe controller firmware.
Evidence (decisive lines)
VERDICT.md table of the five sites with exact bounds that are missing; ../DF-3819/env.txt for guest hardware absence; fix.diff adding all five checks
PoC changes
No trigger exists for device-DMA-only paths; pack is analysis + fix only.
Verified recommended fix
Bounds-check every adapter-supplied index before pointer formation (see fix.diff: total_fibs, AAC_ADAPTER_FIBS, AAC_SCSI_MAX_PORTS).
Verdict
Five adapter-controlled indices are turned into kernel pointers with no bounds validation: aac.c:853 (index>>2 into aac_commands from MMIO outbound queue), aac.c:1118 (SenderData from DMA'd FIB into aac_commands, dead NULL check), aac.c:2221-2223 (aq_fib_addr/512 into the 8-slot ac_fibs[]), aac.c:2238-2240 (aq_fib_addr>>2 into aac_commands), aac.c:3771-3772 (BusCount over the 10-entry stack BusValid/InitiatorBusId arrays). A malicious or faulty AAC controller gets controlled wild-pointer derefs and unlink-style writes through the fake aac_command (aac_remove_busy TAILQ_REMOVE, callout_stop, cm_complete indirect call). Cannot be exercised: guest has no AAC HBA (no /dev/aacd0, zero matching PCI vendor IDs, driver compiled into kernel but never attaches).
No comments yet.