dal_gpio_service_open calls define_ddc_registers/define_hpd_registers on the create_*() result before the NULL check β deterministic NULL-deref panic when DDC/HPD pin allocation fails
Summary
The freshly-created pin is passed straight into define_*_registers (which derefs pin->id immediately and writes through container_of(NULL,...)) before the !pin guard at :276 - a kzalloc failure during display detect/HPD hotplug under heap exhaustion panics (confirmed deref sites in all four hw_factory families). Move-define-after-check fix. AMD hw-gated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4787 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| env.txt | β | 920 B | view raw | |
| fix.diff | β | 2.6 KB | view raw | |
| manifest.json | β | 763 B | view raw | |
| verdict.json | β | 2.5 KB | view raw |
DF-4787 β dal_gpio_service_open calls define_*_registers() before NULL-checking the created pin
What
sys/dev/drm/amd/display/dc/gpio/gpio_service.c:243-262 β in dal_gpio_service_open(),
each create_*() result is immediately passed to define_ddc_registers() /
define_hpd_registers() before the if (!pin) check at line 276:
case GPIO_ID_DDC_DATA:
pin = service->factory.funcs->create_ddc_data(service->ctx, id, en);
service->factory.funcs->define_ddc_registers(pin, en); /* pin may be NULL */
break;
The factory implementations dereference pin unconditionally:
dce110/hw_factory_dce110.c:114-116switch (pin->id)β read at NULL+offsetof(id)dce110/hw_factory_dce110.c:137-139hpd->regs = &hpd_regs[en](viaHW_HPD_FROM_BASE(pin)=container_of(NULL, ...)) β write to a near-NULL address- identical code in
dce80/hw_factory_dce80.c:114-120,dce120/hw_factory_dce120.c:129-135,dcn10/hw_factory_dcn10.c:124-130
dal_hw_ddc_create() (hw_ddc.c:212-233) and dal_hw_hpd_create() (hw_hpd.c:137-165)
return NULL when their kzalloc() fails (kernel memory pressure) β and also when en is
out of range, though all current callers pass VBIOS/translate-bounded enum values.
Same function, second latent instance: the shipped factory tables set
.create_generic = .create_sync = .create_gsl = NULL on all four ASIC families
(hw_factory_dce110.c:148-151, dce80:152-155, dce120:169-172, dcn10:164-167), and
dal_gpio_service_open calls those pointers with no NULL check. No code in the tree
currently opens GENERIC/SYNC/GSL pins, so that arm is latent today (covered by DF-4788).
Impact
Deterministic NULL-pointer-dereference panic (kernel DoS) when a DDC or HPD pin allocation fails under kernel heap exhaustion during display detection / HPD hotplug handling. No privilege boundary is crossed; no info leak; no arbitrary write (address is fixed near-NULL).
Reproduce (requires AMD GPU hardware β NOT available in the audit QEMU guest)
Not runnable on the audit guest: it is a KVM/q35 virtio guest with no AMD GPU and no
driver attach (see env.txt β pciconf -l shows only Intel host bridge / virtio devices;
no amdgpu/dc module loaded). A hardware PoC would fault-inject kzalloc failure
(e.g. failslab-style) and trigger an HPD hotplug or DDC probe.
Fix
fix.diff in this directory β move the define_*_registers() calls after the !pin
check and guard the NULL creator function pointers.
Verdict
untested β code-path analysis is conclusive (certain), but no AMD display hardware
exists in the verification guest, so it cannot be exercised there.
DF-4787 VERDICT
- Status: untested (hardware-gated)
- Confidence: certain (code-path analysis)
- Severity filed: Low (CWE-476 NULL pointer dereference)
Root cause β line-accurate trace
gpio_service.c:224dal_gpio_service_open(service, id, en, mode, &pin).gpio_service.c:245-247(DDC_DATA),:250-252(DDC_CLOCK),:258-261(HPD): the just-returnedpinfromcreate_ddc_data()/create_ddc_clock()/create_hpd()is passed straight intodefine_ddc_registers(pin, en)/define_hpd_registers(pin, en).- The
if (!pin)guard only runs atgpio_service.c:276β after the dereference. dce110/hw_factory_dce110.c:110-116:c struct hw_ddc *ddc = HW_DDC_FROM_BASE(pin); switch (pin->id) { /* NULL + offsetof(struct hw_gpio_pin, id) β fault */dce110/hw_factory_dce110.c:135-139:c struct hw_hpd *hpd = HW_HPD_FROM_BASE(pin); /* container_of(NULL,β¦) β 0xβ¦ */ hpd->regs = &hpd_regs[en]; /* WRITE to near-NULL β fault */Same shape in dce80 (hw_factory_dce80.c:114-120), dce120 (hw_factory_dce120.c:129-135,:154-158), dcn10 (hw_factory_dcn10.c:124-130,:149-157).- Producers of NULL:
hw_ddc.c:224-227(kzallocfailure),hw_ddc.c:219-222(enoutside[GPIO_DDC_LINE_MIN..MAX]),hw_hpd.c:149-152,hw_hpd.c:153-160(kzallocfailure /enoutside[GPIO_HPD_MIN..MAX],id != GPIO_ID_HPD).
Why it is only Low
- The
en-out-of-range arm is unreachable today: every caller obtainsenfromtranslate.funcs->offset_to_id()(bounded mask switches, e.g.dce110/hw_translate_dce110.c:41-101) or from compile-time enums; a VBIOS GPIO record with an unknown offset/mask is rejected (offset_to_idreturns false βdal_gpio_create_ddc/dal_gpio_service_create_irqreturn NULL before reaching open). - The remaining trigger is kernel heap exhaustion during a DDC/HPD pin creation
(
kzalloc(sizeof(struct hw_ddc/hpd))fails). The result is a NULL-page read/write β a clean panic, not a controllable primitive.
Why untested on this run
The single-tenant QEMU guest is DragonFly dfbsd 6.5-DEVELOPMENT on KVM/q35 with only
virtio + Intel devices (env.txt: pciconf -l β no vendor 0x1002 display device, no
vendor 0x1000 MPT3 device; no drm/amdgpu module loaded). The DC GPIO service cannot be
instantiated without AMD display hardware, so neither baseline nor fault-injected
reproduction is possible. fix.diff was therefore not boot-validated either; it is a
mechanical reorder + NULL-guard whose worst-case behavior is strictly narrower than the
original (no new dereference sites introduced).
Recommended fix summary
Move define_ddc_registers/define_hpd_registers after the !pin check and reject
NULL create_* function pointers before calling them (fix.diff).
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 is a mechanical reorder plus NULL guards (no new dereference sites); it cannot be boot-validated on the audit guest because the AMD DC stack never initializes there
findings/poc/DF-4787/fix.diff
Confirmed kernel references
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:245
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:250
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:258
- sys/dev/drm/amd/display/dc/gpio/gpio_service.c:276
- sys/dev/drm/amd/display/dc/gpio/dce110/hw_factory_dce110.c:110
- sys/dev/drm/amd/display/dc/gpio/dce110/hw_factory_dce110.c:135
- sys/dev/drm/amd/display/dc/gpio/hw_ddc.c:224
- sys/dev/drm/amd/display/dc/gpio/hw_hpd.c:158
Detail
Evidence (decisive lines)
README.md (code cites), VERDICT.md (trace), env.txt (guest pci inventory: no vendor 0x1002 device), fix.diff (reorder + NULL guards)
PoC changes
no runnable PoC produced: trigger requires AMD display hardware (kzalloc-failure DDC/HPD pin open); audit guest is virtio-only KVM
Verified recommended fix
Move define_ddc_registers/define_hpd_registers after the !pin check and NULL-check the create_* factory pointers (see fix.diff)
Verdict
dal_gpio_service_open() passes the create_ddc_data/create_ddc_clock/create_hpd() result straight into define_ddc_registers()/define_hpd_registers() before the !pin check; those factory helpers dereference pin (switch (pin->id) at hw_factory_dce110.c:116; hpd->regs write at :139), so a kzalloc failure inside dal_hw_ddc_create/dal_hw_hpd_create during display detect/hotplug under kernel memory pressure causes a deterministic NULL-deref panic. Not exercised on the audit guest because it contains no AMD GPU (pciconf in env.txt), so the bug class is hardware-gated and was verified by line-accurate code tracing only.
No comments yet.