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)
PoC verification
Evidence pack
findings/poc/DF-1133 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 asu32(amdgpu_gfx.h:76-77).gfx_v8_0.c:1106-1108βkmalloc(fmt_size + lst_size, ...): the addition isu32 + u32, wraps before promotion tosize_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 checksfw->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).
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 unprivilegedmaxx) shows (A)0x80000000+0x80000008 -> 0x8(8-byte alloc) vs a loop writing0x80000000bytes, and a realistic variant0xffffff00+0x208 -> 0x108(264-byte alloc) vs a loop writing ~4.29 GB; and (B) an uncheckedarray_offset=0x40000000reading ~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_testablecompile+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.
No comments yet.