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)
PoC verification
Evidence pack
findings/poc/DF-1894 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 thefw_size * 4wrap arithmetic. - Added
fix.diff: validatefw_size > max_fw_size/4BEFORE 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
fixedVALIDATED 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)
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- c
- s
- r
- .
- c
- :
- 4
- 0
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- c
- s
- r
- .
- c
- :
- 4
- 1
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- c
- s
- r
- .
- c
- :
- 4
- 1
- 7
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- i
- 9
- 1
- 5
- /
- i
- n
- t
- e
- l
- _
- c
- s
- r
- .
- c
- :
- 2
- 5
- 9
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).
No comments yet.