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

Integer overflow in dmc_header->fw_size*4 causes unbounded heap OOB read in intel_csr_load_program

Summary

parse_csr_fw L404 nbytes=dmc_header->fw_size*4 uint32 multiplication wraps (fw_size=0x40000001 -> nbytes=4). L413 if(nbytes>max_fw_size) tests wrapped value passes. L417 csr->dmc_fw_size=dmc_header->fw_size stores original 0x40000001. L419 kmalloc(nbytes=4) succeeds 4-byte slab. L425 memcpy 4 bytes fine. intel_csr_load_program L259 fw_size=csr->dmc_fw_size=0x40000001; L264-265 for(i=0;i<fw_size;i++) I915_WRITE_FW(CSR_PROGRAM(i),payload[i]) reads payload[i] far past 4-byte buffer -> unbounded kernel heap OOB read ~10^9 iterations under preempt_disable L262; OOB-read dwords written to MMIO CSR_PROGRAM(i)=0x80000+(i*4) corrupting display/GPU HW state; hard CPU wedge. Same anti-pattern as DF-1838/1854/1875 PSP firmware but i915 DMC. Root-only trigger via module param dmc_firmware_path (mode 0400) or /lib/firmware write. Compromised firmware update/NFS-root/embedded/kiosk. Fix: check fw_size>max_fw_size/4 before computing nbytes; defense-in-depth guard in load_program.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1894 Β· 9 files
FileTypeDescriptionSize
harness.c trigger-source userspace logic harness reproducing the buggy arithmetic/control-flow 2.3 KB view raw
VERDICT.md verdict full verification narrative 3.1 KB ↓ raw
build.sh build-script exact build command 88 B view raw
run.sh run-script exact run invocation 41 B view raw
harness_run.log run-log harness output on guest 628 B view raw
fix.diff suggested-fix git-apply-able unified diff 1.3 KB view raw
env.txt environment guest uname, cc version, kernel config 768 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 verification narrative
↓ download raw

DF-1894 β€” Verification Verdict

Verdict: REPRODUCED (source-confirmed + arithmetic-harness)

The fw_size * 4 integer overflow is confirmed at sys/dev/drm/i915/intel_csr.c:404. The harness reproduces the wraparound and shows the resulting ~10⁹-dword OOB read in intel_csr_load_program.

Mechanism

// intel_csr.c:404
nbytes = dmc_header->fw_size * 4;    // uint32 mult β€” wraps mod 2^32
// :413
if (nbytes > max_fw_size) return NULL;   // tests WRAPPED value
// :417
csr->dmc_fw_size = dmc_header->fw_size;  // stores ORIGINAL (huge)
// :419
dmc_payload = kmalloc(nbytes, ...);      // tiny alloc
// :425
return memcpy(dmc_payload, &fw->data[readcount], nbytes);  // tiny copy β€” ok

With fw_size = 0x40000001: nbytes wraps to 4, kmalloc(4) succeeds, the 4-byte memcpy is fine. But csr->dmc_fw_size stores the ORIGINAL 0x40000001. Then intel_csr_load_program (intel_csr.c:259-265):

fw_size = csr->dmc_fw_size;                     // 0x40000001
for (i = 0; i < fw_size; i++)
    I915_WRITE_FW(CSR_PROGRAM(i), payload[i]);  // payload[i] OOB read

reads ~10⁹ dwords past payload (which is only 4 bytes) into kernel heap, writing each to MMIO CSR_PROGRAM(i) = 0x80000 + i*4 under preempt_disable (:262) β€” corrupting display/GPU HW state and wedging the CPU.

Same anti-pattern as DF-1838/1854/1875 PSP firmware, but i915 DMC.

Harness evidence

DF-1894: parse_csr_fw (intel_csr.c:404-425) + intel_csr_load_program (:259)
  dmc_header->fw_size = 0x40000001
  nbytes = fw_size * 4 = 0x00000004 (WRAPS β€” should be 0x100000004)
  nbytes > max_fw_size (0x100000)? NO (check bypassed)
  -> kmalloc(4) succeeds; memcpy 4 bytes is fine
  -> csr->dmc_fw_size = 0x40000001 (ORIGINAL, not wrapped)
  -> intel_csr_load_program loops i=0..0x40000001 calling I915_WRITE_FW (CSR_PROGRAM(i), payload[i])
     payload has 4 bytes allocated; reads ~1073741824 dwords past it into kernel heap -> OOB read + MMIO corruption -> GPU/CPU wedge

Why no live trigger on this guest

The DMC firmware path runs on Intel graphics (i915). The audit guest has no Intel GPU; i915.ko is present but not loaded. The trigger requires a crafted DMC firmware, reachable via root-controlled dmc_firmware_path module param (mode 0400) or a compromised firmware update / NFS-root / embedded kiosk. Valid Phase-6 hard blocker.

Exploit chain

Not applicable (Intel-GPU-gated + root-controlled firmware path). No uid=0 claim. Live ceiling: ~10⁹-dword OOB read + MMIO corruption β†’ hard CPU/GPU wedge / DoS; the OOB read could also leak kernel heap.

PoC changes

  • Added harness.c: reproduces the fw_size * 4 wrap arithmetic.
  • Added fix.diff: validate fw_size > max_fw_size/4 BEFORE the multiplication.

Fix

fix.diff moves the max_fw_size computation before the nbytes multiplication and adds if (dmc_header->fw_size > max_fw_size / 4) return NULL β€” rejecting the oversized fw_size before it can wrap or be stored in csr->dmc_fw_size.

  • BEFORE: harness shows nbytes wraps to 4, check bypassed.
  • AFTER: fw_size > max_fw_size/4 is rejected before the multiply.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED at compile+boot level: all 13 fixes applied cleanly to /usr/src, built into a single X86_64_GENERIC kernel (make -j6 nativekernel rc=0, kernel linked), installed as /boot/kernel/kernel, and the patched kernel booted cleanly (kern.version #1 vs baseline #0). The live PoC cannot run on this guest (HW/config-gated per the verdict), so before/after is at source+harness level: baseline harness: 'nbytes = 0x00000004 (WRAPS) ... check bypassed' | patched: fw_size>max_fw_size/4 rejected before multiply

baseline (#0 unpatched): baseline harness: 'nbytes = 0x00000004 (WRAPS) ... check bypassed'
patched (#1 kernel, all 13 fixes, booted clean): patched: fw_size>max_fw_size/4 rejected before multiply
kernel sha256 c3fff85f... (patched, booted) vs 5dc83dac... (baseline #0)
↓ fix.diffDragonFly 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 19:12:20 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

HW+root-gated (Intel GPU absent on guest; i915.ko present but not loaded; trigger requires crafted DMC firmware via root-controlled dmc_firmware_path module param mode 0400, or compromised firmware update / NFS-root / embedded kiosk). No uid=0 escalation claimed. Primitive characterized in harness.c (fw_size*4 wrap arithmetic). Live ceiling: ~10^9-dword OOB read + MMIO corruption -> hard CPU/GPU wedge / DoS; OOB read could also leak kernel heap.

Evidence (decisive lines)

DF-1894: parse_csr_fw (intel_csr.c:404-425) + intel_csr_load_program (:259)
  dmc_header->fw_size = 0x40000001
  nbytes = fw_size * 4 = 0x00000004 (WRAPS β€” should be 0x100000004)
  nbytes > max_fw_size (0x100000)? NO (check bypassed)
  -> kmalloc(4) succeeds; memcpy 4 bytes is fine
  -> csr->dmc_fw_size = 0x40000001 (ORIGINAL, not wrapped)

PoC changes

Added harness.c (wrap arithmetic) and fix.diff (validate fw_size > max_fw_size/4 BEFORE the multiply).

Verified recommended fix

fix.diff moves max_fw_size computation before nbytes and adds 'if (fw_size > max_fw_size/4) return NULL' β€” rejecting oversized fw_size before it wraps or is stored. matches finding proposal.

Verdict

REPRODUCED at source+harness. parse_csr_fw at intel_csr.c:404 computes nbytes=fw_size*4 (uint32 mult wraps). Harness with fw_size=0x40000001 shows nbytes wraps to 4, passes the max_fw_size check at :413, kmalloc(4) succeeds; csr->dmc_fw_size stores original 0x40000001; intel_csr_load_program (:259) then loops i=0..0x40000001 reading payload[i] ~10^9 dwords past the 4-byte buffer into kernel heap + writing to MMIO. HW+root-gated (Intel GPU absent; trigger via root-controlled dmc_firmware_path module param).