dce100_link_encoder_create indexes 7/6/6-entry const register tables with unvalidated VBIOS-derived transmitter/channel/hpd_source β OOB const reads become the encoder's MMIO register bases (DF-4312/4319/4352/4375 family twin)
Summary
The link-encoder regs/aux/hpd table lookups are formed directly from encoder_init_data fields originating in the VBIOS object tables with no range validation anywhere on the path; producers emit -1/7/8/9 transmitters, unknown/7/8 channels, and unknown=7 hpd - 8/27 reachable enum values OOB and a craftable all-three-OOB triple. Guest model verified over every producer-reachable value; kernel path hardware-gated (no AMD device). First encoder register access issues wild MMIO at VBIOS-chosen offsets. Bounds-check-and-fail fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4495 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4495.c | β | 7.8 KB | view raw | |
| build.sh | β | 86 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 678 B | view raw | |
| run.log | β | 1.8 KB | view raw | |
| run.2.log | β | 478 B | view raw | |
| env.txt | β | 266 B | view raw | |
| fix.diff | β | 1.0 KB | view raw | |
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 4.5 KB | β raw |
DF-4495 β dce100_link_encoder_create OOB const-table reads (VBIOS-indexed)
File: sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:560-577
Family: DF-4312 (dcn10) / DF-4319 (dce80) / DF-4352 (dce110) / DF-4375 (dce112) twin.
What
dce100_link_encoder_create() indexes three static const register tables
with values taken straight from the GPU VBIOS object tables, with no
validation anywhere on the path:
dce110_link_encoder_construct(enc110, enc_init_data, &link_enc_feature,
&link_enc_regs[enc_init_data->transmitter], /* :573 β 7 entries */
&link_enc_aux_regs[enc_init_data->channel - 1], /* :574 β 6 entries */
&link_enc_hpd_regs[enc_init_data->hpd_source]); /* :575 β 6 entries */
The values are produced by (all in sys/dev/drm/amd/display/dc/core/dc_link.c):
translate_encoder_to_transmitter()(:1006-1069): emitsTRANSMITTER_UNKNOWN(-1) for unknown encoder ids/enum_ids, and 7/8/9 (TRAVIS_CRT/TRAVIS_LCD/UNIPHY_G) for external TRAVIS / UNIPHY3 encoder objects β-1, 7, 8, 9are all OOB forlink_enc_regs[7].get_ddc_line()(:964-1004): emitsCHANNEL_ID_UNKNOWN(0),CHANNEL_ID_DDC_VGA(7),CHANNEL_ID_I2C_PAD(8) β after the- 1these give indices-1, 6, 7, all OOB forlink_enc_aux_regs[6].get_hpd_line()(:925-962): returnsHPD_SOURCEID_UNKNOWN(7) for any connector without a mapped HPD gpio (default arm) β OOB forlink_enc_hpd_regs[6].
The OOB-read garbage is stored as the encoder's register base set; every
subsequent register access on that link encoder issues MMIO at
VBIOS-steerable wild offsets (bring-up hang / machine check during amdgpu
bind / link detection). No userspace-readable leak; no unprivileged or
remote trigger β the attacker model is crafted/flashed VBIOS or a malicious
PCIe device.
Reproduce (guest-side model β no AMD GPU required)
cc -O2 -Wall -o df4495 df4495.c # build.sh ./df4495 # run.sh
Expected decisive output (run.log, re-confirmed in run.2.log):
CONST-TABLE OOB READ CONFIRMED (model): dce100_resource.c:573-575 forms &link_enc_regs[tx], &link_enc_aux_regs[ch-1], &link_enc_hpd_regs[hpd] with unvalidated VBIOS-derived indexes β garbage becomes the encoder's MMIO register bases.
with the per-table tally: 4/11 transmitter, 3/9 channel, 1/7 hpd_source reachable values OOB (8/27), and a concrete craftable triple (TRAVIS_CRT + DDC_VGA + no-HPD β indices 7/6/7, all three OOB at once).
Why not a kernel PoC on the audit guest
env.txt: the guest has 0 AMD/ATI PCI devices; the amdgpu/dce100 path
never executes. Same situation as the accepted family twins
(DF-4352/DF-4375). The defect is source-certain; runtime impact is
hardware-gated.
Fix
fix.diff (git-apply clean against this tree): bounds-check the three
values in dce100_link_encoder_create and return NULL β the caller
(dc_link.c:1184-1190) already handles a NULL link encoder by failing just
that link.
DF-4495 VERDICT
Status: untested (kernel-level) β model-confirmed. Impact: none on this guest (hardware-gated). Confidence: certain (source).
Reproduced?
At the kernel level: no β and it cannot be on this guest. env.txt
records 0 AMD/ATI PCI devices; dce100_create_resource_pool and
dce100_link_encoder_create only execute during amdgpu bind on a DCE10
(Tonga/Topaz) ASIC, which the QEMU guest does not have. This is the same
verification ceiling as the accepted family twins DF-4352 (dce110) and
DF-4375 (dce112).
What was verified:
- Line-accurate source trace. The three index expressions are at
dce100_resource.c:573-575, formed fromenc_init_datafields filled atcore/dc_link.c:1177-1183byget_hpd_line()(:925-962),get_ddc_line()(:964-1004) andtranslate_encoder_to_transmitter()(:1006-1069) β all reading the VBIOS object tables via the bios parser, none validating range. Enum domains frominclude/grph_object_defs.h:45-94; table extents 7/6/6 atdce100_resource.c:232-240,:204-211,:218-225. - Guest-side index-domain model (
df4495.c, compiled with the guestcc 8.3, full output inrun.log/run.2.log): the verbatim index expressions evaluated over every value the three producers can emit β 4/11 transmitter (UNKNOWN(-1),TRAVIS_CRT(7),TRAVIS_LCD(8),UNIPHY_G(9)), 3/9 channel (UNKNOWN(0)β-1,DDC_VGA(7)β6,I2C_PAD(8)β7), 1/7 hpd (UNKNOWN(7)) are OOB β 8/27 reachable values; the concrete craftable triple TRAVIS_CRT+DDC_VGA+no-HPD makes all three lookups OOB simultaneously (indices 7/6/7). - Consequence path:
dce110_link_encoder_constructstores the garbage pointers asenc110->link_regs/aux_regs/hpd_regs; every laterREG_*access on that encoder (link detection, AUX/HPD setup at bind) dereferences them β wild MMIO (hang/machine-check), not a readable leak.
Exploit chain
Crafted/flashed VBIOS (or malicious PCIe GPU) on a DCE10 board declares an
external TRAVIS encoder object, a VGA/I2C-pad DDC line, or a connector
without HPD mapping β amdgpu bind β dc_link_construct fills
enc_init_data with UNKNOWN/out-of-range enum values β
dce100_link_encoder_create performs the OOB const-table reads β bogus
register bases stored β first encoder register access issues MMIO at
VBIOS-chosen offsets. Requires firmware/hardware control; no unprivileged
or remote path. Severity Low (family precedent).
Fix validation
fix.diff bounds-checks the three values (identical shape to the accepted
DF-4352 fix) and returns NULL; the caller dc_link.c:1184-1190 already
fails that single link cleanly on NULL, so the driver continues with the
remaining links. git apply --check passes against the read-only tree.
Fix not kernel-testable on this guest (no AMD device):
fix_status: not_testable.
Negative checks performed on this file (pass-2 depth)
- All other register-table lookups are bounded:
dce100_tg_offsets[6]indexedi < pipe_count(6)(:977-982); mi/ipp/xfm/opp/aux/i2c regs[6] indexedi < pipe_count/i < num_ddc(6)(:989-1036);clk_src_regs[3]withclk_src_count β {2,3}(:884-906);audio_regs[7]withinst β€ min(pipe_count, num_audio)=6(dc_resource.c:232-247);stream_enc_regs[7]witheng_id β€ num_stream_encoder=6(dc_resource.c:252-257). Only the three link-encoder lookups are VBIOS-indexed. destruct()(:678-745) loopspipe_count/num_ddc/stream_enc_count/clk_src_count/audio_count, all set before the allocations they bound and all NULL-guarded β no double-free/UAF on the unwind path.- The i2c 1-based-regs vs 0-based-slot pairing (:614-621 vs :1030) is the
upstream-intended DC_I2C engineβDDC-select mapping
(
dce_i2c_hw.c:282), not an off-by-one. dce100_validate_global/validate_surface_sets(:789-818):stream_status[i]is maintained in lockstep withstreams[i]by the dc core;plane_states[0]is only read whenplane_count == 1. No join arithmetic exists for DCE10 (no underlay/MPC joins).populate_dmldoes not exist for DCE10 (no DML);dce100_validate_bandwidthhardcodes clocks (:777-784) β functional TODO only.
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 git-apply-clean against the read-only sys/ tree; adds the three-range check before dce110_link_encoder_construct and frees enc110 on violation; caller dc_link.c:1184-1190 already fails only that link on NULL, matching the accepted DF-4352 fix shape. Kernel-level validation impossible on this guest (0 AMD PCI devices).
fix.diff; git apply --check output 'DF4495_APPLY_OK'; VERDICT.md 'Fix validation' section
Confirmed kernel references
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:573
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:574
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:575
- sys/dev/drm/amd/display/dc/dce100/dce100_resource.c:232
- sys/dev/drm/amd/display/dc/core/dc_link.c:1177
- sys/dev/drm/amd/display/dc/core/dc_link.c:925
- sys/dev/drm/amd/display/include/grph_object_defs.h:81
Detail
Exploit chain
crafted or foreign VBIOS on a Tonga/Topaz (DCE10) ASIC declares an external TRAVIS encoder, a VGA/I2C-pad DDC line, or a connector without HPD mapping -> amdgpu bind -> dc_link_construct fills enc_init_data with UNKNOWN/out-of-range enum values -> dce100_link_encoder_create performs the OOB const-table reads -> stored bogus register bases -> subsequent encoder programming issues MMIO at VBIOS-chosen offsets (hang/machine-check). Requires firmware/hardware control; no unprivileged or remote path.
Evidence (decisive lines)
run.log + run.2.log ('CONST-TABLE OOB READ CONFIRMED (model)', 8/27 reachable values incl. -1/7/8/9 transmitter and 7/6/7 triple); VERDICT.md (line-accurate domain proof + negative checks); env.txt (0 AMD GPU on guest); fix.diff (git apply --check clean, caller dc_link.c:1187-1190 handles NULL)
PoC changes
No executable kernel PoC possible on this guest (no AMD device). Authored df4495.c: replica of the three const-table extents with the verbatim :573-575 index expressions evaluated over the full producer-reachable enum domains from grph_object_defs.h.
Verified recommended fix
Bounds-check transmitter in [UNIPHY_A, NUTMEG_CRT], channel in [DDC1, DDC6], hpd_source in [HPD_SOURCEID1, HPD_SOURCEID6] in dce100_link_encoder_create and return NULL on violation (see fix.diff).
Verdict
Source-certain, hardware-gated (DF-4312/4319/4352/4375 family twin at the DCE10 instance). dce100_link_encoder_create (dce100_resource.c:560-577) indexes link_enc_regs[7] (:232-240) with transmitter (reachable -1 via TRANSMITTER_UNKNOWN from dc_link.c:1006-1069 default arms, and 7/8/9 via TRAVIS_CRT/TRAVIS_LCD/UNIPHY_G), link_enc_aux_regs[6] (:204-211) with channel-1 (reachable -1 via CHANNEL_ID_UNKNOWN default from dc_link.c:964-1004, and 6/7 via DDC_VGA/I2C_PAD), and link_enc_hpd_regs[6] (:218-225) with hpd_source (reachable 7 via HPD_SOURCEID_UNKNOWN default from dc_link.c:925-962) - all from the GPU VBIOS object tables, unvalidated. The OOB const reads become the link encoder's MMIO register bases -> VBIOS-steerable wild MMIO on every later encoder register access (bind-time hang/machine check); no userspace-readable leak. Kernel path untestable on the guest (pciconf: 0 AMD/ATI devices, amdgpu module present but never binds). Guest model (cc 8.3) reproduces the verbatim index expressions over the real enum domains: 8/27 reachable values OOB incl. the all-three-OOB craftable triple TRAVIS_CRT+DDC_VGA+no-HPD (7/6/7).
No comments yet.