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)
PoC verification
Evidence pack
findings/poc/DF-1211 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 -lshows none; virtio-blk only), soahci(4)never attaches andahci_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).
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/)
ahci_port_read_ncq_error()(ahci.c) is called from the TFES error path (ahci.c:2718) when NCQ commands (ap_sactive) were active.- 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). 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.ahci.c:3490βccb2 = &ap->ap_ccbs[err_slot];NO bounds check.ap_ccbs[]holdssc->sc_ncmds(4-32) entries.ahci.c:3491βif (ccb2->ccb_xa.state == ATA_S_ONCHIP)reads the OOB ccb's state (OOB read).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, thenrfis.type = ATA_FIS_TYPE_D2H; rfis.flags = 0;(two 1-byte OOB writes).- Caller
ahci.c:2805βccb = &ap->ap_ccbs[err_slot];(same bad slot) andahci.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 -lclean; root disk is virtio-blk), soahci(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_testablecompile 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.
No comments yet.