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)
PoC verification
Evidence pack
findings/poc/DF-4104 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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
- The VBIOS image is copied into a heap buffer sized from the image itself:
-
amdgpu_read_bios_from_romamdgpu_bios.c:172-179:len = AMD_VBIOS_LENGTH(header); adev->bios = kmalloc(len); adev->bios_size = len;(also amdgpu_read_bios:135-141 withpci_map_romsize, and the ATOM-flash/VRAM paths β the buffer is exactly image-sized, never padded to 64KB/256KB). struct atom_context(atom.h:126-144) has no bios_size member β no atom consumer can even express a bound check.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 u16data_offsetis handed to the caller with no check that data_offset + table-size β€ bios_size.amdgpu_atombios_encoder_get_lcd_info(atombios_encoders.c:2069) passesNULLfor the size out-param (2081-2082) and immediately castslvds_info = (union lvds_info *)(mode_info->atom_context->bios + data_offset)(2083-2084), then performs ~22 fixed-offset reads through 2091-2131 (sLCDTimingfields,usOffDelayInMs,ucLVDS_Misc, mode-misc polarity bits,usModePatchTableOffsetat 2141,ucSS_Idat 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_testablefix.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
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.
No comments yet.