Unbounded LVDS mode-patch record walk and fake-EDID copy read past the VBIOS image buffer (conditional userspace EDID leak)
Summary
The record walk exits only on a sentinel byte/type; nothing bounds it to the image. usModePatchTableOffset is an unvalidated u16 and the fake-EDID branch kmallocs max(128, ucFakeEDIDLength) and memcpy's up to 255 bytes from record+2 - a record placed at the image end copies adjacent kernel heap into bios_hardcoded_edid; when drm_edid_is_valid passes, those bytes become the connector EDID blob readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR); otherwise the walk continues OOB. HARNESS: fake-EDID record at image tail, len=200 -> 200/200 bytes read beyond the image; walk continues 201+ bytes past end. Crafted LVDS_Info: heap OOB read at LVDS encoder init; conditional leak of ~200 B of adjacent heap to unprivileged userspace.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3744 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| build.log | β | 174 B | view raw | |
| build.sh | β | 65 B | view raw | |
| env.txt | β | 375 B | view raw | |
| harness_lvds.c | β | 4.6 KB | view raw | |
| run.log | β | 377 B | view raw | |
| run.sh | β | 108 B | view raw | |
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.9 KB | β raw | |
| manifest.json | β | 906 B | view raw | |
| verdict.json | β | 3.2 KB | view raw | |
| fix.diff | β | 980 B | view raw |
DF-3744 β Unbounded LVDS mode-patch record walk + fake-EDID copy read past the VBIOS image buffer (conditional userspace leak)
Severity: Medium Β· CWE: CWE-125 Out-of-bounds Read Β· File: sys/dev/drm/radeon/radeon_atombios.c:1691-1752
What this is
radeon_atombios_get_lvds_info() walks the mode-patch record table with while (*record != ATOM_RECORD_END_TYPE) (:1706) β the only exit conditions are the sentinel byte, known record types stepping by fixed sizes or ucFakeEDIDLength, or an unknown type. Nothing bounds the walk to the VBIOS image: usModePatchTableOffset is an unvalidated u16 (:1697-1705) and record strides are attacker-chosen, so the walk crosses the end of the rdev->bios kmalloc'd image and keeps reading heap. The fake-EDID branch (:1717-1737) kmallocs max(128,len) and memcpy's len (<=255) bytes from record+2 β with the record placed at the image end this copies up to 255 bytes of ADJACENT KERNEL HEAP into bios_hardcoded_edid; if drm_edid_is_valid() passes (checksum 1/256 odds on uncontrolled bytes; guaranteed for the in-image part), those bytes become the connector EDID blob, readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR). Harness on guest: record at image end, len=200 -> 200/200 bytes read beyond image; walk continues 201+ bytes past end until garbage type. Kernel path unreachable on this guest.
Reproduce
./build.sh # cc -O1 -g -Wall -o <bin> harness_lvds.c (DragonFly guest, cc 8.3) ./run.sh # expected: fake-EDID len=200: 200/200 bytes read beyond image end; walk continues past end until garbage record type; exit_code=0
The harness replicates the kernel parse arithmetic VERBATIM (cited path:line) against a synthetic crafted-VBIOS buffer with canary-guarded destinations.
Guest / environment
Guest has NO AMD/radeon GPU (virtio-only); /boot/kernel/radeon.ko exists (code shipped in the default install) but cannot attach, so the kernel code path is unreachable at runtime on this guest. Evidence is source-level certainty (path:line traced) plus a verbatim-arithmetic userland harness compiled and executed on the guest (build.log/run.log). See env.txt (uname, cc, kldstat, /boot/kernel/radeon.ko presence).
Fix
bound the walk to the BIOS image (requires tracking the image length in atom_context) β see fix.diff (authored after verification; never applied to sys/).
DF-3744 β Unbounded LVDS mode-patch record walk + fake-EDID copy read past the VBIOS image buffer (conditional userspace leak)
Severity: Medium Β· CWE: CWE-125 Out-of-bounds Read Β· File: sys/dev/drm/radeon/radeon_atombios.c:1691-1752
What this is
radeon_atombios_get_lvds_info() walks the mode-patch record table with while (*record != ATOM_RECORD_END_TYPE) (:1706) β the only exit conditions are the sentinel byte, known record types stepping by fixed sizes or ucFakeEDIDLength, or an unknown type. Nothing bounds the walk to the VBIOS image: usModePatchTableOffset is an unvalidated u16 (:1697-1705) and record strides are attacker-chosen, so the walk crosses the end of the rdev->bios kmalloc'd image and keeps reading heap. The fake-EDID branch (:1717-1737) kmallocs max(128,len) and memcpy's len (<=255) bytes from record+2 β with the record placed at the image end this copies up to 255 bytes of ADJACENT KERNEL HEAP into bios_hardcoded_edid; if drm_edid_is_valid() passes (checksum 1/256 odds on uncontrolled bytes; guaranteed for the in-image part), those bytes become the connector EDID blob, readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR). Harness on guest: record at image end, len=200 -> 200/200 bytes read beyond image; walk continues 201+ bytes past end until garbage type. Kernel path unreachable on this guest.
Reproduce
./build.sh # cc -O1 -g -Wall -o <bin> harness_lvds.c (DragonFly guest, cc 8.3) ./run.sh # expected: fake-EDID len=200: 200/200 bytes read beyond image end; walk continues past end until garbage record type; exit_code=0
The harness replicates the kernel parse arithmetic VERBATIM (cited path:line) against a synthetic crafted-VBIOS buffer with canary-guarded destinations.
Guest / environment
Guest has NO AMD/radeon GPU (virtio-only); /boot/kernel/radeon.ko exists (code shipped in the default install) but cannot attach, so the kernel code path is unreachable at runtime on this guest. Evidence is source-level certainty (path:line traced) plus a verbatim-arithmetic userland harness compiled and executed on the guest (build.log/run.log). See env.txt (uname, cc, kldstat, /boot/kernel/radeon.ko presence).
Fix
bound the walk to the BIOS image (requires tracking the image length in atom_context) β see fix.diff (authored after verification; never applied to sys/).
VERDICT β DF-3744
Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β harness demonstration: YES Impact on real hardware: Medium β CWE-125 Out-of-bounds Read
Finding
radeon_atombios_get_lvds_info() walks the mode-patch record table with while (*record != ATOM_RECORD_END_TYPE) (:1706) β the only exit conditions are the sentinel byte, known record types stepping by fixed sizes or ucFakeEDIDLength, or an unknown type. Nothing bounds the walk to the VBIOS image: usModePatchTableOffset is an unvalidated u16 (:1697-1705) and record strides are attacker-chosen, so the walk crosses the end of the rdev->bios kmalloc'd image and keeps reading heap. The fake-EDID branch (:1717-1737) kmallocs max(128,len) and memcpy's len (<=255) bytes from record+2 β with the record placed at the image end this copies up to 255 bytes of ADJACENT KERNEL HEAP into bios_hardcoded_edid; if drm_edid_is_valid() passes (checksum 1/256 odds on uncontrolled bytes; guaranteed for the in-image part), those bytes become the connector EDID blob, readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR). Harness on guest: record at image end, len=200 -> 200/200 bytes read beyond image; walk continues 201+ bytes past end until garbage type. Kernel path unreachable on this guest.
Why not reproduced in-kernel
The QEMU guest is virtio-only (no AMD GPU): pciconf -l shows no 1002 device;
kldstat shows radeon not loaded. /boot/kernel/radeon.ko exists (the vulnerable code ships in
the default install) but cannot attach, so radeon_atombios.c parse paths never execute here.
All conclusions are source-level (path:line above) plus the on-guest harness (harness_lvds.c).
Harness result
Command: ./run.sh (after ./build.sh) Expected/observed: fake-EDID len=200: 200/200 bytes read beyond image end; walk continues past end until garbage record type; exit_code=0 Full logs: build.log, run.log.
Impact / exploit chain
crafted LVDS_Info usModePatchTableOffset + fake-EDID record at image tail -> 200+ B heap OOB read copied into bios_hardcoded_edid -> (checksum luck) EDID blob exposed to userspace; otherwise walk continues OOB -> crash
Fix
bound the walk to the BIOS image (requires tracking the image length in atom_context) β fix.diff.
Honest notes
- DF-3744 does NOT re-report pass-1 findings DF-1198/1199/1200 (power-table state_index mismatch, asic_ss_info underflow, power-table indices) β different functions/tables/fields.
- fix validation not possible on this guest (no hardware); fix.diff is source-authored only.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
VERDICT β DF-3744
Status: inconclusive (source-certain; harness-reproduced arithmetic; kernel path unreachable on this guest) reproduced: 0 (in-kernel) β harness demonstration: YES Impact on real hardware: Medium β CWE-125 Out-of-bounds Read
Finding
radeon_atombios_get_lvds_info() walks the mode-patch record table with while (*record != ATOM_RECORD_END_TYPE) (:1706) β the only exit conditions are the sentinel byte, known record types stepping by fixed sizes or ucFakeEDIDLength, or an unknown type. Nothing bounds the walk to the VBIOS image: usModePatchTableOffset is an unvalidated u16 (:1697-1705) and record strides are attacker-chosen, so the walk crosses the end of the rdev->bios kmalloc'd image and keeps reading heap. The fake-EDID branch (:1717-1737) kmallocs max(128,len) and memcpy's len (<=255) bytes from record+2 β with the record placed at the image end this copies up to 255 bytes of ADJACENT KERNEL HEAP into bios_hardcoded_edid; if drm_edid_is_valid() passes (checksum 1/256 odds on uncontrolled bytes; guaranteed for the in-image part), those bytes become the connector EDID blob, readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR). Harness on guest: record at image end, len=200 -> 200/200 bytes read beyond image; walk continues 201+ bytes past end until garbage type. Kernel path unreachable on this guest.
Why not reproduced in-kernel
The QEMU guest is virtio-only (no AMD GPU): pciconf -l shows no 1002 device;
kldstat shows radeon not loaded. /boot/kernel/radeon.ko exists (the vulnerable code ships in
the default install) but cannot attach, so radeon_atombios.c parse paths never execute here.
All conclusions are source-level (path:line above) plus the on-guest harness (harness_lvds.c).
Harness result
Command: ./run.sh (after ./build.sh) Expected/observed: fake-EDID len=200: 200/200 bytes read beyond image end; walk continues past end until garbage record type; exit_code=0 Full logs: build.log, run.log.
Impact / exploit chain
crafted LVDS_Info usModePatchTableOffset + fake-EDID record at image tail -> 200+ B heap OOB read copied into bios_hardcoded_edid -> (checksum luck) EDID blob exposed to userspace; otherwise walk continues OOB -> crash
Fix
bound the walk to the BIOS image (requires tracking the image length in atom_context) β fix.diff.
Honest notes
- DF-3744 does NOT re-report pass-1 findings DF-1198/1199/1200 (power-table state_index mismatch, asic_ss_info underflow, power-table indices) β different functions/tables/fields.
- fix validation not possible on this guest (no hardware); fix.diff is source-authored only.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff authored after source-level verification; cannot be validated on this guest (no AMD GPU; radeon.ko cannot attach). Compiling the module or booting a patched kernel would not exercise the path without hardware.
fix.diff (unified, against read-only sys/ tree; never applied)
Confirmed kernel references
Detail
Exploit chain
crafted LVDS_Info usModePatchTableOffset + fake-EDID record at image tail -> 200+ B heap OOB read copied into bios_hardcoded_edid -> (checksum luck) EDID blob exposed to userspace; otherwise walk continues OOB -> crash
Evidence (decisive lines)
harness_lvds.c + build.log + run.log; VERDICT.md; kernel refs sys/dev/drm/radeon/radeon_atombios.c:1691-1752
PoC changes
harness written from scratch (no seed PoC): verbatim kernel arithmetic replica with canary-guarded destination model, built and executed on the DragonFly guest (cc 8.3); fixed nested-comment, stddef/stdint includes and volatile divisors during bring-up
Verified recommended fix
bound the walk to the BIOS image (requires tracking the image length in atom_context)
Verdict
radeon_atombios_get_lvds_info() walks the mode-patch record table with while (*record != ATOM_RECORD_END_TYPE) (:1706) β the only exit conditions are the sentinel byte, known record types stepping by fixed sizes or ucFakeEDIDLength, or an unknown type. Nothing bounds the walk to the VBIOS image: usModePatchTableOffset is an unvalidated u16 (:1697-1705) and record strides are attacker-chosen, so the walk crosses the end of the rdev->bios kmalloc'd image and keeps reading heap. The fake-EDID branch (:1717-1737) kmallocs max(128,len) and memcpy's len (<=255) bytes from record+2 β with the record placed at the image end this copies up to 255 bytes of ADJACENT KERNEL HEAP into bios_hardcoded_edid; if drm_edid_is_valid() passes (checksum 1/256 odds on uncontrolled bytes; guaranteed for the in-image part), those bytes become the connector EDID blob, readable from userspace (DRM_IOCTL_MODE_GETCONNECTOR). Harness on guest: record at image end, len=200 -> 200/200 bytes read beyond image; walk continues 201+ bytes past end until garbage type. Kernel path unreachable on this guest.
No comments yet.