Unvalidated RLC firmware header fields cause integer overflow in kmalloc and OOB heap read/write
Summary
gfx_v9_0_init_microcode at :691-713: reg_list_format_size_bytes+reg_list_size_bytes computed in u32 arithmetic wraps to small value -> undersized kmalloc -> copy loops overflow. Also reg_list_format_array_offset_bytes/reg_list_array_offset_bytes unvalidated -> OOB read past firmware buffer. amdgpu_ucode_validate only checks datasize==size_bytes. Same class as DF-1133 (gfx_v7) and DF-1164 (gfx_v8). Fix: validate offsets+sizes against fw->datasize, use size_t for kmalloc size.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1176 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace reconstruction of u32 overflow kmalloc + OOB read via firmware offset | 5.0 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 152 B | view raw |
| run.sh | run-script | ./harness | 71 B | view raw |
| build.log | build-log | successful in-guest build | 87 B | view raw |
| run.log | run-log | harness output: heap overflow + OOB SIGSEGV | 1.2 KB | view raw |
| env.txt | environment | uname, amdgpu availability, module-build validation | 803 B | view raw |
| fix.diff | suggested-fix | size_t kmalloc + overflow check, validate offsets/sizes vs fw->datasize | 2.0 KB | view raw |
| VERDICT.md | verdict | full narrative + module-build validation + hard-blocker rationale | 5.4 KB | β raw |
| README.md | readme | human-facing summary | 4.1 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-1176 β gfx_v9_0_init_microcode u32 integer overflow + OOB
Finding
gfx_v9_0_init_microcode() at sys/dev/drm/amd/amdgpu/gfx_v9_0.c:691-713
parses an attacker-controlled RLC firmware header without validating internal
sizes or offsets, producing two distinct vulnerabilities.
Bug 1 β Integer overflow in kmalloc size (lines 691-697)
adev->gfx.rlc.reg_list_format_size_bytes =
le32_to_cpu(rlc_hdr->reg_list_format_size_bytes); /* :691 u32 */
adev->gfx.rlc.reg_list_size_bytes =
le32_to_cpu(rlc_hdr->reg_list_size_bytes); /* :693 u32 */
adev->gfx.rlc.register_list_format =
kmalloc(adev->gfx.rlc.reg_list_format_size_bytes +
adev->gfx.rlc.reg_list_size_bytes, M_DRM, GFP_KERNEL); /* :696 */
Both sizes are uint32_t (amdgpu_gfx.h:76-77, populated from
amdgpu_ucode.h:91,93). Their sum is computed in 32-bit arithmetic. A
crafted firmware header with reg_list_format_size_bytes = 0x80000010 and
reg_list_size_bytes = 0x80000010 produces sum = 0x20 (32 bytes), so
kmalloc(32) is called. The subsequent copy loop at lines 705-706 then runs
size_a >> 2 = 0x20000004 iterations, writing ~2 GB into a 32-byte
allocation β structured heap overflow / slab corruption.
Bug 2 β Unvalidated firmware offsets (lines 703-713)
tmp = (unsigned int *)((uintptr_t)rlc_hdr +
le32_to_cpu(rlc_hdr->reg_list_format_array_offset_bytes)); /* :704 */
for (i = 0 ; i < (reg_list_format_size_bytes >> 2); i++)
register_list_format[i] = le32_to_cpu(tmp[i]); /* :706 */
reg_list_format_array_offset_bytes (amdgpu_ucode.h:92) is added to the
firmware buffer pointer with no validation against fw->datasize. A large
offset reads from beyond the firmware kmalloc β OOB read / page fault
panic. The same defect exists at line 711 for
reg_list_array_offset_bytes.
amdgpu_ucode_validate does NOT prevent either bug
/* amdgpu_ucode.c:251 */ int amdgpu_ucode_validate(const struct firmware *fw) {
if (fw->datasize == le32_to_cpu(hdr->size_bytes)) return 0;
return -EINVAL;
}
Only the top-level datasize == size_bytes is checked. Internal offsets and
sizes within the header are not validated, so a crafted firmware file
trivially passes validation.
Same class as DF-1133 (gfx_v7) and DF-1164 (gfx_v8).
Reproducibility on the audit guest
amdgpu is optional (sys/conf/files:2492+); not in X86_64_GENERIC;
QEMU guest has no AMD GPU. The amdgpu.ko module exists at
/boot/modules/amdgpu.ko but cannot initialize without AMD hardware, so a
live trigger is not possible on this guest.
A userspace harness (harness.c) reconstructs the kmalloc + copy loops
and demonstrates both bugs with crafted firmware-header values:
=== Bug 1: u32 overflow in kmalloc(size_a + size_b) === reg_list_format_size_bytes = 0x80000010 (2147483664) reg_list_size_bytes = 0x80000010 (2147483664) u32 sum = 0x00000020 (32) <-- WRAPS kmalloc(32); copy loop runs 0x20000004 iters, writing ~2 GB into 32-byte buffer -> structured heap overflow === Bug 2: unvalidated reg_list_format_array_offset_bytes === fw->datasize = 4096; offset = 0x10000 (> datasize) BUG: OOB read faulted (signal 11) -- kernel equivalent: page fault past firmware
Threat model
The firmware is loaded via request_firmware from /lib/firmware/ (or
amdgpufw_<chip>_rlc). An attacker who can plant a crafted firmware file
(on systems that auto-load firmware, or via a malicious hypervisor that
presents forged firmware) triggers immediate slab corruption or a kernel
panic at GPU initialization.
Severity Medium (CVSS PR:H): requires either root on the target box to plant
firmware, or a malicious VM/hypervisor scenario. For an unprivileged attacker
with write access to /lib/firmware (a common misconfiguration), it becomes
High.
Recommended fix
- Compute the kmalloc size in
size_tand reject on overflow. - Validate
reg_list_format_size_bytes + reg_list_format_array_offset_bytes <= fw->datasize(and same for reg_list) before the copy loops.
See fix.diff.
DF-1176 β VERDICT
Verdict: REPRODUCED (source+harness, module-build-validated). Not uid=0
on the audit guest β this is a memory-corruption primitive (structured heap
overflow + OOB read), but the only realistic trigger paths require root or
a malicious hypervisor, so there is no unprivileged privilege boundary to
cross. Valid hard blocker per Phase 6 (root-only reachability).
Bug confirmation
gfx_v9_0_init_microcode() at sys/dev/drm/amd/amdgpu/gfx_v9_0.c:691-713
parses an attacker-controlled RLC firmware header without validation. Two
distinct bugs:
Bug 1 β Integer overflow in kmalloc size (lines 691-697)
adev->gfx.rlc.reg_list_format_size_bytes =
le32_to_cpu(rlc_hdr->reg_list_format_size_bytes); /* u32 from disk */
adev->gfx.rlc.reg_list_size_bytes =
le32_to_cpu(rlc_hdr->reg_list_size_bytes); /* u32 from disk */
adev->gfx.rlc.register_list_format =
kmalloc(adev->gfx.rlc.reg_list_format_size_bytes +
adev->gfx.rlc.reg_list_size_bytes, M_DRM, GFP_KERNEL);
Both fields are uint32_t (amdgpu_gfx.h:76-77, populated from
amdgpu_ucode.h:91,93). The sum is computed in 32-bit arithmetic. A crafted
firmware with 0x80000010 + 0x80000010 wraps to 0x20 (32 bytes), so
kmalloc(32) is called and the subsequent copy loops (lines 705-706,
712-713) write ~2 GB into a 32-byte allocation β structured slab
corruption / heap overflow.
Bug 2 β Unvalidated firmware offsets (lines 703-713)
tmp = (unsigned int *)((uintptr_t)rlc_hdr +
le32_to_cpu(rlc_hdr->reg_list_format_array_offset_bytes));
for (i = 0 ; i < (reg_list_format_size_bytes >> 2); i++)
register_list_format[i] = le32_to_cpu(tmp[i]);
reg_list_format_array_offset_bytes and reg_list_array_offset_bytes
(amdgpu_ucode.h:92,94) are added to the firmware pointer with no validation
against fw->datasize. A large offset reads past the firmwarekmalloc` β
OOB read / page-fault panic.
amdgpu_ucode_validate does NOT prevent either bug
amdgpu_ucode.c:251 only checks fw->datasize == hdr->size_bytes. Internal
offsets and sizes are not validated, so a crafted firmware trivially passes
validation.
Reproducibility on the audit guest
amdgpu is optional (sys/conf/files:2492+); not in X86_64_GENERIC;
QEMU guest has no AMD GPU. The amdgpu.ko module exists at
/boot/kernel/amdgpu.ko but cannot initialize without AMD hardware, so a
live trigger is not possible on this guest.
A userspace harness (harness.c) reconstructs the kmalloc + copy loops and
demonstrates both bugs:
=== Bug 1: u32 overflow in kmalloc(size_a + size_b) === size_a=0x80000010 size_b=0x80000010 u32_sum=0x20 (32) <-- WRAPS kmalloc(32); copy loop runs 536870916 iterations, writing 2147483664 bytes -> 2147483632-byte heap overflow past kmalloc'd buffer === Bug 2: unvalidated reg_list_format_array_offset_bytes === fw->datasize=4096 offset=0x10000 (> datasize) BUG: OOB read faulted (signal 11) -- kernel equivalent: page fault past firmware
Module build validation
The fix.diff was applied in-guest to /usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.c
and make in /usr/src/sys/dev/drm/amd/amdgpu compiled the patched
gfx_v9_0.c cleanly (/usr/obj/usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.o
produced, no warnings, -Werror). The full amdgpu.ko link step fails on an
unrelated pre-existing issue (color_gamma.o: file is empty in
display/dc/color_gamma.c); this is not related to our patch. Source was
reverted after validation.
Exploit chain
blocked by valid hard blocker (root-only reachability): The vulnerable
parsing runs in request_firmware context β the firmware is loaded from
/lib/firmware/amdgpufw_<chip>_rlc by the kernel. Triggering requires either:
- Root planting a crafted firmware file (root β kernel is game-over by definition; no privilege boundary to cross), or
- A malicious hypervisor presenting forged firmware to a guest VM (out-of-scope for local privilege escalation).
There is no unprivileged local path to deliver a crafted RLC firmware on
the audit guest: firmware files live under /lib/firmware/ (root-owned) and
are loaded by the kernel only at GPU probe time, which itself requires AMD
hardware (absent on the QEMU guest). The primitive is real and would be a
strong escalation candidate on a system where it IS reachable (a multi-user
box with world-writable /lib/firmware, common in some container/VM images),
but on the default DragonFlyBSD deployment the boundary is absent.
Concrete next iteration if an unprivileged firmware-write path were found:
the slab-corruption primitive (Bug 1) lands in the DRM slab bucket β groom
adjacent struct file / struct ucred objects, overflow into a victim, and
redirect a corrupted ucred * to a forged credential in userspace (no SMAP,
no SMEP). This is exactly the chain developed for the analogous DF-0783-class
bugs.
Fix
fix.diff introduces a size_t-computed allocation with overflow check, and
validates both offsets and sizes against fw->datasize before the copy loops.
git apply --check passes; patched source compiles cleanly into
gfx_v9_0.o on the guest.
Threat model
Severity Medium (CVSS PR:H): realistically triggerable by root on a system
with AMD GPU, or by a malicious hypervisor. High for misconfigured systems
where /lib/firmware is world-writable.
Same class as DF-1133 (gfx_v7), DF-1134 (gfx_v7), DF-1164 (gfx_v8).
Fix verification
not_testablecompile+harness validated
module/object build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. gfx_v9_0 RLC firmware u32 overflow kmalloc+OOB read. amdgpu not in GENERIC.
No comments yet.