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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4172 Β· 7 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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)

  1. gdt_mpr_get_status() returns the raw GDT_MPR_EDOOR doorbell byte (sys/dev/raid/iir/iir_pci.c:402-407). This is device-controlled memory-mapped state.
  2. 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].
  3. iir_intr() (iir.c:1522-1645) special-cases only GDT_ASYNCINDEX(0) and GDT_SPEZINDEX(1) (iir.c:1562, 1568) and then, with no range check:

    gccb = &gdt->sc_gccbs[ctx.istatus - 2]; / iir.c:1580 /

  4. sc_gccbs is kmalloc(sizeof(struct gdt_ccb) * GDT_MAXCMDS) with GDT_MAXCMDS == 124 (iir.c:250-251, iir.h:77). Driver-generated command indices are i + 2 for 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.

  5. Consequences on the OOB gccb: - gc_flags is read OOB (iir.c:1583). - If it reads as GDT_GCF_UNUSED(0): gdt_free_ccb() runs SLIST_REMOVE of a non-list-member (iir.c:661) β†’ the SLIST walk runs off the end (NULL deref) or corrupts the free list, and writes gc_flags = 0 OOB (iir.c:660). - If it reads as GDT_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 to param_size (≀3072) bytes are bcopyed to ucmd->data (iir.c:1613-1627) β†’ wild kernel write. - Otherwise (default, iir.c:1637-1639): gdt_sync_event dereferences the garbage gccb->gc_ccb β€” callout_stop(ccb->ccb_h.timeout_ch) (iir.c:1804) and xpt_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_testable
baseline no→ patch + rebuild →patched clean

fix.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)
↓ fix.diffper-fix-DF-4172

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.