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

LVDS_Info parsed at unvalidated VBIOS data_offset: fixed 0x2C-byte struct reads run past the image-sized BIOS heap buffer (parse_data_header performs no bounds check; atom_context has no bios_size)

Summary

get_lcd_info casts bios+data_offset (u16 from the VBIOS master data table) and reads sizeof(ATOM_LVDS_INFO)=0x2C bytes with no check against the BIOS image size; parse_data_header validates only a non-zero master entry; atom_context carries no bios_size; the buffer is exactly image-sized. A table declared near the end of a 32KB image drives ~44 bytes of OOB heap reads; parsed values become driver state. Distinct from DF-1380 (executes even when ModePatchTableOffset==0, different fix). bios_size-plumbing fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4104 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
verdict.json β€” 2.4 KB view raw
fix.diff β€” 2.0 KB view raw

DF-4104 β€” amdgpu atombios_encoders: LVDS_Info parsed at unvalidated VBIOS offset β€” fixed-struct reads go OOB of the BIOS heap buffer

File: sys/dev/drm/amd/amdgpu/atombios_encoders.c (pass 2) Class: heap OOB read (CWE-125) via unvalidated firmware-table offset Hardware gate: AMD GPU (any asic using this file: DCE8/10/11 amdgpu + the LVDS_Info data-table walk). Source-level certainty. Family: DF-3745 (radeon twin: "Connector/encoder object-table walks deref unvalidated VBIOS offsets"), DF-1408.

Build / Run

Hardware-gated; the audit QEMU guest has no AMD GPU (bochs vga only). Source-level analysis. See VERDICT.md.

Expected (on hardware, with a crafted VBIOS)

amdgpu_atombios_encoder_get_lcd_info() reads ~44 bytes (sizeof(ATOM_LVDS_INFO) = 4-byte header + 28-byte DTD + 12 tail bytes) at bios + data_offset where data_offset is a u16 taken verbatim from the VBIOS master data table (amdgpu_atom_parse_data_header, atom.c:1384-1403, performs NO comparison against the actual BIOS image size β€” struct atom_context (atom.h:126-144) does not even carry a bios_size). The BIOS buffer is sized from the ROM image length (amdgpu_bios.c:172-179 len = AMD_VBIOS_LENGTH(header); kmalloc(len), or amdgpu_read_bios kzalloc(size)). A 32KB image declaring LVDS_Info at data_offset 0x7FF0 β†’ the field reads at atombios_encoders.c:2091-2131 run up to 0x801B β€” past the 32768-byte allocation β†’ OOB heap read of up to ~44 bytes; parsed values (panel timing, ucSS_Id, usModePatchTableOffset) become driver state; at an allocation boundary the unmapped tail faults (panic), otherwise silent read.

Artifacts

  • VERDICT.md, verdict.json (status: untested, hardware absent on guest)
  • fix.diff β€” record the BIOS image size in struct atom_context and bound data_offset + sizeof(ATOM_LVDS_INFO) (and the record walk base) in get_lcd_info. Note: the record walk itself is already known DF-1380; this finding covers the fixed-field reads that execute even when usModePatchTableOffset == 0.
VERDICT.md
↓ download raw

DF-4104 VERDICT

Status: untested (hardware-gated β€” no AMD GPU on the audit guest; source-level certainty certain) Impact: OOB heap read (≀ ~44 bytes + follow-on record-walk reads which are DF-1380's scope) of the kmalloc'd VBIOS image buffer; parsed values become panel mode / SS id state. Panic if the read crosses into an unmapped page.

Root cause chain

  1. The VBIOS image is copied into a heap buffer sized from the image itself: - amdgpu_read_bios_from_rom amdgpu_bios.c:172-179: len = AMD_VBIOS_LENGTH(header); adev->bios = kmalloc(len); adev->bios_size = len; (also amdgpu_read_bios:135-141 with pci_map_rom size, and the ATOM-flash/VRAM paths β€” the buffer is exactly image-sized, never padded to 64KB/256KB).
  2. struct atom_context (atom.h:126-144) has no bios_size member β€” no atom consumer can even express a bound check.
  3. amdgpu_atom_parse_data_header (atom.c:1384-1403) does: int idx = CU16(ctx->data_table + offset); ... *data_start = idx; return true; β€” it validates only that the master-table entry is non-zero. The u16 data_offset is handed to the caller with no check that data_offset + table-size ≀ bios_size.
  4. amdgpu_atombios_encoder_get_lcd_info (atombios_encoders.c:2069) passes NULL for the size out-param (2081-2082) and immediately casts lvds_info = (union lvds_info *)(mode_info->atom_context->bios + data_offset) (2083-2084), then performs ~22 fixed-offset reads through 2091-2131 (sLCDTiming fields, usOffDelayInMs, ucLVDS_Misc, mode-misc polarity bits, usModePatchTableOffset at 2141, ucSS_Id at 2131) β€” spanning sizeof(_ATOM_LVDS_INFO) = 0x2C bytes (atombios.h:3898-3917: 4-byte header + 28-byte ATOM_DTD_FORMAT + 12 tail).

Because data_offset is an unvalidated u16 (0..0xFFFF) and the buffer is image-sized (frequently 32KB/64KB, can be as small as the ROM declares), a crafted VBIOS places the table near the image end and every field read runs past the allocation. This is the amdgpu twin of radeon's DF-3745 family.

Distinction from known DF-1380 (same function): DF-1380 covers the while (*record != ATOM_RECORD_END_TYPE) walk from usModePatchTableOffset (atombios_encoders.c:2156). The reads here (2091-2131) execute before and independently of the record walk (they run even when usModePatchTableOffset == 0), and their fix (bound data_offset against bios_size) is different from DF-1380's fix (bound the record pointer).

Reachability

Boot-time: amdgpu_modeset β†’ encoder init (dce_v10_0.c:3520) β†’ get_lcd_info. Attacker model: crafted VBIOS (root-flashed SPI, malicious replacement eGPU/PCIe option ROM, or a supplied .rom loaded via the Linux-vendor rom sysfs mechanism β€” DFly has no such loader, so physical/ root flash is the gate). Read-only primitive; values feed panel timing and lcd_ss_id (consumed in atombios_crtc.c:789 β€” SS id family).

Why untested

Guest has no AMD GPU; amdgpu never attaches (pciconf shows bochs 1234:1111 only). Line-by-line trace performed instead.

Fix

Plumb bios_size into struct atom_context (set at amdgpu_atombios.c:2037 amdgpu_atom_parse(atom_card_info, adev->bios)), bound-check data_offset + minimum struct size in amdgpu_atom_parse_data_header, and in get_lcd_info verify data_offset + sizeof(union lvds_info) <= bios_size before dereferencing. See fix.diff.

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff bounds-checks the LVDS_Info data_offset (and data-table header) against a newly-plumbed bios_size; not kernel-buildable on the guest because amdgpu cannot attach (no hardware).

findings/poc/DF-4104/fix.diff
↓ fix.diffper-fix-DF-4104

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md steps 1-4; the size=NULL call at atombios_encoders.c:2081; struct layout at sys/dev/drm/amd/include/atombios.h:3898-3917.

PoC changes

No runnable PoC (hardware absent); pack is the source trace + fix.

Verified recommended fix

Plumb bios_size into atom_context and reject data tables whose offset+struct-size exceeds the image (fix.diff).

Verdict

Source-level certain: amdgpu_atombios_encoder_get_lcd_info (atombios_encoders.c:2083-2131) reads sizeof(ATOM_LVDS_INFO) (0x2C) bytes at bios+data_offset where data_offset is an unvalidated u16 from the VBIOS master data table - amdgpu_atom_parse_data_header (atom.c:1384-1403) never compares it against the image size and struct atom_context (atom.h:126-144) carries no bios_size. The bios heap buffer is image-sized (amdgpu_bios.c:172-179,135-141), so a table declared near the end of e.g. a 32KB image drives ~44 bytes of fixed-struct reads past the allocation. Distinct from DF-1380 (record-walk loop) - these reads precede the walk and have a different fix. Untestable on the audit guest: no AMD GPU present.