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

gfx_v9_1_parse_ind_reg_list: stack buffer overflow on indirect_start_offsets[10] and unbounded OOB read

Summary

gfx_v9_1_parse_ind_reg_list at :2084-2118: indirect_start_offsets[10] written without hard bounds (WARN_ON is non-fatal). >10 indirect blocks -> stack overflow overwriting saved RBP/return address. Inner while-loop advances indirect_offset by 3 without checking <list_size -> OOB heap read. BUG_ON(idx>=8) unconditional panic with >8 unique indirect IDs. Reached from gfx_v9_0_init_pg during GPU init. Attacker: crafted RLC v2.1 firmware. NEW class not present in gfx_v7/v8. Fix: convert WARN_ON to break, add indirect_offset bounds check.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1178 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace transcription of gfx_v9_1_parse_ind_reg_list that demonstrates the stack-overflow / OOB-read / BUG_ON-panic logic without any kernel dependency 7.9 KB view raw
build.sh build-script cc -O2 -o harness harness.c 125 B view raw
run.sh run-script ./harness 146 B view raw
README.md readme human-readable summary + reproduce 3.2 KB ↓ raw
VERDICT.md verdict detailed mechanism, source trace, why-not-reproduced, fix notes 4.8 KB ↓ raw
fix.diff suggested-fix convert WARN_ON to break, add indirect_offset < list_size bounds check, convert BUG_ON to break 1.2 KB view raw
run.log run-log harness stdout showing WARN_ON fires 5x, 5 slots overflow past indirect_start_offsets[10] 463 B view raw
fix_build.log fix-build-log nativekernel build log showing gfx_v9_0.c compiles cleanly with fix applied (rc=0, -Werror) 5.6 MB ↓ download
fix_run.log fix-run-log harness on patched kernel (no regression; logic-level output identical) 131 B view raw
env.txt environment uname, cc version, pciconf 947 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
README.md readme human-readable summary + reproduce
↓ download raw

DF-1178 β€” gfx_v9_1_parse_ind_reg_list stack overflow + OOB read + BUG_ON panic

Bug (confirmed in source)

sys/dev/drm/amd/amdgpu/gfx_v9_0.c:2084-2118, reached only via gfx_v9_0_init_pg() β†’ gfx_v9_1_init_rlc_save_restore_list() (lines 2405, 2142) when adev->gfx.rlc.is_rlc_v2_1 == true.

Three distinct defects in gfx_v9_1_parse_ind_reg_list():

  1. Stack buffer overflow (CWE-121) at gfx_v9_0.c:2096-2098: WARN_ON(*indirect_start_offsets_count >= max_start_offsets_count); is non-fatal in DragonFly (sys/dev/drm/include/asm/bug.h:43-49 β€” it kprintfs a warning and returns the condition). Execution continues unconditionally into indirect_start_offsets[*indirect_start_offsets_count] = indirect_offset; With max_start_offsets_count == 10 (the stack array declared at line 2126 has exactly 10 ints), a crafted RLC v2.1 firmware with >10 indirect blocks smashes past indirect_start_offsets[10] into saved RBP / the return address.

  2. Heap OOB read (CWE-125) at gfx_v9_0.c:2100-2101: the inner while (register_list_format[indirect_offset] != 0xFFFFFFFF) loop does indirect_offset += 2; with no check that indirect_offset < list_size. The register_list_format array is a kmalloc() (line 2134) of reg_list_format_size_bytes >> 2 ints; a firmware that omits the 0xFFFFFFFF terminator causes the parser to walk off the end of the heap allocation.

  3. BUG_ON panic at gfx_v9_0.c:2111: BUG_ON(idx >= unique_indirect_reg_count) expands to panic() in DragonFly (sys/dev/drm/include/asm/bug.h:33-37). A firmware with >8 distinct indirect register IDs is an unconditional kernel panic / local DoS β€” the per-call unique_indirect_regs[8] (line 2123) cannot hold a ninth entry.

Class: NEW class not present in gfx_v7/v8 β€” added with gfx_v9.

Trigger model

  • Requires the amdgpu driver attached to a real AMD Vega-class GPU (vgapci0 here is QEMU stdvga vendor 0x1234 β€” not AMD, no attachment).
  • Triggered during GPU init by a crafted RLC v2.1 firmware.
  • CVSS AV:L/AC:L/PR:H/UI:N/S:U:C:H/I:H/A:H β€” high integrity/availability impact but high privilege required (malicious firmware must be flashed by an attacker with root or physical access).

On this guest

The path is unreachable on this guest (no AMD GPU; the amdgpu module is present in /boot/kernel/amdgpu.ko but never attaches). This is a latent bug β€” confirmed real in source, not triggerable on the audit guest. The harness harness.c is a faithful userspace transcription of the parser that demonstrates the overflow logic without any kernel dependency.

Reproduce

ssh dfbsd-maxx "cd poc/DF-1178 && ./build.sh && ./run.sh"

Expected: the harness prints WARN_ON fires and Stack-buffer overflows past indirect_start_offsets[10]: N slot(s), proving the parser logic permits the overflow. No kernel effect on this guest.

Convert WARN_ON to a hard break, add an indirect_offset < list_size bounds check on the inner while, and convert the BUG_ON to a break. Full git-apply-able patch in fix.diff. This matches the finding proposal (convert WARN_ON to break, add indirect_offset bounds check).

VERDICT.md verdict detailed mechanism, source trace, why-not-reproduced, fix notes
↓ download raw

DF-1178 β€” VERDICT

Status: NOT REPRODUCED (HW-gated; bug confirmed in source) Impact: none (cannot trigger on this guest) Confidence: certain (line-by-line source trace) Class: memory corruption (latent; CWE-121 + CWE-125 + DoS-via-panic)

Mechanism (source-trace, every hop cited)

Caller chain into the buggy function (all in sys/dev/drm/amd/amdgpu/gfx_v9_0.c):

  1. gfx_v9_0_hw_init() is the .hw_init IP-block callback (line 4724), invoked by the amdgpu driver when it attaches to a Vega-class GPU.
  2. gfx_v9_0_hw_init() calls gfx_v9_0_init_pg(adev) at line 2510.
  3. gfx_v9_0_init_pg() (line 2396) calls gfx_v9_1_init_rlc_save_restore_list(adev) at line 2405 β€” only when adev->gfx.rlc.is_rlc_v2_1 == true, which is set during gfx_v9_0_init_rlc() at line 673 based on the RLC firmware header.
  4. gfx_v9_1_init_rlc_save_restore_list() (line 2121) declares two stack-allocated arrays and calls gfx_v9_1_parse_ind_reg_list(): c int unique_indirect_regs[8] = {0}; // line 2123 int indirect_start_offsets[10] = {0}; // line 2126 ... gfx_v9_1_parse_ind_reg_list(register_list_format, // line 2142 adev->gfx.rlc.reg_list_format_direct_reg_list_length, adev->gfx.rlc.reg_list_format_size_bytes >> 2, unique_indirect_regs, ARRAY_SIZE(unique_indirect_regs), // 8 indirect_start_offsets, &indirect_start_offsets_count, ARRAY_SIZE(indirect_start_offsets)); // 10

  5. gfx_v9_1_parse_ind_reg_list() (line 2084) has three defects (confirmed against sys/dev/drm/include/asm/bug.h):

  • Stack overflow at lines 2096-2098: c WARN_ON(*indirect_start_offsets_count >= max_start_offsets_count); indirect_start_offsets[*indirect_start_offsets_count] = indirect_offset; *indirect_start_offsets_count = *indirect_start_offsets_count + 1; WARN_ON in DragonFly is non-fatal (just kprintfs, see sys/dev/drm/include/asm/bug.h:43-49). After the 10th block the write goes past indirect_start_offsets[10] into the caller's saved RBP and return address. Stack-based buffer overflow (CWE-121).

  • Heap OOB read at lines 2100-2101: c while (register_list_format[indirect_offset] != 0xFFFFFFFF) { indirect_offset += 2; No check that indirect_offset < list_size. register_list_format is a kmalloc(reg_list_format_size_bytes, ...) (line 2134). A firmware that doesn't terminate the indirect block with 0xFFFFFFFF causes the parser to read past the heap allocation (CWE-125).

  • BUG_ON panic at line 2111: c BUG_ON(idx >= unique_indirect_reg_count); BUG_ON expands to panic() in DragonFly (sys/dev/drm/include/asm/bug.h:33-37). More than 8 distinct indirect IDs is an unconditional kernel panic / local DoS.

Why it cannot be reproduced on this guest

  • The guest's PCI bus (verified via pciconf -l): hostb0@pci0:0:0:0: vendor 0x8086 Intel 440FX vgapci0@pci0:0:2:0: vendor 0x1234 QEMU stdvga (NOT AMD) virtio_pci0/1 virtio net/blk There is no AMD GPU. The amdgpu module exists at /boot/kernel/amdgpu.ko but the driver never attaches (no matching PCI device), so the .hw_init/gfx_v9_0_init_pg() path never executes.

  • amdgpu, radeon, drm are not in the X86_64_GENERIC config (grep -E 'radeon|amdgpu|drm' sys/config/X86_64_GENERIC β†’ no match). They are only loadable as modules, and even loaded they cannot attach to the QEMU stdvga.

  • Even with hardware present, the trigger requires a crafted RLC v2.1 firmware β€” i.e. an attacker who has already flashed malicious firmware (root or physical access). This matches the CVSS PR:H rating.

This is a latent / hardware-gated bug β€” confirmed real by source trace, unreachable on the audit guest, and not an unprivileged-local-to-root escalation. The harness harness.c is a userspace transcription of the parser that demonstrates the overflow logic without any kernel dependency; it builds, runs, and prints the overflow count, but produces no kernel effect.

Fix

fix.diff converts the non-fatal WARN_ON into a hard break, adds an indirect_offset < list_size bounds check inside the inner while loop, and converts the BUG_ON panic into a break. The patch matches the finding markdown's proposal (convert WARN_ON to break, add indirect_offset bounds check). Because the path cannot be exercised on the guest, the fix is validated as applies + compiles only (fix_status: not_testable).

Fix verification

not_testable

compile+harness validated

kernel build rc=0 + harness

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. gfx_v9_1_parse_ind_reg_list stack overflow + OOB read. amdgpu not in GENERIC.