offset_to_id returns success for all DDC-family offsets without ever writing *id β dal_gpio_service_create_irq then switches on an uninitialized gpio_id; garbage==GPIO_ID_HPD smuggles DDC-line en=6/7 into hpd_regs[6] (OOB)
Summary
All eight DDC branches write only *en; the consumer declares id uninitialized and switches on it - garbage==HPD(3) with a DDC en of 6/7 indexes hpd_regs[6..7] past the 6-entry array and programs MMIO from garbage. Poison-survival proven verbatim on guest (3 offsets); the same fix.diff as DF-4942 structurally eliminates the uninitialized use.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4943 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| harness_dce80.c | β | 18.9 KB | view raw | |
| harness_fixed.c | β | 19.0 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 89 B | view raw | |
| build.log | β | 68 B | view raw | |
| run.log | β | 2.1 KB | view raw | |
| fixrun.log | β | 2.0 KB | view raw | |
| env.txt | β | 419 B | view raw | |
| fix.diff | β | 3.3 KB | view raw | |
| manifest.json | β | 770 B | view raw | |
| verdict.json | β | 3.7 KB | view raw |
DF-4943 β hw_translate_dce80.c: offset_to_id returns success for DDC-family offsets without writing *id β consumer switches on an uninitialized gpio_id
File audited (pass 2): sys/dev/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c
Lines: 172-196. Consumer: gpio_service.c:143-157 (dal_gpio_service_create_irq).
What to run
./build.sh && ./run.sh # harness_dce80.c T1 section (run.log)
Expected (reproduced on the DF 6.5-DEVELOPMENT guest)
== T1: DDC/VGA/I2CPAD offsets return success WITHOUT writing *id == offset_to_id(mmDC_GPIO_DDCVGA_A,0x100) = true en=6(id==(other)) -> BUG(DF-4943): success returned, *id NEVER WRITTEN; consumer switches on uninitialized id offset_to_id(mmDC_GPIO_I2CPAD_A,0x1) = true en=7(id==(other)) offset_to_id(mmDC_GPIO_DDC1_A,0x100) = true en=0(id==(other))
(0x7B7B7B7B poison left in id β the function returns true having only
written *en; the same holds for DDC2..DDC6.)
Kernel chain (code trace)
- VBIOS GPIO pin LUT entry (
usGpioPin_AIndex, arbitrary u16 βbios_parser.c:1819-1820) that decodes to a DDC-family register offset is passed byget_hpd_gpio(dc_link.c:115-118) intodal_gpio_service_create_irqfor a connector's hotplug pin. gpio_service.c:148-149declaresenum gpio_id id; uint32_t en;uninitialized;offset_to_id(line 151) returns true without writingidfor DDC offsets (hw_translate_dce80.c:172-196).- Line 156 passes the indeterminate
idtodal_gpio_create_irq(gpio_service.c:366-391): onlyGPIO_ID_HPD(3)/GPIO_ID_GPIO_PAD(4)proceed; everything else is (correctly) rejected. - If the garbage value is 3 (
GPIO_ID_HPD),dal_gpio_create(service, HPD, enβ{0..7}, β¦)βdal_gpio_service_open(HPD, en)βdefine_hpd_registers( pin, en)(gpio_service.c:258-261) βhpd->regs = &hpd_regs[en](hw_factory_dce80.c:143) withhpd_regs[]sized 6 βen6/7 is an OOB const-array read; the garbageint_status/toggle_filt_cntldescriptors then driveREG_GET/REG_UPDATE_2MMIO (hw_hpd.c:89,110). (id_to_offsetitself would reject HPD enβ₯6 β proven in the harness T5 β but that check sits in a different function than the open path.)
Uninitialized-memory use is MSan-detectable and CWE-457 regardless of which garbage value lands in the slot; the OOB sub-chain additionally requires that value to equal 3 (per-build deterministic, unknown statically).
After fix.diff
T1 shows *id = GPIO_ID_DDC_DATA written on every DDC decode
(fixrun.log: "-> FIXED: *id now written (GPIO_ID_DDC_DATA), consumer guard
accepts"), eliminating the uninitialized read entirely.
Impact ceiling
Same VBIOS-craft precondition as DF-4942 plus a stack-garbage coin flip; worst case OOB rodata read β wild MMIO (DoS). Rated Low because both stages are probabilistic relative to DF-4942's deterministic producer.
DF-4943 VERDICT
Status: reproduced (unit-level contract violation executed on the DF guest); kernel consequence chain proven by code trace, probabilistic final stage.
harness_dce80.c(verbatim copy ofoffset_to_idfromhw_translate_dce80.c:63-208) demonstrates on the guest thatoffset_to_id(mmDC_GPIO_DDCVGA_A|mmDC_GPIO_I2CPAD_A|mmDC_GPIO_DDC1_A, β¦)returns true while leaving the caller'sidat its pre-call poison value β the function's success contract does not include writing*idfor the entire DDC family (lines 172-196). This is exactly the situationdal_gpio_service_create_irq(gpio_service.c:148) creates with its uninitialized locals.- Consumer behavior traced:
dal_gpio_create_irq(gpio_service.c:366-391) only proceeds forid == GPIO_ID_HPD(3)orGPIO_ID_GPIO_PAD(4). With garbage == 3 and a DDC-lineenof 6/7,define_hpd_registers(gpio_service.c:261 β hw_factory_dce80.c:143) computes&hpd_regs[6..7]past the 6-entry array;hw_hpd.c:89/110then issues MMIO from the garbage descriptors. With garbage == 4 the open is rejected (default case, gpio_service.c:271-273) β harmless. Any other garbage β NULL β callers handle. id_to_offset's own HPD enβ₯6 rejection (harness T5:id_to_offset(HPD,en=6)=false) does not protect this path β that check lives in a different function than the create/open flow.- Severity Low: crafted-VBIOS precondition plus stack-garbage value == 3; the deterministic sibling producer is DF-4942.
- Fix (fix.diff) initializes
*idon every DDC decode; harness_fixed shows the consumer now always observesGPIO_ID_DDC_DATAβ the uninitialized use is structurally gone, 0 regressions.
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 sets id=GPIO_ID_DDC_DATA in every DDC-family branch of offset_to_id, so the consumer's id is always initialized on success (fixrun.log: '-> FIXED: id now written (GPIO_ID_DDC_DATA), consumer guard accepts'); dal_gpio_create_irq's switch then deterministically rejects DDC decodes; 0 regressions.
fixrun.log T1 section; fix.diff hunk 1 (eight '+ *id = GPIO_ID_DDC_DATA;' lines).
Confirmed kernel references
- sys/dev/drm/amd/display/dc/gpio/dce80/hw_translate_dce80.c:172
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:148
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:156
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:366
- sys/dev/drm/amd/display/dc/gpio/dce80/hw_factory_dce80.c:61
- sys/dev/drm/amd/display/dc/gpio/dce80/hw_factory_dce80.c:143
- 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 = a DDC register offset, bios_parser.c:1819) -> get_hpd_gpio (dc_link.c:115) -> dal_gpio_service_create_irq -> offset_to_id true, id UNWRITTEN -> switch on garbage id -> garbage==GPIO_ID_HPD(3) -> dal_gpio_service_open(HPD, en=6/7) -> define_hpd_registers -> hpd_regs[6..7] OOB rodata read -> REG_UPDATE MMIO at garbage offsets -> panic/MCE. Probabilistic final stage (stack-garbage dependent).
Evidence (decisive lines)
run.log T1: three DDC offsets each return true with id left at the poison value ('*id NEVER WRITTEN'); T5: id_to_offset(HPD,en=6)=false proves the only route for HPD en>=6 into hpd_regs[] is the uninitialized-id path; fixrun.log: after fix, *id==GPIO_ID_DDC_DATA on all DDC decodes.
PoC changes
n/a β fresh harness (verbatim function copy, real constants).
Verified recommended fix
Write *id (GPIO_ID_DDC_DATA) in every successful offset_to_id DDC-family branch so no consumer can observe an uninitialized id.
Verdict
All eight DDC-family branches of offset_to_id (hw_translate_dce80.c:172-196) return true after writing only *en; dal_gpio_service_create_irq (gpio_service.c:148-156) then feeds the never-written id into dal_gpio_create_irq's switch β a use of an uninitialized stack variable (CWE-457). Reproduced on the DF guest with the verbatim function copy: a poisoned id survives a successful offset_to_id(mmDC_GPIO_DDCVGA_A/I2CPAD/DDC1) call (run.log T1). If the indeterminate slot holds GPIO_ID_HPD(3), a DDC-line en of 6/7 reaches define_hpd_registers, which indexes hpd_regs[6] β an OOB const-array read (array has 6 entries, hw_factory_dce80.c:61-68,143) whose garbage descriptors then drive MMIO (hw_hpd.c:89,110); id_to_offset's own en>=6 rejection (harness T5) sits off the open path and does not intercept it. Kernel-stage execution requires an AMD ASIC + crafted VBIOS (absent in QEMU); the uninitialized-write contract violation itself is proven by execution.
No comments yet.