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

ata_ahci_reset() dereferences ch->dma unconditionally: NULL-pointer panic when ata_dmaalloc() failed at channel attach

Summary

ch->dma->work_bus is read with no NULL guard; ata_dmaalloc failure frees ch->dma and NULLs it but attach continues, and ata_pcichannel_reset calls ctlr->reset unguarded (while the adjacent DMA reset in the same function IS guarded). NULL+offset panic at attach/reinit/hot-plug under DMA memory exhaustion. Guard-after-port-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4809 Β· 4 files
FileTypeDescriptionSize
README.md file 3.0 KB ↓ raw
VERDICT.md file 2.0 KB ↓ raw
fix.diff file 452 B view raw
verdict.json file 2.0 KB view raw
README.md file
↓ download raw

DF-4809 β€” ata_ahci_reset() NULL ch->dma dereference when DMA workspace alloc failed

File: sys/dev/disk/nata/chipsets/ata-ahci.c (pass 2) Severity: Low Β· CWE-476 (NULL pointer dereference) Β· confidence: likely (class: robustness / boot-time panic; not the DF-1891/DF-2510 unbounded-channels family)

Build / run

Not applicable β€” this is a static control-flow finding. There is no buildable, runnable PoC: the trigger is ata_dmaalloc() failure (bus_dmamem_alloc / bus_dma_tag_create failing for the 4 KB sg + 8 KB work DMA areas) during ATA channel attach, i.e. DMA-coherent memory exhaustion at device attach time. Verification on the QEMU guest was therefore not attempted (Phase V bar not met: not Critical/High, not memcorrupt/privesc, no trivial local program).

The path (all citations from this tree)

  1. ata_pcichannel_attach() β€” sys/dev/disk/nata/ata-pci.c:790-793: ctlr->dmainit(dev) then if (ch->dma) ch->dma->alloc(dev). For AHCI, dmainit = ata_ahci_dmainit (set at ata-ahci.c:106), alloc = ata_dmaalloc (set at ata-dma.c:69).
  2. ata_dmaalloc() β€” sys/dev/disk/nata/ata-dma.c:90-154: any tag-create / dmamem-alloc / map-load failure jumps to error: (line 149) which prints "WARNING - DMA allocation failed, disabling DMA", kfree(ch->dma), and sets ch->dma = NULL (ata-dma.c:152-153). Attach continues β€” it does not abort the channel.
  3. ata_attach() β€” sys/dev/disk/nata/ata-all.c:111 (body around 126-130): ATA_RESET(dev) is called unconditionally for every channel.
  4. ATA_RESET β†’ ata_pcichannel_reset() β€” sys/dev/disk/nata/ata-pci.c:841-851: the DMA-engine reset is guarded by if (ch->dma) (842-847), but ctlr->reset(dev) (850-851) is called with no guard.
  5. For AHCI, ctlr->reset = ata_ahci_reset (set at ata-ahci.c:105). ata_ahci_reset() β€” sys/dev/disk/nata/chipsets/ata-ahci.c:466-469: work = ch->dma->work_bus + ATA_AHCI_CL_OFFSET; β€” unguarded NULL deref (offset deref of a NULL struct ata_dma *) β†’ kernel panic.

The same reset is also reached on resume / channel reinit (ata_reinit, ata-all.c:187 β†’ ATA_RESET), so the NULL must also survive any later re-init after a failed alloc.

Sibling code is uniformly guarded: ata-pci.c:791 (if (ch->dma) alloc), ata-pci.c:811 (if (ch->dma) free in detach), ata-pci.c:842 (if (ch->dma) in this very reset function) β€” the AHCI reset's unconditional ch->dma->work_bus is the outlier.

Expected behavior if triggered

Fatal trap 12: page fault in supervisor mode while accessing (struct ata_dma *)NULL + offsetof(work_bus) during device attach / reinit β€” boot-time or hot-plug-attach denial of service. No privilege impact; the written value never reaches the fault (the fault is the first access).

Fix

Guard the work-area setup (see fix.diff):

if (ch->dma == NULL || ch->dma->work_bus == 0)
    return;

placed after the port-implemented check (ata-ahci.c:461-464), mirroring the if (ch->dma) guards used by every sibling path.

VERDICT.md file
↓ download raw

DF-4809 β€” VERDICT

Status: untested (static-analysis finding; Phase V not applicable β€” no runnable PoC exists for the trigger condition on the QEMU guest).

Reproduced?

No, and deliberately not attempted. Triggering requires ata_dmaalloc() (ata-dma.c:90-154) to fail for an AHCI channel at attach time, which requires bus_dmamem_alloc/bus_dma_tag_create failure for 12 KB of DMA-coherent memory β€” i.e. DMA-mappable memory exhaustion during device enumeration. There is no unprivileged (or any) syscall path to force this from a running system; it is a boot/hot-plug-time robustness defect. Per the audit contract (Low/Info findings do not get guest verification) the QEMU guest was not used for this finding.

Why I am confident the bug is real anyway

The code path is fully static and unconditional:

  • ch->dma = NULL is the documented failure exit of ata_dmaalloc (ata-dma.c:152-153) and attach proceeds afterwards.
  • ata_attach β†’ ATA_RESET(dev) β†’ ata_pcichannel_reset calls ctlr->reset(dev) with no NULL guard (ata-pci.c:850-851) while guarding the adjacent DMA reset with if (ch->dma) (ata-pci.c:842).
  • ata_ahci_reset immediately does ch->dma->work_bus (ata-ahci.c:467).

Impact ceiling: panic at attach/reinit (CWE-476). Not memory corruption, no attacker-controlled data is involved; severity Low, confidence "likely" (path certain; trigger constrained to resource exhaustion).

One-line guard in ata_ahci_reset (see fix.diff in this directory; authored against the read-only sys/ tree, never applied):

if (ch->dma == NULL || ch->dma->work_bus == 0)
    return;

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.diff adds the same 'if (ch->dma == NULL) return;' guard used by sibling paths; not build-validated because the trigger condition (DMA alloc failure) cannot be produced on the guest

findings/poc/DF-4809/fix.diff
↓ fix.diffper-fix-DF-4809

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4809/VERDICT.md (full path trace with path:line citations); findings/poc/DF-4809/fix.diff (one-line guard)

PoC changes

not applicable (no seed PoC; finding produced by pass-2 static analysis)

Verified recommended fix

Guard ata_ahci_reset work-area setup with 'if (ch->dma == NULL || ch->dma->work_bus == 0) return;' after the port-implemented check

Verdict

Static NULL-deref, not guest-verified (no runnable trigger): ata_dmaalloc() failure leaves ch->dma == NULL (ata-dma.c:152-153) but channel attach continues; ata_attach -> ATA_RESET -> ata_pcichannel_reset (ata-pci.c:850-851) invokes ctlr->reset with no NULL guard, and ata_ahci_reset immediately dereferences ch->dma->work_bus (ata-ahci.c:466-469) -> attach/reinit-time kernel panic (CWE-476) under DMA-coherent memory exhaustion. Every sibling path guards with 'if (ch->dma)' (ata-pci.c:791, 811, 842). Severity Low: boot/hot-plug DoS only, no attacker-controlled data.