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

RLC v2.0 firmware register-list parsing: integer-overflow kmalloc + unvalidated offset to heap OOB write/read

Summary

gfx_v8_0_init_microcode at gfx_v8_0.c:1106-1125: kmalloc(reg_list_format_size_bytes + reg_list_size_bytes) computed in u32 arithmetic wraps modulo 2^32 before widening to size_t. E.g. 0x80000000+0x80000008 wraps to 0x8 -> undersized 8-byte buffer. Loop at :1117-1118 writes reg_list_format_size_bytes>>2 dwords into it -> massive heap OOB write with firmware-controlled content. Separately reg_list_format_array_offset_bytes unvalidated -> OOB read past firmware blob into adjacent kernel heap -> copied into register_list_format -> later consumed by gfx_v8_0_parse_ind_reg_list via WREG32. amdgpu_ucode_validate only checks datasize==size_bytes. Same class as DF-1119/DF-1130. Fix: validate offsets/sizes against fw->datasize and cast to size_t before add.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1133 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace replica of RLC v2.0 reg-list kmalloc overflow + offset OOB + WITH-FIX pass 5.9 KB view raw
build.sh build-script cc -O2 -Wall -Wextra -o harness harness.c 114 B view raw
run.sh run-script ./harness 60 B view raw
build.log build-log final build, full output 8 B view raw
run.log run-log decisive run incl CONFIRMED + FIX VALIDATED 1.4 KB view raw
env.txt environment uname, cc, pciconf (no AMD GPU) 241 B view raw
fix.diff suggested-fix size_t add + validate offset+size<=datasize in gfx_v8_0_init_microcode 1.5 KB view raw
fix_validation.txt fix-validation apply-check + compile (gfx_v8_0.o rc=0) + harness fix-demo 1.7 KB view raw
VERDICT.md verdict full narrative 3.4 KB ↓ raw
README.md readme summary + repro 2.4 KB ↓ 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
README.md readme summary + repro
↓ download raw

DF-1133 β€” gfx_v8_0_init_microcode RLC v2.0 register-list integer overflow + OOB

Verdict

CONFIRMED (source-trace + harness) β€” INCONCLUSIVE on-guest (HW-gated). Real bug; not triggerable on the QEMU guest (no AMD GPU; amdgpu not compiled into GENERIC, only LINT64).

Bug (one line)

kmalloc(reg_list_format_size_bytes + reg_list_size_bytes, ...) computes the size as u32 + u32 (both fields are u32), which wraps mod 2^32 before widening to size_t -> a tiny allocation; the copy loop then writes reg_list_format_size_bytes>>2 dwords into it. Separately reg_list_format_array_offset_bytes is unvalidated, so the read pointer can pass the firmware blob.

Mechanism (path:line)

  • gfx_v8_0.c:1101-1104 β€” both sizes read as u32 (amdgpu_gfx.h:76-77).
  • gfx_v8_0.c:1106-1108 β€” kmalloc(fmt_size + lst_size, ...): the addition is u32 + u32, wraps before promotion to size_t. e.g. 0x80000000 + 0x80000008 -> 0x8 (8-byte alloc).
  • gfx_v8_0.c:1117-1118 β€” loop writes (fmt_size >> 2) dwords into the allocation -> massive heap OOB write (firmware-controlled content).
  • gfx_v8_0.c:1115-1116 β€” tmp = rlc_hdr + reg_list_format_array_offset_bytes (offset unvalidated) -> tmp[i] reads past the firmware blob -> heap OOB read.
  • amdgpu_ucode.c:251-260 β€” amdgpu_ucode_validate() only checks fw->datasize == hdr->size_bytes; it does NOT validate the reg-list sizes/offsets.

Trigger / threat model

A crafted rlc firmware blob (matching datasize==size_bytes) supplied via reflash, KVM GPU passthrough, or an emulated AMD GPU. PR:H per the CVSS (local privileged loader), but the corruption is a kernel heap OOB write with firmware-controlled data.

Reproduction on the audit guest

Not possible β€” no AMD GPU; amdgpu not in GENERIC. harness.c replicates the exact arithmetic, proving (A) the integer-overflow -> undersized alloc -> OOB write, and (B) the unvalidated-offset OOB read, then runs the fixed logic to show both are blocked.

Build / run

./build.sh && ./run.sh

Expected: DF-1133: CONFIRMED (A) heap OOB write ... AND (B) heap OOB read ... then DF-1133 FIX: VALIDATED - size_t add prevents wrap, offset validation rejects OOB read.

Fix

fix.diff (a) casts both operands to size_t before the add, and (b) validates offset + size <= fw->datasize for both register-list arrays, returning -EINVAL if violated. Applies cleanly; compiles in the amdgpu module build (gfx_v8_0.o, rc=0).

VERDICT.md verdict full narrative
↓ download raw

DF-1133 β€” VERDICT

Verdict: CONFIRMED via source-trace + userspace harness. On-guest: INCONCLUSIVE (HW-gated β€” no AMD GPU; amdgpu only in LINT64, not GENERIC).

Root-cause confirmation

Two distinct defects in gfx_v8_0_init_microcode():

(A) Integer-overflow -> heap OOB write (CWE-787 + CWE-190). adev->gfx.rlc.reg_list_format_size_bytes and reg_list_size_bytes are both u32 (amdgpu_gfx.h:76-77). The allocation at gfx_v8_0.c:1106-1108, kmalloc(fmt + lst, M_DRM, GFP_KERNEL), evaluates fmt + lst in 32-bit arithmetic (both operands u32) and only then promotes the (wrapped) result to size_t. With e.g. 0x80000000 + 0x80000008 the sum wraps to 0x8, allocating an 8-byte buffer. The copy loop at gfx_v8_0.c:1117-1118 then writes (fmt >> 2) = 0x20000000 dwords into it β€” a massive heap OOB write with firmware-controlled content.

(B) Unvalidated array offset -> heap OOB read (CWE-125). tmp = (u32*)((u8*)rlc_hdr + reg_list_format_array_offset_bytes) (gfx_v8_0.c:1115, offset read straight from the firmware header) is never bounds-checked against fw->datasize; a crafted offset makes tmp[i] read past the firmware blob into adjacent kernel heap. amdgpu_ucode_validate() (amdgpu_ucode.c:251-260) only checks fw->datasize == hdr->size_bytes and does not validate either the reg-list sizes or the array offsets.

Evidence

  • harness.c (run as unprivileged maxx) shows (A) 0x80000000+0x80000008 -> 0x8 (8-byte alloc) vs a loop writing 0x80000000 bytes, and a realistic variant 0xffffff00+0x208 -> 0x108 (264-byte alloc) vs a loop writing ~4.29 GB; and (B) an unchecked array_offset=0x40000000 reading ~1 GB past a 4096-byte firmware blob.

Exploit chain / impact

This is a write primitive (A) plus a read primitive (B). On the default GENERIC kernel it cannot be reached (amdgpu not compiled in); on a system with the amdgpu driver + a malicious rlc firmware, (A) is a kernel heap OOB write with attacker-shaped content β€” realistically a panic on INVARIANTS-ON GENERIC (slab poisoning / guard page) and a corruption/code-exec primitive on a non-INVARIANTS build. No uid=0 chain was developed because the path is unreachable on this guest (no AMD GPU / amdgpu absent from GENERIC). The realistic GENERIC-on-real-HW ceiling is panic (or info leak via (B)).

Fix validation

fix.diff casts both sizes to size_t before adding (kills the wrap) and validates offset + size <= fw->datasize for both register-list arrays (kills the OOB read), returning -EINVAL. - git apply --check -p1 => OK (applies alongside DF-1134 to the same file with no conflict). - Compiles in the amdgpu module: targeted make gfx_v8_0.o built gfx_v8_0.o (116648 B, -Werror clean) with BOTH DF-1133 and DF-1134 fixes applied; full nativekernel rc=0. - Harness "WITH FIX" pass: size_t add yields 0x100000008 (huge alloc -> ENOMEM, no OOB); offset validation REJECTS the OOB read. - fix_status: not_testable (HW-gated runtime; apply-check + compile + harness fix-demo + trace all pass).

PoC changes

Evidence pack authored from scratch: harness.c (+ WITH-FIX pass), build.sh, run.sh, fix.diff, VERDICT.md, manifest.json, logs.

Kernel refs (confirmed during verification)

sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1101, :1106, :1115, :1117; sys/dev/drm/amd/amdgpu/amdgpu_gfx.h:76; sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251; sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:91-93.

Fix verification

not_testable

compile+harness validated

module build rc=0 + harness 0 OOB

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed+harness. gfx_v8_0 RLC u32+u32 kmalloc overflow -> OOB write + unvalidated offset OOB read. amdgpu not in GENERIC.