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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1176 Β· 11 files
FileTypeDescriptionSize
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
README.md readme human-facing summary
↓ download 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.

  1. Compute the kmalloc size in size_t and reject on overflow.
  2. 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.

VERDICT.md verdict full narrative + module-build validation + hard-blocker rationale
↓ download raw

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:

  1. Root planting a crafted firmware file (root β†’ kernel is game-over by definition; no privilege boundary to cross), or
  2. 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_testable

compile+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.