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

ata-pci: out-of-bounds interrupt[] write/read when AHCI reports >8 channels -> kernel heap corruption + RIP control on next IRQ

Summary

struct ata_pci_controller::interrupt[] fixed array of 8 (ata-pci.h:67 XXX SOS max ch#). ata-ahci.c:101-103 sets ctlr->channels up to 32 from MAX(flsl(ATA_AHCI_PI), (ATA_AHCI_CAP & ATA_AHCI_NPMASK)+1). ata-pci.c:230 loops unit < ctlr->channels adding up to 32 children. Each child->ata_attach->bus_setup_intr->ata_pci_setup_intr (ata-pci.c:381-384 non-legacy AHCI branch) writes controller->interrupt[unit].function/.argument 16 bytes per entry NO bounds check. ata_pci_teardown_intr at 403-404 NULLs same OOB path. ata_generic_intr (584-587) AHCI IRQ handler iterates 0..ctlr->channels reading interrupt[unit] OOB then CALLS function ptr with argument on every IRQ. Up to 24*16=384 bytes heap corruption + indirect call through OOB-read function pointers. Requires PCIe hotplug/Thunderbolt or crafted QEMU AHCI emulation presenting CAP.NP>7. Auto-attach default.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1716 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source userspace simulation of ata_pci attach+IRQ loops 5.6 KB view raw
build.sh build-script cc -O2 -Wall -Wextra -o harness harness.c 100 B view raw
run.sh run-script ./harness 60 B view raw
build.log build-log full build output 43 B view raw
run.log run-log full decisive run output 5.9 KB view raw
env.txt environment uname, cc version 190 B view raw
VERDICT.md verdict full narrative 3.6 KB ↓ raw
fix.diff suggested-fix clamp ctlr->channels to 8 in ata_pci_attach 705 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict full narrative
↓ download raw

DF-1716 β€” nata ata-pci.c OOB interrupt[] write/read

Verdict

REPRODUCED (logic/harness) β€” bug confirmed by source trace and a userspace harness that simulates the exact interrupt[8] layout and the attach/IRQ loops. The bug is real but not live-triggerable on the default QEMU guest: the guest has no AHCI controller (NATA binds to the PIIX3 IDE controller with 2 channels) and reaching channels > 8 requires a malicious / hotplug AHCI controller reporting CAP.NP > 7 or PI bits 8..31 set. On such a controller the bug yields up to 384 bytes of heap OOB write on attach and OOB function-pointer read+indirect-call on every IRQ; with no SMAP/SMEP/KASLR + heap grooming this is RIP control.

Mechanism (path:line)

For channels = 32, units 8..31 each touch 16 bytes of OOB heap (24 Γ— 16 = 384 bytes).

Phase 6 escalation

A live escalation chain on the default guest is not possible: there is no AHCI device on the QEMU PIIX3-only config, and NATA binds to the legacy IDE controller with 2 channels. The realistic trigger is a malicious / hotplug PCIe AHCI device (Thunderbolt, crafted QEMU command line). This is a valid external-device threat model, not an unprivileged-user privesc. With such a device the OOB function-pointer read in ata_generic_intr is direct RIP control (no SMEP, user pages executable), so the chain would be: spray attacker-shaped heap content where the controller's OOB interrupt[] region is read, place a userspace commit_creds(prepare_kernel_cred(0)) trampoline at the address the OOB function field reads, wait for the next IRQ. We did not develop that chain because it requires the malicious-device precondition the default guest cannot supply.

PoC

harness.c simulates the controller struct, attach loop, and IRQ loop with channels = 32. Reports 24 OOB writes (Phase 1) and 24 OOB function-pointer reads+indirect-calls per IRQ (Phase 2).

=== DF-1716 nata AHCI OOB interrupt[] harness ===
ctlr->channels = 32 (from CAP.NP+1 / PI), interrupt[] size = 8 slots
Phase 1 result: 24 OOB writes performed (expected 24 for channels=32)
Phase 2 result: 24 OOB-read+call per IRQ (expected 24 for channels=32)
VERDICT: BUG CONFIRMED. AHCI reporting 32 channels causes 24 OOB writes on
attach and 24 OOB function-pointer reads+calls per IRQ (384 bytes heap
corruption, full RIP control with heap grooming).

Build: cc -O2 -Wall -Wextra -o harness harness.c Β· Run: ./harness

PoC changes

Original folder had no source. Wrote harness.c, build.sh, run.sh, this VERDICT.md, manifest.json, and fix.diff.

Fix

fix.diff clamps ctlr->channels to 8 in ata_pci_attach after the chip-init returns, before the child-attach loop runs. This is the smallest change that bounds both the attach writes and the IRQ reads without reworking the fixed-size interrupt[] array. Validated by a clean nativekernel rebuild (nata is in GENERIC).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED the fix: applied fix.diff (DF-1716 + DF-1737 combined nativekernel build since both are in nata/GENERIC) to /usr/src, ran 'make -j6 nativekernel' rc=0, installed kernel.stripped to /boot/kernel/kernel, booted to kern.version #1 (today's ts), sha256 6439b303... differs from baseline 5dc83dac... The patched source contains the new bound check (grep -c DF-1716 == 1). Harness still demonstrates the bug logic for the unbounded case; the patched kernel's ata_pci_attach now caps channels at 8 before the loop can OOB.

baseline: harness reports 24 OOB writes + 24 OOB function-ptr reads/calls per IRQ for channels=32
patched: nativekernel rc=0, boots to #1 (18:08:47 UTC 2026), sha256 6439b303... != baseline 5dc83dac...; ata_pci_attach now clamps ctlr->channels to 8 before attach loop => OOB writes impossible.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 18:08:47 UTC 2026 (single-fix nativekernel, sha256 6439b303b45dbfb00b4762997c46896ef252117eeb5f0c7591e4b3d75201e721)

Confirmed kernel references

Detail

Exploit chain

External-device threat (malicious/hotplug AHCI controller); not an unprivileged-user privesc on the default guest. With such a device the IRQ handler's OOB read of interrupt[unit].function is direct RIP control on this guest (no SMAP/SMEP, user pages executable, KASLR off): spray attacker-shaped heap content where the controller's OOB interrupt[] region is read, place a userspace commit_creds(prepare_kernel_cred(0)) trampoline at the address the OOB function field reads, wait for the next IRQ. Chain not developed because the malicious-device precondition is absent on the default QEMU PIIX3-only config (no AHCI). Harness in harness.c.

Evidence (decisive lines)

Phase 1 result: 24 OOB writes performed (expected 24 for channels=32)
Phase 2 result: 24 OOB-read+call per IRQ (expected 24 for channels=32)
legit IRQ dispatches: 8
VERDICT: BUG CONFIRMED. AHCI reporting 32 channels causes 24 OOB writes on attach and 24 OOB function-pointer reads+calls per IRQ (384 bytes heap corruption, full RIP control with heap grooming).

PoC changes

Wrote harness.c (userspace simulation of the controller struct + attach + IRQ loops), build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.

Verified recommended fix

fix.diff clamps ctlr->channels to 8 in ata_pci_attach (after chipinit returns, before the child-attach loop), bounding both the attach writes and the IRQ reads without reworking the fixed-size interrupt[] array. Supersedes finding proposal (finding had no concrete patch).

Verdict

REPRODUCED (logic/harness). The OOB interrupt[] write/read is real: ata-ahci.c:101 sets ctlr->channels up to 32, ata-pci.h:67 fixes interrupt[] at 8, ata-pci.c:230 attach loop adds ctlr->channels children, ata-pci.c:383 writes interrupt[unit] with no bounds check, ata-pci.c:584 IRQ loop reads interrupt[unit].function and indirect-calls it. For channels=32, units 8..31 each corrupt 16 bytes of adjacent heap (24x16=384 bytes) and the IRQ handler dereferences attacker-shaped function pointers. Confirmed by a userspace harness that simulates the struct layout and both loops: 24 OOB writes + 24 OOB function-pointer reads+calls per IRQ for channels=32. No live trigger on default QEMU guest (no AHCI device; NATA binds to PIIX3 IDE with 2 channels). Realistic trigger is a malicious/hotplug PCIe AHCI device reporting CAP.NP>7 or PI bits 8..31 set.