iir_intr: unvalidated controller completion index reads/writes up to 2 gdt_ccb entries past sc_gccbs[124] (OOB dispatch, SLIST corruption, wild gc_ccb/gc_ucmd derefs)
Summary
ctx.istatus is the raw doorbell byte with only bit 0x80 stripped, ranging [0,127]; the special cases cover only 0/1, then gccb = &sc_gccbs[istatus - 2] against a 124-entry array. A device-fed 126/127 indexes 124/125 - up to ~780 bytes past the ~47.6KB block - and the OOB gc_flags dispatches into gdt_free_ccb (SLIST_REMOVE off-walk + OOB flags write), gdt_sync_event (wild gc_ccb callout_stop/xpt_done), or the IOCTL completion (wild gc_ucmd status writes and up to 3KB bcopy). Malicious/buggy Intel/ICP RAID controller or PCIe peer driving the DPMEM doorbell. Range-check fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4172 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| gate_check.sh | β | 779 B | view raw | |
| gate_check.log | β | 577 B | view raw | |
| fix.diff | β | 462 B | view raw | |
| manifest.json | β | 904 B | view raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-4172 β iir(4) iir_intr: unvalidated controller completion index β OOB gdt_ccb access
Build / Run
There is no userland trigger for this finding: ctx.istatus is a byte read
directly from the controller's doorbell register (gdt_mpr_get_status,
sys/dev/raid/iir/iir_pci.c:402-407) and delivered to iir_intr
(sys/dev/raid/iir/iir.c:1522) from the bus interrupt / polling paths. The only
"trigger" is a controller (or a PCIe peer with the DPMEM window mapped) that
delivers a completion index of 126 or 127.
To document the hardware gate on the audit guest, run:
sh gate_check.sh # shows: no /dev/iir, no 0x1119/0x8086:0600 PCI device, iir never attached
Expected on hardware-less guest: no iir/isp PCI device present, ls: /dev/iir*: No such file or directory.
Expected on a machine with an Intel/ICP iir RAID controller whose firmware (or
an attacker with bus-master access) writes an out-of-range completion index:
iir_intr indexes sc_gccbs[istatus-2] up to 2 entries (~780 bytes) past the
124-entry array β OOB gc_flags read β either
gdt_free_ccb() SLIST corruption / NULL-deref (iir.c:661),
gdt_sync_event() dereferencing a garbage gc_ccb (iir.c:1715, 1804), or the
GDT_GCF_IOCTL completion writing through a garbage gc_ucmd (iir.c:1600-1632).
Fix
fix.diff adds the missing range check (istatus must be in
[2, GDT_MAXCMDS+1]) before the array access.
DF-4172 VERDICT β unvalidated device-fed completion index in iir_intr
Status: untested on this guest (hardware-gated). Bug existence: certain from code trace.
Reproduced?
No runtime reproduction was possible: the DragonFly audit guest
(DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 β¦ X86_64_GENERIC)
has no Intel/ICP RAID controller β gate_check.log shows /dev/iir absent,
no PCI device with vendor 0x1119 / Intel 0x0600, and the only CAM SIMs are
QEMU's ata/ahci. iir_intr cannot be entered without the card, and even
with the card the malformed value must be written by the device, not by any
syscall. The gate itself is proven by gate_check.log.
Code trace (why the bug is certain)
gdt_mpr_get_status()returns the rawGDT_MPR_EDOORdoorbell byte (sys/dev/raid/iir/iir_pci.c:402-407). This is device-controlled memory-mapped state.gdt_mpr_intr()(iir_pci.c:410-449) clears only bit 0x80 (ctx->istatus &= ~0x80, line 418-419) β the remaining value is fully device-chosen, range [0, 127].-
iir_intr()(iir.c:1522-1645) special-cases onlyGDT_ASYNCINDEX(0) andGDT_SPEZINDEX(1) (iir.c:1562, 1568) and then, with no range check:gccb = &gdt->sc_gccbs[ctx.istatus - 2]; / iir.c:1580 /
-
sc_gccbsiskmalloc(sizeof(struct gdt_ccb) * GDT_MAXCMDS)withGDT_MAXCMDS == 124(iir.c:250-251, iir.h:77). Driver-generated command indices arei + 2for i β [0,123] β valid istatus β [2,125] (iir.c:256-258). A device-fed istatus of 126 or 127 yields array index 124/125 β up to 2 Γ ~390 bytes past the end of a ~47.6 KB kmalloc block. - Consequences on the OOB
gccb: -gc_flagsis read OOB (iir.c:1583). - If it reads asGDT_GCF_UNUSED(0):gdt_free_ccb()runsSLIST_REMOVEof a non-list-member (iir.c:661) β the SLIST walk runs off the end (NULL deref) or corrupts the free list, and writesgc_flags = 0OOB (iir.c:660). - If it reads asGDT_GCF_IOCTL(4):ucmd = gccb->gc_ucmd(OOB pointer, iir.c:1600) is written through (ucmd->status/info/complete_flag, iir.c:1608-1610) and up toparam_size(β€3072) bytes arebcopyed toucmd->data(iir.c:1613-1627) β wild kernel write. - Otherwise (default, iir.c:1637-1639):gdt_sync_eventdereferences the garbagegccb->gc_ccbβcallout_stop(ccb->ccb_h.timeout_ch)(iir.c:1804) andxpt_done(ccb)(iir.c:1859) on a wild pointer.
Why not false-positive
The two-element overflow window is small but real, and the driver itself
documents (via gc_cmd_index = i+2) that indices up to 125 are the only legal
ones β there is simply no enforcement at the consumption site. FreeBSD's
descendant of this code (gd_th) gained exactly this check.
Impact ceiling
Malicious/compromised RAID controller or PCIe peer β kernel memory corruption in interrupt context (list corruption + wild writes). Not reachable by unprivileged or even privileged software alone. Severity Low per the audit rubric ("requires specific hardware"); bucket: memcorrupt.
Fix validation
fix.diff authored post-trace; git apply --check passes. A kernel
build+boot validation is not possible on this guest (no hardware to trigger
the corrected path); fix_status = not_testable.
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_testablefix.diff adds the missing bounds check and passes git apply --check; cannot be runtime-validated without iir hardware
findings/poc/DF-4172/fix.diff (git apply --check clean)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full path:line trace); gate_check.log (hardware absent); fix.diff (range check, git apply --check clean)
PoC changes
no PoC source written: the trigger is device-fed interrupt state, unreachable from userland; only a hardware gate check was run
Verified recommended fix
Range-check ctx.istatus to [2, GDT_MAXCMDS+1] before indexing sc_gccbs in iir_intr (see fix.diff)
Verdict
Bug certain from line-accurate code trace, but not runtime-testable on this guest: iir_intr indexes sc_gccbs[ctx.istatus-2] (iir.c:1580) with a device-fed doorbell byte that is never range-checked (only bit 0x80 stripped, iir_pci.c:418), so a controller returning 126/127 reads/writes up to 2 gdt_ccb entries (~780 bytes) past the 124-entry kmalloc array - OOB gc_flags dispatch into gdt_free_ccb SLIST corruption (iir.c:661), wild gc_ccb deref in gdt_sync_event (iir.c:1715/1804), or wild gc_ucmd writes in the GDT_GCF_IOCTL completion (iir.c:1600-1632). Guest has no iir controller (gate_check.log: no /dev/iir, no 0x1119/Intel-0600 PCI device), and no syscall path can inject the malformed index - only the device can.
No comments yet.