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

dce110_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 twin)

Summary

Three const-table lookups use enums from the VBIOS object tables with no validation: transmitter -1/7..9 into link_enc_regs[7], channel-1 -1/6/7 into aux_regs[6] (the -1 shift is unique to this instance), hpd_source 6..7 into hpd_regs[6]. The OOB reads yield garbage register-offset structs stored as the encoder's MMIO bases - VBIOS-steerable wild MMIO. Guest model: 9/28 reachable values index OOB including index -1 twice. Bounds-check-return-NULL fix (caller handles NULL).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4352 Β· 9 files
FileTypeDescriptionSize
df4352.c β€” 6.5 KB view raw
build.sh β€” 86 B view raw
run.sh β€” 89 B view raw
build.log β€” 496 B view raw
run.log β€” 1.5 KB view raw
env.txt β€” 966 B view raw
fix.diff β€” 1.0 KB view raw
VERDICT.md β€” 2.8 KB ↓ raw
README.md β€” 1.3 KB ↓ raw

DF-4352 β€” dce110_link_encoder_create indexes 7/6/6-entry const register tables with unvalidated VBIOS-derived transmitter/channel/hpd_source (DF-4312/4319 twin)

Build / Run (guest model)

cc -O2 -Wall -o df4352 df4352.c
./df4352

Expected: CONST-TABLE OOB READ CONFIRMED (model) β€” 9 of 28 reachable enum values index out of bounds: - link_enc_regs[transmitter] (7 entries): TRANSMITTER_UNKNOWN(-1), TRAVIS_CRT(7), TRAVIS_LCD(8), UNIPHY_G(9) - link_enc_aux_regs[channel-1] (6 entries): CHANNEL_ID_UNKNOWN(0)β†’[-1], DDC_VGA(7)β†’[6], I2C_PAD(8)β†’[7] - link_enc_hpd_regs[hpd_source] (6 entries): COUNT(6), UNKNOWN(7)

Root cause

dce110_resource.c:588-590 performs the three lookups with values sourced at link bring-up from the VBIOS object tables (dc_link.c:1177-1183 β€” get_ddc_line, get_hpd_line, translate_encoder_to_transmitter, each with UNKNOWN defaults) and no bounds checks. An OOB const read yields a garbage register-offset struct that becomes the encoder's MMIO base for every later register access β€” VBIOS-steerable wild MMIO. Trust boundary identical to DF-4216/DF-4312/DF-4319 (crafted/foreign VBIOS).

Fix

fix.diff (git apply --check clean): bounds-check all three and return NULL β€” the caller (dc_link.c:1187-1190) already fails the link cleanly on NULL.

VERDICT.md
↓ download raw

DF-4352 VERDICT

Status: untested (kernel path) β€” code defect certain; guest model enumerates every reachable OOB index.

Root cause (line-accurate)

dce110_link_encoder_create (sys/dev/drm/amd/display/dc/dce110/ dce110_resource.c:576-592):

588:  &link_enc_regs[enc_init_data->transmitter],      /* 7 entries :259-267 */
589:  &link_enc_aux_regs[enc_init_data->channel - 1],  /* 6 entries :230-237 */
590:  &link_enc_hpd_regs[enc_init_data->hpd_source]    /* 6 entries :244-251 */

The three indices are VBIOS-derived, filled in dc_link.c at link construct:

  • transmitter β€” translate_encoder_to_transmitter (dc_link.c:1006-1024+) returns TRANSMITTER_UNKNOWN (-1) for unrecognized encoder ids, and external/NUTMEG/TRAvis encoders and UNIPHY_G map to 6..9 (grph_object_defs.h:81-94). Table domain: 0..6.
  • channel β€” get_ddc_line (dc_link.c:964-1004) returns CHANNEL_ID_UNKNOWN (0) by default, CHANNEL_ID_DDC_VGA (7) and CHANNEL_ID_I2C_PAD (8) explicitly. With the - 1 shift at :589 the reachable indices are -1, 6, 7 against a 6-entry table (grph_object_defs.h:58-69).
  • hpd_source β€” get_hpd_line (dc_link.c:925-962) returns HPD_SOURCEID_UNKNOWN (7) when no HPD gpio exists; COUNT=6. Both are OOB against the 6-entry table (grph_object_defs.h:45-55).

None of the three is validated anywhere on the path. The OOB read produces a struct dce110_link_enc_registers-shaped blob of adjacent .rodata which dce110_link_encoder_construct stores; every subsequent register access on that encoder dereferences offsets from it as MMIO addresses β€” VBIOS-chosen wild MMIO (bring-up hang / machine check at minimum, wrong-hardware register pokes at worst). No userspace-readable leak.

Twin of DF-4312 (dcn10, 4-entry tables) and DF-4319 (dce80) β€” same family, different file, different table sizes and a -1 shift unique to the channel lookup.

Verification

  • Kernel path untestable on the audit guest: pciconf (root) lists only i440FX/PIIX + virtio devices β€” no AMD GPU, amdgpu unbindable (env.txt).
  • Guest model df4352.c (cc 8.3; build.log/run.log) replicates the three tables (7/6/6) and the three verbatim index expressions over the real enum domains: 9 of 28 reachable values index OOB, including -1 twice.

Fix validation

Kernel-level validation impossible on this guest. fix.diff (git apply --check clean) bounds-checks all three values and returns NULL; the caller dc_link.c:1187-1190 already handles NULL by failing this link's creation cleanly (link_enc_create_fail).

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

Kernel fix validation impossible on this guest (no DCE11 hardware). fix.diff is git-apply-clean against sys/; the caller dc_link.c:1187-1190 already handles a NULL link encoder by failing this link cleanly, so the added check cannot regress bring-up beyond refusing the malformed VBIOS entry.

fix.diff; caller NULL-handling trace dc_link.c:1184-1190 in VERDICT.md
↓ fix.diffper-fix-DF-4352

Confirmed kernel references

Detail

Exploit chain

crafted or foreign VBIOS on a Carrizo/Stoney APU 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 -> dce110_link_encoder_create performs 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 ('CONST-TABLE OOB READ CONFIRMED (model)', 9/28 table incl. -1/-1/7/8 indices); VERDICT.md (line-accurate domain proof); env.txt (no AMD GPU on guest); fix.diff (git apply --check clean)

PoC changes

No executable kernel PoC possible on this guest. Authored df4352.c: replica of the three const tables with the verbatim index expressions evaluated over the full 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] and return NULL on violation (see fix.diff).

Verdict

Source-certain, hardware-gated (DF-4312/DF-4319 twin at the DCE11 instance). dce110_link_encoder_create (dce110_resource.c:588-590) indexes link_enc_regs[7] with transmitter (reachable -1 via TRANSMITTER_UNKNOWN from dc_link.c:1006-1024+ and 7..9 via TRAVIS_CRT/TRAVIS_LCD/UNIPHY_G), link_enc_aux_regs[6] 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] with hpd_source (reachable 6..7 via HPD_SOURCEID_COUNT/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 (bring-up hang / machine check); no userspace-readable leak. Kernel path untestable on the guest (pciconf: no AMD/ATI device). Guest model (cc 8.3) reproduces the verbatim index expressions over the real enum domains: 9/28 reachable values OOB.