Hardcoded EDID parser over-reads rdev->bios by up to 32KB, disclosing kernel heap via EDID ioctl
Summary
radeon_combios_check_hardcoded_edid at radeon_combios.c:373-379: raw=rdev->bios+edid_info, size=EDID_LENGTH*(raw[0x7e]+1) = 128*(0-255) up to 32768. memcpy(edid,raw,size) reads from BIOS allocation which can be as small as 512 bytes (bios[2]*512 from radeon_read_platform_bios). raw[0x7e]=0xff -> 32768 byte over-read from 512-byte slab -> ~32KB adjacent kernel heap leaked. If drm_edid_is_valid passes: stored as bios_hardcoded_edid, returned via connector EDID ioctl to unprivileged user. Crafted VBIOS (PCI swap/hotplug/VFCT). Fix: clamp size to bios_size, reject unreasonable block count.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1250 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replication of radeon_combios_check_hardcoded_edid over-read | 2.9 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 93 B | view raw |
| run.sh | run-script | ./harness | 48 B | view raw |
| run.log | run-log | harness output: 32768-byte memcpy over-reads 512-byte BIOS by 32320 | 456 B | view raw |
| fix.diff | suggested-fix | add bios_length field + bound EDID read against BIOS allocation (radeon.h, radeon_bios.c, radeon_combios.c) | 2.4 KB | view raw |
| fix_build.log | build-log | radeon.ko builds clean with -Werror (fix compiles) | 24.5 KB | view raw |
| env.txt | environment | uname, cc, module list | 346 B | view raw |
| README.md | readme | how to reproduce | 624 B | β raw |
| VERDICT.md | verdict | full analysis | 2.8 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-1250 β reproduction
Userspace harness replicating radeon_combios_check_hardcoded_edid()'s
size = 128*(raw[0x7e]+1) computation and the resulting BIOS over-read.
The live kernel trigger requires an AMD/ATI radeon GPU with a malicious Video BIOS (absent from the QEMU guest); the harness proves the over-read math against a 512-byte BIOS slab.
./build.sh && ./run.sh
Expected: PRIMITIVE CONFIRMED: memcpy(edid, raw, 32768) reads 32320 bytes past a 512-byte BIOS slab.
Fix: fix.diff adds a bios_length field + bounds the EDID read. Builds
cleanly into radeon.ko (-Werror). See VERDICT.md.
DF-1250 β radeon hardcoded-EDID parser over-reads rdev->bios
Verdict (one line)
CONFIRMED REAL (source trace + harness primitive), NOT reproduced on audit guest (no AMD/ATI radeon GPU).
Finding
sys/dev/drm/radeon/radeon_combios.c:radeon_combios_check_hardcoded_edid()
computes the EDID copy size from an attacker-controlled BIOS byte
raw[0x7e] and memcpys that many bytes out of the BIOS allocation
before any validity check, so a malicious Video BIOS whose image is
small (as little as 512 bytes via radeon_read_platform_bios) but whose
raw[0x7e] = 0xff causes a ~32 KB over-read of adjacent kernel heap.
Mechanism (path:line)
radeon_combios.c:373βraw = rdev->bios + edid_info;radeon_combios.c:374βsize = EDID_LENGTH * (raw[0x7e] + 1);raw[0x7e]β [0,255] βsizeβ [128, 32768].radeon_combios.c:375βedid = kmalloc(size, ...);radeon_combios.c:379βmemcpy(edid, raw, size);β over-read here.radeon_combios.c:381βif (!drm_edid_is_valid(edid))is checked only after thememcpy, so the leak is unconditional.- The BIOS allocation can be as small as 512 bytes:
sys/dev/drm/radeon/radeon_bios.c:radeon_read_platform_biosdoessize = bios[2] * 512(radeon_bios.c:146) andkmemdup(bios, size,...)(radeon_bios.c:153).raw[0x7e]=0xffβsize=32768β ~32 KB of adjacent heap leaked.
Why not reproduced on the audit guest
radeon is a loadable DRM module, not in GENERIC, and the guest has no
AMD/ATI GPU. The hardcoded-EDID table is parsed during GPU attach from the
card's Video BIOS, which is not present. The realistic trigger is a
malicious GPU Video BIOS (e.g. a hotplug PCIe card with a crafted ROM) β a
legitimate "malicious peripheral firmware" threat, not exercisable here.
Primitive proof (harness)
harness.c models a 512-byte BIOS slab, sets raw[0x7e]=0xff, computes
size exactly as the kernel does, and reports the over-read:
bios allocation = 512 bytes raw[0x7e] = 0xff computed size (radeon_combios.c:374) = 32768 bytes over-read past BIOS allocation: 32320 bytes of adjacent kernel heap PRIMITIVE CONFIRMED: memcpy(edid, raw, 32768) reads 32320 bytes past a 512-byte BIOS slab.
Fix
fix.diff (3 files):
- radeon.h: add uint32_t bios_length; (allocation size of *bios).
- radeon_bios.c: set rdev->bios_length = size; at every BIOS alloc
site (5 sites).
- radeon_combios.c: before dereferencing raw[0x7e] and before the
memcpy, verify edid_info + 0x7f and edid_info + size are within
rdev->bios_length; otherwise return false.
Validated: builds cleanly into radeon.ko with -Werror. (DF-1251's
fix shares the same bios_length infrastructure.)
Reproduce
ssh dfbsd-maxx; cd poc/DF-1250 && cc -O2 -Wall -o harness harness.c && ./harness
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. radeon_combios hardcoded EDID raw[0x7e] unbounded -> 32KB BIOS over-read. radeon not in GENERIC.
No comments yet.