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

Unvalidated 16-bit BIOS offsets dereferenced in LVDS mode-table and GPIO voltage-table parsing (OOB read up to 64KB)

Summary

radeon_combios_get_lvds_info at :1246-1272: tmp=RBIOS16(lcd_info+64+i*2) is 16-bit offset into BIOS, then RBIOS16(tmp)/RBIOS16(tmp+2)/RBIOS8(tmp+23)/RBIOS16(tmp+28) etc with NO bounds check vs BIOS allocation. Attacker controls hdisplay/vdisplay (from BIOS) to match, then leaked u32s land in lvds->native_mode -> DRM mode-info ioctl to unprivileged user. radeon_combios_get_power_modes at :2754-2760: voltage_table_offset=RBIOS16 unchecked, dereferenced for gpio.reg/gpio.mask. Crafted VBIOS with tmp=0xF000 against 512-byte allocation. Fix: check tmp+29<=bios_size before deref.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1252 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict source trace, both OOB-read paths, fix rationale 3.4 KB ↓ raw
fix.diff suggested-fix add bios_size field + radeon_bios_in() bounds checks at LVDS and voltage-table paths 4.2 KB view raw
fix_build_radeon.log build-log radeon_bios.o, radeon_combios.o compiled with fix, RC=0, no warnings 1.3 KB view raw
build.sh build-log repro: apply-check + note 406 B view raw
run.sh run-log no runtime trigger (no HW) 396 B view raw
env.txt environment uname, cc version, module state 359 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 source trace, both OOB-read paths, fix rationale
↓ download raw

DF-1252 β€” Unvalidated 16-bit BIOS offsets dereferenced in LVDS mode-table and GPIO voltage-table parsing

Verdict

SOURCE-CONFIRMED (real bug), INCONCLUSIVE at runtime β€” the radeon.ko module is not loaded (no AMD GPU with legacy COMBIOS in the QEMU guest) and is not in the GENERIC kernel, so the unvalidated-offset paths are dormant. Fix authored and compile-validated.

Mechanism (source trace)

RBIOS16(i) / RBIOS8(i) are raw byte-indexing macros into the cached BIOS blob with no bounds check (sys/dev/drm/radeon/radeon.h:2686-2688):

#define RBIOS8(i)  (rdev->bios[i])
#define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i)+1) << 8))
#define RBIOS32(i) ((RBIOS16(i)) | (RBIOS16((i)+2) << 16))

rdev->bios is a kmalloc(size, …) blob (radeon_bios.c:68,98,269,717) whose size is the ROM image length (variable; never stored on the struct). Two parser paths read a 16-bit offset from the BIOS and then dereference it without validating it lies within the blob:

Path 1 β€” LVDS native-mode table (radeon_combios.c:1246-1272):

for (i = 0; i < 32; i++) {
    tmp = RBIOS16(lcd_info + 64 + i * 2);   /* tmp = attacker/BIOS-controlled 16-bit offset */
    if (tmp == 0) break;
    /* NO bounds check that tmp < bios_size */
    if ((RBIOS16(tmp) == hdisplay) &&        /* derefs bios[tmp], bios[tmp+1] */
        (RBIOS16(tmp + 2) == vdisplay)) {     /* derefs bios[tmp+2], bios[tmp+3] */
        ... RBIOS16(tmp + 17), RBIOS16(tmp+19), RBIOS16(tmp+21), RBIOS8(tmp+23),
            RBIOS16(tmp+24), RBIOS16(tmp+26), RBIOS16(tmp+28), RBIOS16(tmp+9) ...

With tmp up to 0xFFFF and the BIOS blob often < 64KB, every RBIOS*(tmp+N) reads up to ~64KB past the allocation β†’ kernel heap OOB read. The leaked u32s land in lvds->native_mode (htotal/vtotal/clock/etc.) which are exposed to userspace via the DRM mode-info ioctl.

Path 2 β€” GPIO voltage table (radeon_combios.c:2754-2760):

u16 voltage_table_offset = RBIOS16(offset + 0x5 + 0xc);   /* unchecked 16-bit offset */
if (entries && voltage_table_offset) {
    ... RBIOS16(voltage_table_offset) * 4;                 /* derefs bios[vto], bios[vto+1] */
        RBIOS8(voltage_table_offset + 0x2);                /* derefs bios[vto+2] */

Same OOB-read pattern.

Why not reproduced at runtime

  • radeon.ko is not loaded on the guest and is not in X86_64_GENERIC.
  • Requires an AMD Radeon GPU with a legacy COMBIOS (pre-AtomBIOS ATI chips) attached; QEMU guest has no AMD GPU.
  • The BIOS image is read from the card's ROM at probe time; a malicious PCI card (VFIO passthrough) could supply a crafted ROM. Not unprivileged-user-triggered.

Fix (fix.diff, compile-validated)

  1. Add size_t bios_size; to struct radeon_device (radeon.h) and a radeon_bios_in(rdev, off, len) bounds-check helper.
  2. Set rdev->bios_size at every allocation site in radeon_bios.c (and clear it on the NULL error paths).
  3. Guard both cited paths in radeon_combios.c: skip the entry if !radeon_bios_in(rdev, tmp, 30) (LVDS) / !radeon_bios_in(rdev, voltage_table_offset, 3) (voltage table).

The patched radeon.h, radeon_bios.c, and radeon_combios.c all compile cleanly with gcc 8.3, -Werror, no warnings.

Realistic impact ceiling

Kernel heap OOB read (up to ~64KB) of attacker/BIOS-influenced data, with partial leak to userspace via DRM mode ioctls, triggered by a malicious PCI card's crafted ROM at GPU probe. On this guest: not reachable (no AMD GPU). Driver/hardware-trust hardening fix.

Fix verification

not_testable

compile validated

module/object build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. RBIOS16/8 raw offset no bounds -> OOB read. radeon not in GENERIC, no AMD GPU.