amdgpu atom_context has no bios_size: every VBIOS offset dereference in the ATOM interpreter and master-table parsers is unbounded (OOB heap read up to 128KB; interpreter executes past the image) β central hub fix port of DF-4337 to the amdgpu twin
Summary
The amdgpu twin of DF-4337: struct atom_context carries no bios length and amdgpu_atom_parse receives none - although adev->bios_size ALREADY EXISTS and is set by every amdgpu_bios reader (so the port needs no bios.c changes). Every 16-bit VBIOS offset is dereferenced unvalidated: the master cmd/data/IIO pointers, the interpreter base/header and fetch loop (jumps with no upper bound), the ID operand (~128KB reach), calltable/setdatablock, asic_init FWI, and the twin parsers feeding ~54 downstream call sites. CENTRAL FIX PORT (293-line diff, git-apply clean): add bios_size to atom_context, thread adev->bios_size, gate every dereference behind amdgpu_atom_in_bios. Guest harness: ALL 8 OOB cases SIGSEGV baseline vs clean-reject fixed. Neutralization: DF-4104 mostly (residual caller-size checks), DF-4152/4289 partial, DF-4048 unaffected, bios_parser2 paths NOT covered.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4345 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 7.8 KB | β raw | |
| atom_harness.c | β | 14.3 KB | view raw | |
| atom_harness_fixed.c | β | 11.4 KB | view raw | |
| build.sh | β | 151 B | view raw | |
| run.sh | β | 232 B | view raw | |
| run_fixed.sh | β | 233 B | view raw | |
| build.log | β | 26 B | view raw | |
| run.log | β | 815 B | view raw | |
| fix_run.log | β | 1.1 KB | view raw | |
| env.txt | β | 382 B | view raw | |
| fix.diff | β | 8.8 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.9 KB | view raw |
DF-4345 β amdgpu ATOM interpreter + master-table parsers dereference unvalidated VBIOS offsets (no bios_size)
sys/dev/drm/amd/amdgpu/atom.c is the amdgpu twin of radeon's atom.c
(DF-4337, whose central fix this port mirrors). struct atom_context
carries no bios length, so every 16-bit offset taken from VBIOS content
is dereferenced unbounded:
- master table pointers + IIO pointer at attach: atom.c:1323-1334
- part-number string walk: atom.c:1340-1348 (filed separately as DF-4347)
- IIO table walk + opcode index: atom.c:1280-1294 (filed as DF-4346)
- interpreter table base + header: atom.c:1200-1211
- interpreter fetch loop (jump/switch-movable ptr): atom.c:1228-1249
- ID operand: atom.c:268
- calltable / setdatablock master entries: atom.c:619, 869
- asic_init FWI chain: atom.c:1356-1367
amdgpu_atom_parse_data_header/parse_cmd_header: atom.c:1384-1420
Plumbing for the fix already exists on the amdgpu side:
adev->bios_size (amdgpu.h:860) is set by every reader in
amdgpu_bios.c (:111, :140, :179, :217, :357, :431); only the call at
amdgpu_atombios.c:2037 and the atom_context itself ignore it.
Reproduce (logic level β guest has no AMD GPU)
cc -O1 -g -o atom_harness atom_harness.c ./atom_harness data_header # + 7 more OOB cases -> SIGSEGV/139 each ./atom_harness_fixed data_header # fixed logic -> clean reject, exit 0
run.log (baseline) traps on all 8 OOB cases at the exact kernel
expressions; fix_run.log shows the ported-fix logic rejecting every
case. Kernel-runtime validation requires an AMD card with a crafted
VBIOS (or writable ROM shadow), which QEMU cannot provide.
Fix
fix.diff β add bios_size to atom_context, plumb
amdgpu_atom_parse(card, bios, bios_size) from adev->bios_size, and
gate all bios-offset dereferences behind amdgpu_atom_in_bios()
(including the interpreter fetch loop and the part-number string).
Neutralizes the outer offsets of the DF-4104/DF-4152/DF-4289 family on
the amdgpu side (see VERDICT.md for the per-finding assessment).
DF-4345 VERDICT β amdgpu atom.c has no bios_size (DF-4337 central fix port)
Status: inconclusive (kernel runtime hardware-gated β guest has no AMD GPU; logic-level OOB reproduced on the audit guest, 8/8 cases). Defect: certain. This is the amdgpu twin of DF-4337 (radeon), which this port mirrors 1:1.
Root cause (code-proven)
struct atom_context (amd/amdgpu/atom.h:125-143) has no bios length
member, and amdgpu_atom_parse(struct card_info *, void *)
(atom.h:147) receives no size β even though struct amdgpu_device
already carries bios_size (amdgpu.h:860) and every amdgpu_bios.c
reader sets it (VRAM 256KB at amdgpu_bios.c:111, pci_map_rom size at
:140, ROM-reader length at :179, platform ROM at :217, ATRM 256KB at
:357, VFCT ImageLength at :431). The one-line consumer at
amdgpu_atombios.c:2037 passes only adev->bios.
Meanwhile every ATOM dereference uses unvalidated 16-bit offsets taken from image content:
| Site (sys/dev/drm/amd/amdgpu/atom.c) | Expression |
|---|---|
| 1323-1334 | base=CU16(0x48); CU16(base+0x1E/0x20); CU16(data_table+0x32) β cmd/data/IIO pointers |
| 1340-1347 | idx=CU16(0x6E); str=CSTR(idx); %s + strlcpy walk |
| 1287-1293 | IIO walk CU8(base), atom_iio_len[CU8(base)] |
| 1200, 1209-1211 | base=CU16(cmd_table+4+2*index); header reads |
| 1228-1249 | interpreter fetch op=CU8(ptr++) β ptr moved by jumps (atom.c:756 *ptr = ctx->start + target) and switch walks (atom.c:1004-1022) with no upper bound |
| 268 | val = U32(idx + gctx->data_block) β up to 0x1FFFE (~128KB) |
| 619 | calltable master entry U16(cmd_table+4+2*idx) |
| 869 | setdatablock master entry U16(data_table+4+2*idx) |
| 1356-1367 | asic_init FWI chain CU32(hwi+8/0xC) |
| 1388-1401 | amdgpu_atom_parse_data_header: idx=CU16(data_table+offset), *size=CU16(idx), *data_start=idx β unvalidated |
| 1408-1418 | amdgpu_atom_parse_cmd_header: same |
Note check_atom_bios (amdgpu_bios.c:48-75) only proves
bios_header_start+8 <= size ("ATOM"/"MOTA" magic); it does NOT bounds
any of the pointers above. (It also has its own off-by-one β reads
bios[0x49] after checking only size < 0x49, i.e. size==0x49 β one
byte OOB; amdgpu_bios.c:52-64 β adjacent-file observation, folded into
the port notes.) Unlike radeon, where a 512-byte image is accepted from
bios[2]*512, the smallest amdgpu-sourced image is the VFCT
ImageLength (attacker-influenced only via platform firmware); the
256KB VRAM/ATRM readers make offsets up to 0xFFFC fully reachable
in-allocation, but nothing stops a short VFCT/ROM image (e.g. 4KB)
from coexisting with a 16-bit offset of 0x8000 β OOB read of a heap
kzalloc(size) allocation.
Reproduction (logic level, on the guest)
Guest: DragonFly 6.5-DEVELOPMENT, gcc 8.3 (env.txt). No AMD GPU
(pciconf -l: QEMU 0x1234:1111 only) β amdgpu never attaches β kernel
path unreachable, identical to the DF-4337 situation on the radeon side.
atom_harness.c copies the exact expressions (verbatim
amdgpu_atom_parse_data_header/parse_cmd_header, ID operand,
calltable/setdatablock entries, IIO walk, part-string, asic_init FWI
reads, interpreter fetch loop) into a userland mirror whose bios buffer
ends at a 16MB PROT_NONE guard region. Result (run.log):
case data_header exit=139 (CU16(idx) @ atom.c:1396) case cmd_header exit=139 (CU8(idx+2) @ atom.c:1416) case id_operand exit=139 (U32(idx+db) @ atom.c:268) case master_entry exit=139 (U16(ct+4+2*idx) @ atom.c:619/869) case iio exit=139 (atom_iio_len[CU8]@ atom.c:1291) -> DF-4346 case partstr exit=139 (%s/strlcpy walk @ atom.c:1346-47)-> DF-4347 case asic_fwi exit=139 (CU32(hwi+8) @ atom.c:1362) case fetchloop exit=139 (CU8(ptr++) @ atom.c:1229) case shift exit=0 (UB divergence report) -> DF-4348
8/8 OOB cases deterministically SIGSEGV at the exact kernel expression; identical arithmetic, identical result class in-kernel (heap OOB read / unbounded interpreter fetch past the allocation).
Fix (the DF-4337 port) + validation
fix.diff (293 lines, git apply --check OK) adds int bios_size to
amd/amdgpu/atom.h's atom_context, threads it through
amdgpu_atom_parse(card, bios, bios_size) from
amdgpu_atombios.c:2037 (adev->bios_size β already maintained by all
six amdgpu_bios.c readers, so no amdgpu_bios.c changes are needed,
unlike the radeon fix), and gates every site above behind
amdgpu_atom_in_bios(ctx, off, len) including the interpreter fetch
loop and the part-number string (NUL must exist inside the image).
Validated like DF-4337:
1. git apply --check passes against the pristine tree (3 files).
2. atom_harness_fixed.c re-runs all 9 cases with the fixed logic on
the guest: every OOB case cleanly rejected (ok=0 / ret=1 /
ret=-EINVAL / entry-skip), fix_run.log, all exit 0.
A kernel-build + on-hardware validation is impossible on this guest (no
AMD GPU to attach the patched driver to) β fix_status inconclusive.
Downstream neutralization assessment (amdgpu side)
~38 amdgpu_atom_parse_data_header + ~16 amdgpu_atom_parse_cmd_header
call sites (amdgpu_atombios.c Γ22, amdgpu_atomfirmware.c Γ9, dpm: ci/kv/si
Γ6, atombios_encoders/crtc, ppatomctrl via smu_helper) get the
outer-offset guarantee (entry offset + declared table size within the
image) from this diff alone:
- DF-4104 (atombios_encoders.c LVDS_Info fixed 0x2C reads at
unvalidated data_offset, atombios_encoders.c:2074-2081): mostly
neutralized β after the gate,
data_offset+size β image; the residual is a table whose declared size < 0x2C whiledata_offset+0x2Cstill ends within the image (read stays in-allocation, merely past the table body β info disclosure within the bios buffer, not heap OOB). Recommend the caller-sidesize >= 0x2Ccheck as noted in DF-4337's verdict. - DF-4152 (amdgpu_atombios.c object-table walks): partially β outer data-table offsets validated; the inner walks (object header β connector/path arrays with their own strides and counts) still need per-site bios_size bounds.
- DF-4289 (ppatomctrl.c SS walk bounded only by its own usStructureSize): partially β same shape: entry offset validated, the walk itself needs a per-site bound.
- DF-4048 (command_table.c ulSymClock 16-bit truncation): not affected β not a bounds defect.
- amdgpu-side bios_parser2.c (DC): not neutralized β never calls amdgpu_atom_parse_data_header (same as the radeon-side DF-4168 note).
Not fixed by this port (orthogonal, already known, do-not-re-report):
DF-1542 (calltable recursion), DF-1543 (FB scratch integer overflow,
atom.c:273/529 β note fb_base + idx*4 still overflows), DF-1544 (PS
operand index, atom.c:218/491), DF-1545 (WS operand index + ws=NULL,
atom.c:256/523). New twins filed with this batch: DF-4346 (IIO index),
DF-4347 (part string), DF-4348 (shifts).
amdgpu vs radeon atom.c differences relevant to the port
Diffed the two files: the interpreter core is byte-identical modulo
printk renames (kprintfβprintk, DRM_INFOβpr_info) and the RV515
ioreg-write quirk (radeon-only). amdgpu adds atom_op_mul32/div32
(64-bit math, benign) and replaces radeon's name[512] message loop
with the vbios_version[20] part-number string (atom.c:1340-1348) β
which is why DF-4339's fix shape does not apply here and DF-4347 exists.
All DF-4337 gate sites map 1:1; the only plumbing difference is that
amdgpu already tracks the allocation length in the device struct.
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
inconclusivefix.diff (293 lines, 3 files) passes git apply --check against the pristine tree; the fixed-logic mirror on the guest rejects all 8 baseline-trapping inputs (ok=0/ret=-EINVAL, exit 0) where the baseline traps (139). A patched-kernel run is impossible on this guest (no AMD/ATOM hardware), so fixed-at-kernel-runtime is not demonstrable; unlike the radeon fix, no amdgpu_bios.c plumbing is required because adev->bios_size already exists.
fix.diff; fix_run.log (all 9 cases exit 0 with clean rejections); VERDICT.md 'Fix (the DF-4337 port) + validation'
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/atom.h:125
- sys/dev/drm/amd/amdgpu/atom.c:268
- sys/dev/drm/amd/amdgpu/atom.c:619
- sys/dev/drm/amd/amdgpu/atom.c:869
- sys/dev/drm/amd/amdgpu/atom.c:1200
- sys/dev/drm/amd/amdgpu/atom.c:1229
- sys/dev/drm/amd/amdgpu/atom.c:1287
- sys/dev/drm/amd/amdgpu/atom.c:1323
- sys/dev/drm/amd/amdgpu/atom.c:1356
- sys/dev/drm/amd/amdgpu/atom.c:1384
- sys/dev/drm/amd/amdgpu/atom.c:1405
- sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:2037
- sys/dev/drm/amd/amdgpu/amdgpu.h:860
- sys/dev/drm/amd/amdgpu/amdgpu_bios.c:140
Detail
Evidence (decisive lines)
run.log: 8/8 OOB cases Segmentation fault exit=139; fix_run.log: all cases rejected exit 0; fix.diff (3 files, git apply --check OK); VERDICT.md site table with path:line + downstream neutralization assessment (DF-4104 mostly, DF-4152/DF-4289 partial, DF-4048 unaffected)
PoC changes
harness adapted from the DF-4337 radeon mirror to the amdgpu twins (renamed functions, vbios_version part-string instead of name[512], asic_init FWI, fetch-loop NOP walk); fixed one comment self-termination bug and EINVAL include on the guest build
Verified recommended fix
Port DF-4337's central fix: add bios_size to amd/amdgpu/atom.h's atom_context, pass adev->bios_size at amdgpu_atombios.c:2037, and gate every bios-offset dereference (both header parsers, interpreter fetch loop, ID/calltable/setdatablock reads, IIO walk, asic_init FWI, part-number string) behind amdgpu_atom_in_bios()
Verdict
Code defect certain: the amdgpu atom_context (amd/amdgpu/atom.h:125-143) has no bios length and amdgpu_atom_parse (amdgpu_atombios.c:2037) passes none -- even though adev->bios_size already exists (amdgpu.h:860) and every amdgpu_bios.c reader sets it (bios_size at :111/:140/:179/:217/:357/:431). Every 16-bit VBIOS offset dereference is unbounded: master/cmd/data/IIO table pointers at atom.c:1323-1334, interpreter base+fetch loop at atom.c:1200/1228-1249 (ptr moved by jumps atom.c:756 and switch walks atom.c:1004-1022), ID operand U32(idx+data_block) at atom.c:268 (reach ~128KB), calltable/setdatablock master entries at atom.c:619/869, asic_init FWI chain at atom.c:1356-1367, and the verbatim twin parsers amdgpu_atom_parse_data_header/amdgpu_atom_parse_cmd_header at atom.c:1384-1420 (idx/size/data_start unvalidated; ~38+16 downstream call sites). Kernel runtime could not be exercised (no AMD GPU on the guest, QEMU bochs vga only); proven instead on the guest by a guard-page userland mirror of the exact expressions: ALL 8 OOB cases (data_header, cmd_header, id_operand, master_entry, iio, partstr, asic_fwi, fetchloop) SIGSEGV deterministically (exit 139, run.log); the DF-4337 port fix logic (bios_size + amdgpu_atom_in_bios gates incl. the fetch loop) rejects every case cleanly (exit 0, fix_run.log).
No comments yet.