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

OOB read+write in NCQ error recovery: device-controlled log-page tag indexes ap_ccbs without bounds check

Summary

ahci_port_read_ncq_error() at ahci.c:3486-3498: err_slot=log->err_regs.type&ATA_LOG_10H_TYPE_TAG_MASK (5-bit 0-31, device-controlled from READ LOG EXT page 10h). ccb2=&ap->ap_ccbs[err_slot] at :3490 OOB if err_slot>=sc_ncmds(4-32). If OOB ccb_xa.state==ATA_S_ONCHIP(5): memcpy 20 bytes device-controlled data into OOB rfis (:3493) + 2 byte writes. Caller at ahci.c:2804 also writes ccb->ccb_xa.state=ATA_S_ERROR to OOB. ahci.c:2791 only rejects err_slot<0, not >=ncmds. Worsens DHRS finding: includes write not just read. Malicious controller returns crafted NCQ error log page. Fix: bounds-check err_slot>=sc_ncmds before indexing.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1211 Β· 9 files
FileTypeDescriptionSize
VERDICT.md verdict full path:line trace + reachability analysis 3.5 KB ↓ raw
README.md readme claim, verdict, threat model, fix 1.9 KB ↓ raw
fix.diff suggested-fix bounds-check NCQ err_slot (log-page tag) vs sc_ncmds before ap_ccbs[] deref 571 B view raw
run.sh reachability-probe shell probe: is an AHCI controller / SATA disk attached? 571 B view raw
build.sh build-noop no userspace build (trigger is device DMA'd log page 10h) 279 B view raw
fix_build.log build-log GENERIC kernel build with the fix applied (rc=0; ahci.o/ahci.ko clean) 5.6 MB ↓ download
env.txt environment guest uname, cc, device topology 718 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
README.md readme claim, verdict, threat model, fix
↓ download raw

DF-1211 β€” OOB read+write in AHCI NCQ error recovery (device-controlled log-page tag)

Claim

ahci_port_read_ncq_error() (sys/dev/disk/ahci/ahci.c:3486-3498): err_slot = log->err_regs.type & ATA_LOG_10H_TYPE_TAG_MASK is a 5-bit (0-31) tag read from the device's READ LOG EXT page 10h (device-supplied, DMA'd into ap_err_scratch). ccb2 = &ap->ap_ccbs[err_slot] (ahci.c:3490) indexes ap_ccbs[] (sized for sc_ncmds, 4-32) with no bounds check. If the OOB ccb2->ccb_xa.state == ATA_S_ONCHIP, ahci.c:3495 does memcpy(&ccb2->ccb_xa.rfis, &log->err_regs, sizeof(struct ata_fis_d2h)) (20 bytes of device data into the OOB ccb) plus two 1-byte writes at ahci.c:3497-3498. The caller at ahci.c:2804-2808 then writes ap_ccbs[err_slot].ccb_xa.state = ATA_S_ERROR using the same bad slot. A malicious device returning tag >= sc_ncmds β‡’ OOB read + OOB write.

Verdict

NOT TESTABLE on this audit guest (real bug, traced line-by-line; fix authored and compile-validated into the GENERIC kernel). See VERDICT.md.

Why not reproduced here

  • No AHCI/SATA controller on the guest (pciconf -l shows none; virtio-blk only), so ahci(4) never attaches and ahci_port_read_ncq_error() is never called.
  • The malicious value comes from the ATA device's NCQ error log page (device-DMA'd), not from any userspace syscall; a benign device/QEMU model always returns a valid tag, so there is no unprivileged trigger.

Trigger (threat model)

A malicious or buggy SATA/NCQ device (or a malicious VM host's disk device model) returning a READ LOG EXT page 10h whose tag field >= the host's sc_ncmds. Same peripheral / malicious-device-model class as DF-1210.

Fix

fix.diff adds the bounds check err_slot < 0 || err_slot >= sc_ncmds after decoding the tag and before ap_ccbs[err_slot], routing a bad tag to the function's existing err: cleanup (which sets err_slot = -1, causing the caller to reset the port).

VERDICT.md verdict full path:line trace + reachability analysis
↓ download raw

DF-1211 β€” VERDICT

Finding: OOB read+write in AHCI NCQ error recovery: a device-controlled READ LOG EXT page-10h tag indexes ap_ccbs[] without a bounds check (sys/dev/disk/ahci/ahci.c). Status: NOT TESTABLE on this audit guest. Confidence (bug is real): certain. Impact ceiling: OOB array index β†’ 20-byte OOB write + 2 single-byte OOB writes of device-controlled data, plus a caller OOB write; device-controlled. Fix: authored in fix.diff, applied clean, compile-validated into GENERIC.

Mechanism (confirmed line-by-line in sys/)

  1. ahci_port_read_ncq_error() (ahci.c) is called from the TFES error path (ahci.c:2718) when NCQ commands (ap_sactive) were active.
  2. It issues a READ LOG EXT - 0x10, DMA'd into ap->ap_err_scratch (ahci.c:3480, log = (struct ata_log_address_10h *)ap->ap_err_scratch).
  3. ahci.c:3488 β€” err_slot = log->err_regs.type & ATA_LOG_10H_TYPE_TAG_MASK; The tag field is the low 5 bits (0-31) of a device-supplied byte.
  4. ahci.c:3490 β€” ccb2 = &ap->ap_ccbs[err_slot]; NO bounds check. ap_ccbs[] holds sc->sc_ncmds (4-32) entries.
  5. ahci.c:3491 β€” if (ccb2->ccb_xa.state == ATA_S_ONCHIP) reads the OOB ccb's state (OOB read).
  6. ahci.c:3495-3498 β€” on match: memcpy(&ccb2->ccb_xa.rfis, &log->err_regs, sizeof(struct ata_fis_d2h)) writes 20 bytes of device data into the OOB ccb's rfis, then rfis.type = ATA_FIS_TYPE_D2H; rfis.flags = 0; (two 1-byte OOB writes).
  7. Caller ahci.c:2805 β€” ccb = &ap->ap_ccbs[err_slot]; (same bad slot) and ahci.c:2807-2808 β€” KKASSERT(...); ccb->ccb_xa.state = ATA_S_ERROR; another OOB write.

The only mitigation today is that ap_ccbs[] is sometimes over-allocated relative to the real NCQ depth, so some out-of-range tags land in unused tail slots β€” but the array is exactly sc_ncmds long, so any tag in [sc_ncmds .. 31] is a genuine OOB.

Why it is NOT TESTABLE on this guest

  • No AHCI/SATA controller (pciconf -l clean; root disk is virtio-blk), so ahci(4) does not attach; the NCQ error-recovery path is dead code here.
  • The malicious tag originates in device firmware / a malicious disk device model (DMA into ap_err_scratch), not in any userspace syscall. A benign device always echoes a tag the host issued (< sc_ncmds).

Valid "device-controlled, not reachable from an unprivileged user on this guest" case. Bug is genuine (missing bounds check at the only tag-decode site); threat model = malicious/buggy NCQ device / malicious VM disk model.

Exploit chain

None developed: device-controlled primitive with no unprivileged syscall path on this guest (and no AHCI device at all here). Documented impact ceiling: OOB read + up to 22 bytes of OOB write (device-controlled content) per NCQ error.

Fix

fix.diff adds the bounds check right after the tag is decoded:

err_slot = log->err_regs.type & ATA_LOG_10H_TYPE_TAG_MASK;
if (err_slot < 0 || err_slot >= ap->ap_sc->sc_ncmds) {
    kprintf("%s: NCQ error log bad tag %d\n", PORTNAME(ap), err_slot);
    err_slot = -1;
    goto err;
}
ccb2 = &ap->ap_ccbs[err_slot];

err: (ahci.c:3507) is the function's existing cleanup label; it returns err_slot = -1, and the caller's err_slot < 0 handling (ahci.c:2791) then resets the port (goto failall). Minimal, targeted at the only missing-check site, symmetric with the TFES guard.

Build / run on this guest

./run.sh is a reachability probe; on this guest it reports "no AHCI controller β†’ NCQ error path unreachable". Bug confirmed by the trace above; fix compile-validated into GENERIC.

Fix verification

not_testable

compile validated -Werror

module/kernel build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ahci NCQ error recovery err_slot from device log page 10h no bounds -> 20B OOB write. No AHCI controller.