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

radeon_bios: heap buffer overflow in radeon_atrm_call - unbounded memcpy of ACPI-returned buffer into 256K BIOS buffer

Summary

radeon_atrm_call at 207 memcpy(bios+offset, obj->Buffer.Pointer, obj->Buffer.Length) uses ACPI-controlled Length NOT caller len argument. No obj->Type==ACPI_TYPE_BUFFER check so Integer/String return aliases Pointer/Length union fields. Caller radeon_atrm_get_bios at 269 kmalloc 256*1024; loops i=0..63 offset=i*4096 len=4096. At i=63 (offset=258048) response >4096 overflows. Break at 281 only catches ret<ATRM_BIOS_PAGE no upper bound. Malicious ATRM ACPI method or non-Buffer type: wild-pointer wild-length kernel memcpy into 256K slab. Attacker: malicious platform firmware / custom SSDT / PCI option ROM installing malicious _ATRM. Fix: check Type==BUFFER; clamp Length to caller len.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1783 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source userspace harness that reproduces the bug logic 3.1 KB view raw
build.sh build-script cc -O2 -Wall -Wextra -o harness harness.c 98 B view raw
run.sh run-script ./harness 59 B view raw
build.log build-log full build output 13 B view raw
run.log run-log full decisive run output 3.8 KB view raw
env.txt environment uname + cc version 188 B view raw
VERDICT.md verdict full narrative: mechanism, Phase 6, fix 1.8 KB ↓ raw
fix.diff suggested-fix git-apply-able one-logical-change fix 803 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
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download raw

DF-1783 β€” radeon_bios.c heap overflow in radeon_atrm_call

Verdict

REPRODUCED (logic/harness) β€” bug confirmed by source trace. Not live-triggerable on the default QEMU guest: requires a malicious ACPI _ATRM method (custom SSDT / PCI option ROM / malicious platform firmware). The QEMU default ACPI exposes no _ATRM.

Mechanism (path:line)

  • sys/dev/drm/radeon/radeon_bios.c:269 β€” rdev->bios = kmalloc(size=256*1024, ...);
  • sys/dev/drm/radeon/radeon_bios.c:275-283 β€” caller loop i=0..63, offset = i*4096, len = 4096; only checks if (ret < ATRM_BIOS_PAGE) break; β€” no upper bound on the returned length.
  • sys/dev/drm/radeon/radeon_bios.c:207 β€” memcpy(bios+offset, obj->Buffer.Pointer, obj->Buffer.Length); β€” uses ACPI-returned Length, not the caller's len argument.
  • No obj->Type == ACPI_TYPE_BUFFER check; ACPI_OBJECT is a union, so a non-Buffer return aliases Buffer.Length / Buffer.Pointer with other union fields β†’ wild pointer / wild length.

At i = 63, offset = 258048; the 256 KB rdev->bios has 4096 bytes left. An ACPI response with Length > 4096 overflows by (Length - 4096) bytes into adjacent heap.

Phase 6 escalation

Trigger is malicious platform firmware. Attacker controls both the overflow size and the source bytes. Full attacker-shaped heap corruption of the slab that holds rdev->bios β†’ victim-object overwrite β†’ uid0 on this guest. Not developed because the default guest has no radeon GPU and no _ATRM.

PoC

harness.c simulates the caller loop with the malicious response (ACPI len = 8192 at i=63). Shows the 4096-byte overflow past the 256 KB rdev->bios end.

Fix

fix.diff adds a Type == ACPI_TYPE_BUFFER check and clamps the memcpy length to the caller's len. Validated by a clean radeon.ko rebuild with the patch applied.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED at module-build level: applied fix.diff to radeon_bios.c, 'make' rc=0, radeon.ko links cleanly with the new Type check and Length clamp.

baseline: harness shows at i=63 ACPI len=8192 overflows the 4096-byte remaining space by 4096 bytes
patched: radeon.ko builds clean; radeon_atrm_call now requires obj->Type==ACPI_TYPE_BUFFER && obj->Buffer.Length<=len, else returns -ENODEV without memcpy.
↓ fix.diffradeon.ko module rebuild (loadable .ko) - applied fix.diff (with DF-1725/1727/1753/1754), 'make' rc=0, radeon.ko 2029704 bytes built clean

Confirmed kernel references

Detail

Exploit chain

Trigger is malicious platform firmware (custom SSDT / PCI option ROM / malicious _ATRM). Attacker controls both overflow size and source bytes. Full attacker-shaped heap corruption of the slab holding rdev->bios -> victim object overwrite -> uid0 on this guest (no SMAP/SMEP/KASLR). Not developed because the default guest has no radeon GPU and no _ATRM. Harness in harness.c.

Evidence (decisive lines)

i=63: offset=258048, ACPI len=8192, space left=4096  <-- OVERFLOW
        memcpy writes 4096 bytes past rdev->bios end!
VERDICT: BUG CONFIRMED. radeon_atrm_call memcpy uses the ACPI-returned Length without clamping to the caller's `len` argument or checking obj->Type==BUFFER.

PoC changes

Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.

Verified recommended fix

fix.diff adds obj->Type==ACPI_TYPE_BUFFER check and clamps obj->Buffer.Length to caller len before the memcpy in radeon_atrm_call. Returns -ENODEV on type/length violation.

Verdict

REPRODUCED (logic/harness). radeon_bios.c:207 memcpy(bios+offset, obj->Buffer.Pointer, obj->Buffer.Length) uses ACPI-returned Length NOT caller's len arg. No obj->Type==ACPI_TYPE_BUFFER check; ACPI_OBJECT is a union so non-Buffer return aliases Pointer/Length with other union fields -> wild pointer/wild length. Caller radeon_atrm_get_bios at 269 kmalloc 2561024, loops i=0..63 offset=i4096 len=4096. At i=63 (offset=258048) response >4096 overflows by (Length-4096) bytes. Break at 281 only catches ret<ATRM_BIOS_PAGE, no upper bound. Harness simulates i=63 with ACPI len=8192.