dce110 hw_translate offset_to_id returns success for all eight DDC-family offsets without writing *id β dal_gpio_service_create_irq switches on an uninitialized gpio_id; garbage==GPIO_ID_HPD smuggles DDC-line en=6/7 into the 6-entry hpd_regs[] (OOB rodata read driving MMIO)
Summary
The 4th uninitialized-*id twin (DF-4943/45/46 family): verbatim harness proves all 8 DDC branches leave *id unwritten (poison preserved) while the controls DO write it; the GPIOPAD cross-domain producer is structurally absent on dce110 (register not defined) - so only the uninit path remains. VBIOS-fed; write-*id + consumer-guard fix (completes the family: all four ASIC generations now filed).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4965 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness_dce110.c | β | 16.5 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 116 B | view raw | |
| b2.sh | β | 134 B | view raw | |
| r2.sh | β | 41 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 4.7 KB | view raw | |
| run.2.log | β | 2.4 KB | view raw | |
| fixrun.log | β | 2.3 KB | view raw | |
| env.txt | β | 232 B | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| README.md | β | 1.1 KB | β raw | |
| verdict.json | β | 4.8 KB | view raw |
DF-4965 β dce110 hw_translate: DDC-family offset_to_id returns success without writing *id (4th uninitialized-id twin)
Reproduce
./build.sh # gcc -O2 -Wall -Wextra (harness + -DFIXED variant)
./run.sh # baseline run + fixed build run
Expected (baseline): all eight DDC-family offsets (0x4869..0x487d, 0x4881,
0x4899) return true with en set 0..7 and id UNWRITTEN (poison preserved)
β dal_gpio_service_create_irq (gpio_service.c:148) proceeds on garbage;
garbage==GPIO_ID_HPD(3) β define_hpd_registers(pin, 6/7) β &hpd_regs[6..7]
OOB past the 6-entry array (hw_factory_dce110.c:57-64).
Expected (fixed build): the eight branches write id=DDC_DATA(0); controls
(GENERIC/HPD/SYNCA/rejects) unchanged.
Files
harness_dce110.cβ verbatim dce110offset_to_id/id_to_offset+ real DCE11 constants;-DFIXEDcompiles the fixed branchesbuild.sh,run.sh,build.log,run.log,run.2.log,fixrun.logenv.txtβ guest uname/compiler/no-AMD-hw prooffix.diffβ dce110*id = GPIO_ID_DDC_DATAhunks + consumer hardeningVERDICT.mdβ full narrativemanifest.json,verdict.json
DF-4965 VERDICT
Status: reproduced (unit-level contract violation executed on the DF guest with the verbatim DCE110 functions); kernel consequence chain proven by code trace, probabilistic final stage. Identical defect family to DF-4943 (dce80) / DF-4945 (dce120) / DF-4946 (dcn10).
What was executed
harness_dce110.c carries verbatim copies of the dce110 offset_to_id
(hw_translate_dce110.c:39-179) and id_to_offset (hw_translate_dce110.c:181-366)
with the real DCE11 register constants taken from
sys/dev/drm/amd/include/asic_reg/dce/dce_11_0_d.h and dce_11_0_sh_mask.h.
The dce110 switch constants are absolute MMIO offsets
(mmDC_GPIO_DDCVGA_A=0x4881, mmDC_GPIO_I2CPAD_A=0x4899, dce_11_0_d.h:1481,1504),
which is exactly the domain the VBIOS stores in GPIO_Pin_LUT.usGpioPin_AIndex
(bios_parser.c:1819-1820, v1 parser used for DCE110-era asics) and which
get_hpd_gpio (dc_link.c:115-118) forwards into dal_gpio_service_create_irq.
The mask arrives as 1 << ucGpioPinBitShift (bios_parser.c:1825-1826) β the
DDC-family branches do not consult it at all.
Guest run (run.log, stable across run.2.log):
- All eight DDC-family offsets (DDC1-0x4869 .. DDC6-0x487d, DDCVGA-0x4881,
I2CPAD-0x4899) return true with
*enset (0..7) and*idUNWRITTEN (poison 0xA11CE0D5 preserved through the call). - Controls prove the methodology: GENERIC/HPD/SYNCA branches do write
*id; PWRSEQ/DEBUG/PAD_STRENGTH/unknown offsets are rejected withfalse.
Kernel consequence chain (code trace)
dal_gpio_service_create_irq (gpio_service.c:148) declares enum gpio_id id;
uninitialized β CWE-457. With garbage id:
- garbage != GPIO_ID_HPD(3) && != GPIO_ID_GPIO_PAD(4) β
dal_gpio_create_irq(gpio_service.c:373-381) returns NULL β benign. - garbage == GPIO_ID_GPIO_PAD(4) β
dal_gpio_service_openrejects it in the switch default (gpio_service.c:271-274) β benign. - garbage == GPIO_ID_HPD(3) β
dal_gpio_create(HPD, en)βdal_gpio_service_open(gpio_service.c:258-262) βcreate_hpd+define_hpd_registers(pin, en)βhpd->regs = &hpd_regs[en](hw_factory_dce110.c:139,142).hpd_regs[]has exactly 6 entries (hw_factory_dce110.c:57-64;number_of_pins[GPIO_ID_HPD]=6, line 171), so en=6 (DDC_VGA) and en=7 (I2C_PAD) index 1 and 2 entries out of bounds β an OOB read ofconstrodata whose garbageint_status/toggle_filt_cntldescriptors then drive MMIO viaREG_GET/REG_UPDATE(hw_hpd.c).
DCE110 differences vs the dce80 twin, both verified on the guest
- (a) No
mmGPIOPAD_Areverse-map case β the register is not even defined in dce_11_0_d.h; feeding the dce80 value 0x183 returnsfalse(T[GPIOPAD]). The deterministic DF-4942 producer (GPIOPAD β en up to 30 β ddc arrays) is structurally absent on dce110. The uninit-id path is the only cross-domain route and is probabilistic (needs garbage == 3). - (b) No
GPIO_ID_GPIO_PADforward-map case inid_to_offsetβ the DF-4944 mask-vs-index defect does not exist here (T: id_to_offset(GPIO_PAD,5) = false). - (c)
ddc_data_regs[]/ddc_clk_regs[]have 8 entries (hw_factory_dce110.c:80-100), consistent with the DDC-line domain 0..7 β in-bounds (no finding).
Fix validation
fix.diff = dce110 hunks (8Γ *id = GPIO_ID_DDC_DATA) + consumer hardening in
dal_gpio_service_create_irq (init id = GPIO_ID_UNKNOWN, reject if still
unknown). The -DFIXED build of the harness applies exactly the 8-branch edit:
fixrun.log shows all eight DDC-family decodes now write id=DDC_DATA(0)
(poison eliminated) with zero regressions on the controls; with the fix,
dal_gpio_create_irq deterministically rejects DDC_DATA (default branch β NULL)
instead of switching on garbage.
Severity
Low (matches DF-4945/DF-4946): crafted-VBIOS precondition + stack-garbage==HPD final stage; worst case OOB rodata read β wild MMIO β panic/attach failure on Tonga/Fiji/Carrizo-class (DCE110) hardware. Not unprivileged-user-reachable on stock systems.
Guest
DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, 2026-07-02), gcc 8.3.
amdgpu modules loaded: 0; AMD PCI devices: 0; /dev/dri absent β kernel-stage
execution of the VBIOS-driven GPIO decode is impossible on this QEMU guest, so
the verbatim translate functions were executed at unit level instead.
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 adds *id = GPIO_ID_DDC_DATA to all eight DDC-family branches of the dce110 offset_to_id plus a consumer guard in dal_gpio_service_create_irq (id = GPIO_ID_UNKNOWN init + reject). The -DFIXED harness build applies exactly the 8-branch edit: fixrun.log shows id=DDC_DATA(0) on every DDC decode (poison eliminated) with zero regressions on the GENERIC/HPD/SYNCA/reject controls; with the fix dal_gpio_create_irq deterministically rejects DDC_DATA (default -> NULL) instead of switching on garbage. No AMD hardware on the guest, so kernel-build validation is impossible; harness-level validation only (same standard as the DF-4945 family).
fix.diff (dce110 hunks + gpio_service.c create_irq guard); fixrun.log (fixed-build run, 8/8 branches write id, 0 regressions).
Confirmed kernel references
- sys/dev/drm/amd/display/dc/gpio/dce110/hw_translate_dce110.c:143
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:148
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:373
- sys/dev/drm/amd/display/dc/gpio/dce110/hw_factory_dce110.c:57
- sys/dev/drm/amd/display/dc/gpio/dce110/hw_factory_dce110.c:139
- sys/dev/drm/amd/display/dc/gpio/hw_hpd.c:89
- sys/dev/drm/amd/display/dc/core/dc_link.c:115
- sys/dev/drm/amd/display/dc/bios/bios_parser.c:1819
Detail
Exploit chain
crafted VBIOS GPIO_Pin_LUT (usGpioPin_AIndex = mmDC_GPIO_DDCVGA_A=0x4881 or mmDC_GPIO_I2CPAD_A=0x4899, bios_parser.c:1819-1826) -> get_hpd_gpio (dc_link.c:115-118) -> dal_gpio_service_create_irq -> offset_to_id true with id UNWRITTEN -> garbage id==GPIO_ID_HPD(3) -> dal_gpio_service_open(HPD, en=6/7) -> define_hpd_registers -> hpd_regs[6..7] OOB rodata read -> REG_UPDATE/REG_GET MMIO at garbage register descriptors -> panic/attach failure on DCE110 hardware. Probabilistic final stage.
Evidence (decisive lines)
run.log lines 'T[DDCVGA] offset_to_id(0x4881,0x100)=true en=6 id=<poison> <== id UNWRITTEN' and 'T[I2CPAD] offset_to_id(0x4899,0x1)=true en=7 id=<poison> <== id UNWRITTEN'; T[GPIOPAD] 0x183->false; fixrun.log: FIXED build writes id=DDC_DATA(0) in all 8 branches, controls unchanged.
PoC changes
fresh harness (verbatim dce110 offset_to_id/id_to_offset copy, real DCE11 constants: absolute MMIO offsets from dce_11_0_d.h, field masks from dce_11_0_sh_mask.h, enums from gpio_types.h); fixed gcc-8 compile issue (enum gpio_sync needs HSYNC_B/VSYNC_B referenced by the verbatim body).
Verified recommended fix
Write id (GPIO_ID_DDC_DATA) in every successful DDC-family branch of dce110 offset_to_id and harden dal_gpio_service_create_irq against decoders that leave id unwritten.
Verdict
The DCE110 copy of offset_to_id (hw_translate_dce110.c:143-167) has the family defect of DF-4943/DF-4945/DF-4946: all eight DDC-family branches (DDC1-0x4869..DDC6-0x487d, DDCVGA-0x4881, I2CPAD-0x4899) return true after writing only en. dal_gpio_service_create_irq (gpio_service.c:148) then switches on the uninitialized id; garbage==GPIO_ID_HPD(3) with a DDC-line en of 6 (DDC_VGA) or 7 (I2C_PAD) reaches define_hpd_registers, which indexes the 6-entry hpd_regs[] (hw_factory_dce110.c:57-64,139,142) out of bounds -- garbage int_status/toggle_filt_cntl descriptors then drive MMIO (hw_hpd.c). Executed on the DF guest with a verbatim copy of the dce110 functions and real DCE11 absolute-offset constants (run.log: all 8 branches true with id untouched at poison 0xA11CE0D5; GENERIC/HPD/SYNCA controls do write *id). Unlike dce80, dce110 has no mmGPIOPAD_A case (register not even defined in dce_11_0_d.h; T[GPIOPAD]=0x183->false) so the deterministic DF-4942 producer is absent, and no GPIO_ID_GPIO_PAD forward-map case (no DF-4944 twin) -- the uninit-id path is the only cross-domain route and is probabilistic. Kernel-stage execution requires a DCE110 ASIC (Tonga/Fiji/Carrizo) + crafted VBIOS (guest has none).
No comments yet.