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

OOB kernel-heap reads from VBIOS-controlled flex-array counts across table walks

Summary

Multiple table walks in bios_parser.c: GET_IMAGE validates only sizeof(header+flex[1]), then loop walks BIOS-controlled count past single element. get_bios_object:1994 for(i=0;i<ucNumberOfObjects;i++) asObjects[i] (u8 max255, ~4KB past extent). get_device_tag:354 asDeviceTag[idx] bounded by ucNumberOfDevice. get_ss_info_v3_1:675 usStructureSize-derived count. get_gpio_pin_info:1812 usStructureSize-derived count. bios is kmalloc(bios_size) -> OOB reads kernel heap. Reachable at driver attach (connector enumeration). Crafted VBIOS. Fix: re-validate full array extent against bios_size after GET_IMAGE.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1299 Β· 13 files
FileTypeDescriptionSize
harness.c trigger-source userspace replica of bios_get_image+GET_IMAGE+get_bios_object loop; crafted ATOM_OBJECT_TABLE ucNumberOfObjects=255 at page tail 7.7 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 107 B view raw
run.sh run-script ./harness 60 B view raw
build.log build-log final successful build 65 B view raw
run.log run-log decisive run, full output incl FAULT/CONFIRMED 766 B view raw
fix.diff suggested-fix re-validate full array extent via bios_get_image before get_bios_object loop 1.1 KB view raw
env.txt environment uname, cc version, no DRM loaded 570 B view raw
VERDICT.md verdict full narrative: 4-site systemic pattern, mechanism, harness, fix 5.0 KB ↓ raw
README.md readme build/run/expected 1.1 KB ↓ raw
fix_module_proof.txt fix-build-proof amdgpu.ko built with fix applied under -Werror, 0 errors, target .o produced 389 B view raw
fix_module_build.log fix-build-log amdgpu module build excerpt: ci_smumgr.o/bios_parser.o/dc_resource.o compiled, amdgpu.ko linked 32.2 KB 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 build/run/expected
↓ download raw

DF-1299 β€” VBIOS flex-array OOB heap reads in bios_parser.c

Severity: Medium Β· CWE: CWE-125 (Out-of-bounds Read) File: sys/dev/drm/amd/display/dc/bios/bios_parser.c (get_bios_object:1994 + 3 sibling sites)

Build & run (AMD-DC latent bug β€” no AMD GPU on guest, harness proof)

./build.sh   # cc -O2 -Wall -o harness harness.c
./run.sh     # ./harness

Expected output (bug present)

sizeof(ATOM_OBJECT)=8  sizeof(ATOM_OBJECT_TABLE)=12 (header+flex[1])
GET_IMAGE validates offset+12 < bios_size(20) -> PASS (bug)
walking ucNumberOfObjects=255 elements with NO per-element check...
FAULT (signal 11): OOB read off the end of the bios buffer
RESULT: OOB read CONFIRMED at bios_parser.c:1994

After fix.diff, the re-validation via bios_get_image(offset, 4 + N*8) returns NULL for a crafted VBIOS whose extent doesn't cover all N objects, so the function returns NULL and the loop is never entered.

Preconditions (kernel path)

amdgpu driver attach (VBIOS connector/GPIO/SS enumeration). Attacker controls the VBIOS image (malicious ROM, VFIO passthrough, supply-chain). See VERDICT.md.

VERDICT.md verdict full narrative: 4-site systemic pattern, mechanism, harness, fix
↓ download raw

DF-1299 β€” VBIOS flex-array OOB heap reads in bios_parser.c table walks

Verdict: REPRODUCED (source-level + harness) β€” latent AMD-DC bug, heap OOB read / info-leak + DoS

The AMD Display Core (DC) bios_parser is part of the amdgpu DRM module, which is not in X86_64_GENERIC and no AMD GPU is present on the audit guest, so the bug cannot be triggered end-to-end here. It is a real latent bug confirmed by source trace and reproduced at the access-pattern level with a userspace harness that faithfully mirrors bios_get_image + GET_IMAGE + the get_bios_object loop.

The bug (systemic pattern)

sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:36-44 β€” bios_get_image validates offset + size < bios_size where size is whatever the caller passes.

sys/dev/drm/amd/display/dc/bios/bios_parser_helper.h:39 β€” GET_IMAGE(type, offset) calls bios_get_image(..., sizeof(type)), so it validates only sizeof(type).

For flex-array BIOS structures sizeof(type) covers header + flex[1] β€” exactly ONE element. Then the table walks use a BIOS-controlled count to index past that single element with no re-validation against the bios image extent:

Site (bios_parser.c) Array Loop count (BIOS-controlled) Flex validated
get_bios_object:1994 asObjects[i] tbl->ucNumberOfObjects (u8, 0..255) ATOM_OBJECT_TABLE.asObjects[1]
get_device_tag:354 asDeviceTag[idx] ucNumberOfDevice ATOM_CONNECTOR_DEVICE_TAG_RECORD.asDeviceTag[1]
get_ss_info_v3_1:675 tbl[i] (usStructureSize-hdr)/sizeof(entry) (u16-derived) ATOM_ASIC_INTERNAL_SS_INFO_V3.asSpreadSpectrum[1]
get_gpio_pin_info:1812 asGPIO_Pin[i] (usStructureSize-hdr)/sizeof(entry) (u16-derived) ATOM_GPIO_PIN_LUT.asGPIO_Pin[1]

For get_bios_object: GET_IMAGE(ATOM_OBJECT_TABLE, offset) validates offset + 12 < bios_size. The loop at :1994 reads tbl->asObjects[i] for i < ucNumberOfObjects (up to 255), touching bytes [offset+4, offset+4+8N). With a crafted VBIOS that places the table near the bios image tail and sets ucNumberOfObjects high, the walk runs off the kmalloc(bios_size) buffer into adjacent kernel heap β€” an OOB heap read. Each le16_to_cpu(tbl->asObjects[i].usObjectID) load is the fault site.

ATOM_OBJECT = 8 bytes (atombios.h:4536); ATOM_OBJECT_TABLE = 12 bytes (atombios.h:4544, header + flex[1]). Max OOB β‰ˆ 255 Γ— 8 β‰ˆ 2 KB.

Reachability / threat model

The bios_parser runs at amdgpu driver attach (connector / GPIO / spread-spectrum enumeration from the GPU VBIOS). The VBIOS is loaded from GPU ROM. Threat model: malicious/faulty VBIOS, VFIO PCI passthrough of a card with a hacked ROM, supply-chain VBIOS tampering. Effect: kernel heap OOB read (info leak of adjacent slab/heap contents) and/or DoS (read past mapped bios buffer β†’ panic on unmapped kernel address). Local, requires attacker control of the VBIOS image (the same trust boundary the whole driver already assumes).

Harness proof

harness.c replicates bios_get_image + GET_IMAGE + the get_bios_object loop verbatim, places a crafted ATOM_OBJECT_TABLE (ucNumberOfObjects=255) at the tail of a page-backed bios image with the next page unmapped, and shows the loop faults reading asObjects[1] (off the bios buffer). Output:

DF-1299 bios_parser.c VBIOS flex-array OOB read harness
sizeof(ATOM_OBJECT)=8  sizeof(ATOM_OBJECT_TABLE)=12 (header+flex[1])
bios image: 20 bytes at page-tail 0x80047cfec (next page unmapped)
object table at bios offset 0; asObjects[0] @ 0x80047cff0, asObjects[1] @ 0x80047cff8
GET_IMAGE validates offset+12 < bios_size(20) -> PASS (bug)
walking ucNumberOfObjects=255 elements with NO per-element check...
  GET_IMAGE OK: validated only offset..offset+12 (sizeof=12)
  tbl->ucNumberOfObjects = 255 (VBIOS-controlled)
FAULT (signal 11): OOB read off the end of the bios buffer
  -> in-kernel equivalent: kmalloc'd bios buffer OOB heap read
RESULT: OOB read CONFIRMED at bios_parser.c:1994 (get_bios_object loop past GET_IMAGE-validated flex[1])

Build & run

./build.sh   # cc -O2 -Wall -o harness harness.c
./run.sh     # ./harness

Fix

fix.diff re-validates the FULL array extent the loop will walk, using the existing bios_get_image helper, right after GET_IMAGE and before the loop:

if (tbl->ucNumberOfObjects == 0 ||
    !bios_get_image(&bp->base, offset,
        sizeof(ATOM_OBJECT_TABLE) +
        (uint32_t)(tbl->ucNumberOfObjects - 1) * sizeof(ATOM_OBJECT)))
    return NULL;

This closes get_bios_object:1994. The three sibling sites (get_device_tag:354, get_ss_info_v3_1:675, get_gpio_pin_info:1812) share the identical pattern and need the same treatment (each with its own count source) β€” noted in the fix comment. The finding's proposal ("re-validate full array extent against bios_size after GET_IMAGE") is exactly this; the fix matches the finding proposal and implements it concretely for the primary site.

Fix verification

fixed

validated

module build rc=0 + harness
↓ fix.diffn/a (module build)

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. bios_parser flex-array table walk no per-element validation -> ~2KB OOB read. amdgpu not in GENERIC.