Five spread-spectrum table parsers dereference unchecked GET_IMAGE() results β NULL-page kernel panic at amdgpu/dc attach from a crafted/corrupt VBIOS
Summary
GET_IMAGE() returns NULL when the table pointer plus the struct size leaves the VBIOS image. Five SS parsers fetch the full struct and immediately dereference ->sHeader.usStructureSize with no NULL check. Reachability: the master-data-table SS pointer is a raw VBIOS u16; point it at bios_size-10 so the revision probe succeeds while the full struct fetch fails -> NULL -> panic. Every sibling parser in the file checks. VERIFIED: logic replica executes the exact bounds semantics and SIGSEGVs on the replicated deref; 5-guard fix compile-validated by a full amdgpu.ko -Werror rebuild. Attacker who controls the VBIOS image on an AMD GPU: deterministic kernel NULL-page panic during attach (DoS).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3942 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| bios_ss_null_deref_replica.c | β | 5.4 KB | view raw | |
| build.sh | β | 131 B | view raw | |
| run.sh | β | 112 B | view raw | |
| build.log | β | 304 B | view raw | |
| run.log | β | 619 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| fix_build.log | β | 423 B | view raw | |
| VERDICT.md | β | 4.2 KB | β raw | |
| README.md | β | 1.4 KB | β raw | |
| verdict.json | β | 4.6 KB | view raw | |
| manifest.json | β | 1.5 KB | view raw |
DF-3942 β bios_parser.c: five SS-table parsers dereference unchecked GET_IMAGE() results (NULL-deref panic)
File: sys/dev/drm/amd/display/dc/bios/bios_parser.c (pass 2)
Severity: Low Β· Confidence: certain (source level) Β· Hardware-gated (needs an AMD GPU + crafted/corrupt VBIOS)
Build
cc -O2 -Wall -o /tmp/bios_ss_null_deref_replica bios_ss_null_deref_replica.c
Run
/tmp/bios_ss_null_deref_replica
Expected
This is a userland logic replica of the exact kernel decision path
(same bios_get_image() bounds semantics, same probe order). It prints the
kernel-side trace (4-byte revision probe succeeds at a table offset 10 bytes
from the image end; full-struct GET_IMAGE returns NULL) and then executes
the replicated deref β SIGSEGV (rc=139), demonstrating the NULL is reached.
In the kernel the same load faults the NULL page β panic during
amdgpu/dc attach. See run.log.
The in-kernel path cannot run on the audit guest: there is no AMD/ATI PCI device and no amdgpu module (verified, env.txt).
Contents
bios_ss_null_deref_replica.cβ logic replica of bios_parser.c:1028-1048build.sh/run.sh,build.log/run.logβ untrimmedfix.diffβ five NULL guards, git-apply-able, compile-validated by a full amdgpu.ko rebuild on the guest kernel tree (fix_build.log, -Werror, rc=0)VERDICT.md,manifest.json,verdict.json
DF-3942 VERDICT β unchecked GET_IMAGE() NULL derefs in bios_parser.c SS parsers
Bottom line
inconclusive at runtime (no AMD GPU on the audit guest); the reachable
NULL dereference is proven at source level and by an executed logic replica
of the exact kernel decision path. With a crafted or corrupt VBIOS whose
spread-spectrum table pointer points near the end of the ROM image, the
kernel dereferences a NULL GET_IMAGE() result and panics during driver
attach.
The five sites
GET_IMAGE(type, off) is (type *)bios_get_image(bp, off, sizeof(type))
(bios_parser_helper.h) and returns NULL when off + sizeof(type) leaves the
image (bios_parser_helper.c:36-44). All five functions below fetch the
4-byte revision probe successfully (which gates entry), then fetch the full
struct without checking for NULL and immediately dereference
->sHeader.usStructureSize:
| # | function | GET_IMAGE | deref |
|---|---|---|---|
| 1 | get_ss_info_from_internal_ss_info_tbl_V2_1 |
bios_parser.c:1041 | :1046 |
| 2 | get_ss_info_from_ss_info_table |
bios_parser.c:1114 | :1140 |
| 3 | get_ss_entry_number_from_ss_info_tbl |
bios_parser.c:1645 | :1671 |
| 4 | get_ss_entry_number_from_internal_ss_info_tbl_v2_1 |
bios_parser.c:1723 | :1726 |
| 5 | get_ss_entry_number_from_internal_ss_info_tbl_V3_1 |
bios_parser.c:1759 | :1761 |
Reachability (site 1 traced; the others are the same shape):
bios_parser_construct()accepts anyusMasterDataTableOffsetwhose ATOM_MASTER_DATA_TABLE fits (bios_parser.c:2898-2903);master_data_tbl->ListOfDataTables.AsicInternalSS_Infois a raw VBIOS u16 β a crafted VBIOS points it atbios_size - 10;bios_parser_get_spread_spectrum_info()(called during DC init / SS setup) probesGET_IMAGE(ATOM_COMMON_TABLE_HEADER, off)(bios_parser.c:952-954) β 4 bytes fit, the header claims rev 2.1 β gate passes;get_ss_info_from_tbl()dispatches to site 1;GET_IMAGE(ATOM_ASIC_INTERNAL_SS_INFO_V2, off)needs4 + sizeof(ATOM_ASIC_SS_ASSIGNMENT_V2)bytes β only 10 remain β NULL;- line 1046 loads
header->sHeader.usStructureSizefrom NULL β panic.
Every sibling parser in the file does check: get_gpio_pin_info
(:1802), get_gpio_i2c_info (:1852), get_embedded_panel_info_v1_2
(:1233), get_integrated_info_v8/v9 (:2248/:2398). The five missing checks
are plain omissions (upstream added them in later revisions).
Impact: deterministic kernel NULL-page dereference β panic at amdgpu/dc attach (or first SS query). Attacker model: crafted VBIOS (flashed ROM, passthrough ROM override, or bit-rotted image). DoS only β no info leak, no write. Severity Low per the project rubric (crafted-VBIOS-gated DoS), consistent with the class of DF-3741/DF-3745.
What ran on the guest
- Hardware absence documented: no 1002: PCI device, no drm/amdgpu KLD.
bios_ss_null_deref_replica.c(build.log, run.log): replicatesbios_get_image()and the probe order exactly; prints the trace, then executes the replicated deref and dies with SIGSEGV (rc=139) β the same load the kernel would take. This demonstrates the NULL is reachable; it is honestly labeled a replica, not the in-kernel path.- Fix compile-validated:
fix.diff(5 NULL guards) applied to the guest's/usr/src,amdgpu.kofully rebuilt with-Werror, rc=0 (fix_build.log). Runtime baseline/patched comparison impossible without an AMD card β fix_status not_testable for that reason only.
Kernel references
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1041,1046
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1114,1140
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1645,1671
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1723,1726
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1759,1761
- sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:36-44 β GET_IMAGE semantics
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 (5 NULL guards) applied to guest /usr/src/sys; full amdgpu.ko module rebuilt under -Werror with rc=0 (fix_build.log). Runtime baseline/patched comparison impossible: no AMD GPU on the guest.
fix_build.log (bios_parser.c recompiled, amdgpu.ko relinked, AMDGPU_FIX_MAKE_RC=0); fix.diff is git-apply-able (git apply --check passed on the audit tree)
Confirmed kernel references
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1041
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1046
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1114
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1140
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1645
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1671
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1723
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1726
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1759
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1761
- sys/dev/drm/amd/display/dc/bios/bios_parser_helper.c:36
Detail
Evidence (decisive lines)
run.log (replica: revision probe ok at off=bios_size-10, struct GET_IMAGE NULL, SIGSEGV on replicated deref); VERDICT.md (full per-site trace); fix_build.log (amdgpu.ko rebuilt with the 5 NULL guards, -Werror, rc=0)
PoC changes
Authored as a logic replica (no seed existed): exact copies of bios_get_image() bounds semantics and the revision-gate order, packed ATOM struct layouts from atombios.h, live NULL load (first iteration was DCE-eliminated; fixed with noinline + observable result)
Verified recommended fix
Add NULL checks after each of the five GET_IMAGE() calls (fix.diff, matching the file's own existing checks)
Verdict
Hardware-gated: the audit guest has no AMD/ATI PCI device and no amdgpu KLD, so the in-kernel path cannot execute β runtime reproduction inconclusive on this machine. The NULL dereference is proven at source level and by an executed userland logic replica of the exact kernel decision path (bios_get_image bounds semantics + probe order): with a crafted VBIOS whose AsicInternalSS_Info u16 points 10 bytes before the image end, the 4-byte revision probe at bios_parser.c:952 succeeds and gates rev 2.1 entry, the full-struct GET_IMAGE at :1041 returns NULL, and :1046 loads header->sHeader.usStructureSize from NULL -> kernel panic at amdgpu/dc attach. Five sites share the omission (:1041/:1046, :1114/:1140, :1645/:1671, :1723/:1726, :1759/:1761); every sibling parser in the file checks. The replica run.log shows the reached NULL (SIGSEGV rc=139).
No comments yet.