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

DCE12 copy of the uninitialized-*id defect: offset_to_id returns success for all DDC-family offsets (165-189) without writing *id β€” dal_gpio_service_create_irq switches on an uninitialized gpio_id; garbage==GPIO_ID_HPD smuggles en=6/7 into the 6-entry hpd_regs[] (OOB)

Summary

Identical contract violation in the Vega12-family unit (proven verbatim with the real REG() constants on the guest); VBIOS gpio_pin_lut input. Same fix hunks (write *id in the DDC branches + the consumer domain guard).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4945 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
harness_dce120.c β€” 10.0 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 89 B view raw
build.log β€” 68 B view raw
run.log β€” 979 B view raw
env.txt β€” 419 B view raw
fix.diff β€” 3.3 KB view raw
manifest.json β€” 735 B view raw
verdict.json β€” 3.9 KB view raw

DF-4945 β€” hw_translate_dce120.c: offset_to_id returns success for DDC-family offsets without writing *id (DCE12 copy of DF-4943)

File audited (pass 2): sys/dev/drm/amd/display/dc/gpio/dce120/hw_translate_dce120.c Lines: 165-189. Consumer: gpio_service.c:143-157.

What to run

./build.sh && ./run.sh     # harness_dce120.c (run.log)

harness_dce120.c is a verbatim copy of the DCE12 offset_to_id (hw_translate_dce120.c:61-201) with the real DCE12/Vega10 constants: REG(x) = DCE_BASE__INST0_SEG2(0x34C0) + mm x (vega10_ip_offset.h:277, all DC_GPIO regs BASE_IDX 2) β€” exactly the full-offset domain the VBIOS gpio_pin_lut.data_a_reg_index stores (bios_parser2.c:537-539), e.g. REG(DC_GPIO_DDC1_A)=0x55a7.

Expected (reproduced on the DF 6.5-DEVELOPMENT guest)

offset_to_id(REG(DDCVGA),0x100) = true en=6(id==0x7b7b7b7b UNTOUCHED)
  -> BUG(DF-4945): success, *id never written; garbage id==GPIO_ID_HPD(3)
     => dal_gpio_create(HPD, en=6) with en>=6 indexes hpd_regs[6] OOB
offset_to_id(REG(I2CPAD),0x1)    = true en=7(id==0x7b7b7b7b UNTOUCHED)
  -> BUG(DF-4945): en=7 >= hpd_regs[6]: garbage-id==HPD path is OOB

Kernel chain (code trace)

  1. VBIOS gpio_pin_lut v2.1 entry (data_a_reg_index = a DCE12 DDC-family full offset β€” bios_parser2.c:537-539) for a connector's HPD pin β†’ get_hpd_gpio (dc_link.c:115-118) β†’ dal_gpio_service_create_irq.
  2. gpio_service.c:148-151: id uninitialized; offset_to_id returns true having written only *en (hw_translate_dce120.c:165-189).
  3. Garbage id == GPIO_ID_HPD(3) β†’ dal_gpio_create(HPD, en∈{0..7}) β†’ dal_gpio_service_open β†’ define_hpd_registers(pin, en) β†’ hpd_regs[6..7] OOB (hpd_regs[] has 6 entries, hw_factory_dce120.c:76-83,158) β†’ REG_GET(int_status) / REG_UPDATE_2(toggle_filt_cntl) MMIO from garbage descriptors (hw_hpd.c:89,110). DDC-line values 6/7 (DDC_VGA / I2C_PAD) are the OOB producers; values 0..5 alias valid HPD pins (wrong-pin programming).

DCE120-specific notes

  • No mmGPIOPAD_A case exists in the DCE12 reverse map (verified: harness T2 rejects 0x183) and no GPIO_ID_GPIO_PAD case in id_to_offset β€” the deterministic GPIOPADβ†’DDC OOB producer (DF-4942) does not exist on dce120; only the uninit-*id defect does.
  • The REG() base arithmetic (SEG2=0x34C0) matches the VBIOS full-offset domain β€” the switch constants are correct (negative result).

After fix.diff

The dce120 hunks set *id = GPIO_ID_DDC_DATA in all 8 DDC-family branches plus the dal_gpio_create_ddc consumer guard β€” structurally eliminating the uninitialized id on this family as well.

Impact ceiling

Same as DF-4943 (Low): crafted VBIOS + stack-garbage==HPD; worst case OOB rodata read β†’ wild MMIO β†’ panic at driver attach on Vega10-class ASICs.

VERDICT.md
↓ download raw

DF-4945 VERDICT

Status: reproduced (unit-level contract violation executed on the DF guest with the verbatim DCE12 function); kernel consequence chain proven by code trace, probabilistic final stage.

  • harness_dce120.c carries a verbatim copy of the dce120 offset_to_id (hw_translate_dce120.c:61-201) with real constants. The REG() macro arithmetic was verified against the register headers: REG(x) = DCE_BASE__INST0_SEG2 (0x34C0) + mm x (vega10_ip_offset.h:277; all DC_GPIO regs BASE_IDX 2), yielding e.g. REG(DC_GPIO_DDC1_A)=0x55a7 β€” exactly the full-offset domain VBIOS gpio_pin_lut.data_a_reg_index stores (bios_parser2.c:537-539). So the switch constants are correct; the defect is purely the unwritten *id.
  • Guest execution (run.log): offset_to_id(REG(DC_GPIO_DDCVGA_A), 0x100) β†’ true, en=6, id untouched at poison; offset_to_id(REG(DC_GPIO_I2CPAD_A), 0x1) β†’ true, en=7, id untouched. Consumer dal_gpio_service_create_irq (gpio_service.c:148) declares id uninitialized β†’ CWE-457.
  • Chain for garbage id == GPIO_ID_HPD(3): dal_gpio_create(HPD, 6/7) β†’ dal_gpio_service_open β†’ define_hpd_registers β†’ &hpd_regs[6..7] β€” OOB past the 6-entry array (hw_factory_dce120.c:76-83,158) β†’ REG_GET(int_status) / REG_UPDATE_2(toggle_filt_cntl) MMIO from garbage rodata descriptors (hw_hpd.c:89,110).
  • DCE12 differences vs dce80, both verified: (a) no mmGPIOPAD_A reverse-map case and no GPIO_ID_GPIO_PAD forward-map case β€” the deterministic DF-4942 producer is absent (harness T2: offset 0x183 β†’ false); (b) ddc arrays still 8 entries (DDC1..6 + VGA + I2CPAD), consistent with the DDC enum domain 0..7 (no finding).
  • Fix: dce120 hunks of fix.diff (8Γ— *id = GPIO_ID_DDC_DATA) + consumer guard; validated on the dce80 harness twin (fixrun.log, 0 regressions).
  • Severity Low: crafted-VBIOS precondition + stack-garbage==HPD; worst case OOB rodata read β†’ wild MMIO β†’ panic at attach on Vega10-class hardware. Not unprivileged-user-reachable on stock systems.

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 reproduced→ patch + rebuild →patched clean

fix.diff adds *id = GPIO_ID_DDC_DATA to all eight DDC-family branches of the dce120 offset_to_id and the dal_gpio_create_ddc consumer guard; the same edit applied to the dce80 harness family (harness_fixed) demonstrates the uninitialized id is structurally eliminated and the DDC decode is the only accepted domain β€” 0 regressions (fixrun.log). The dce120 hunks are textual twins of the validated dce80 hunks.

fix.diff hunks for sys/dev/drm/amd/display/dc/gpio/dce120/hw_translate_dce120.c and gpio_service.c; fixrun.log (dce80 model run of the same fix).
↓ fix.diffper-fix-DF-4945

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS gpio_pin_lut v2.1 (data_a_reg_index = REG(DC_GPIO_DDCVGA_A)=0x55bf or REG(DC_GPIO_I2CPAD_A)=0x55d7, bios_parser2.c:537) -> get_hpd_gpio (dc_link.c:115) -> dal_gpio_service_create_irq -> offset_to_id true, 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 MMIO at garbage offsets -> panic/MCE. Probabilistic final stage.

Evidence (decisive lines)

run.log T1: 'offset_to_id(REG(DDCVGA),0x100) = true en=6(id==0x7b7b7b7b UNTOUCHED)' and 'offset_to_id(REG(I2CPAD),0x1) = true en=7(id==0x7b7b7b7b UNTOUCHED)'; T2: no GPIOPAD mapping (0x183 rejected) proving the dce80-style deterministic producer is absent on dce120.

PoC changes

n/a β€” fresh harness (verbatim dce120 offset_to_id copy, real DCE12/Vega10 constants incl. SEG2 base 0x34C0).

Verified recommended fix

Write *id (GPIO_ID_DDC_DATA) in every successful offset_to_id DDC-family branch (dce120 file), matching the dce80 fix.

Verdict

The DCE12 copy of offset_to_id (hw_translate_dce120.c:165-189) has the same defect as DF-4943: all eight DDC-family branches return true after writing only en. dal_gpio_service_create_irq (gpio_service.c:148-156) 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_dce120.c:76-83,158) out of bounds β€” garbage int_status/toggle_filt_cntl descriptors then drive MMIO (hw_hpd.c:89,110). Executed on the DF guest with a verbatim copy of the dce120 function and real DCE12 REG() constants (REG(DDCVGA)=0x55bf, REG(I2CPAD)=0x55d7 β€” run.log T1: both return true with id untouched). Kernel-stage execution requires a Vega10-class ASIC + crafted VBIOS (absent in QEMU). Unlike dce80, dce120 has no GPIOPAD case (T2 negative control), so the deterministic DF-4942 producer does not exist on this family β€” the uninit-id path is the only cross-domain route and is probabilistic.