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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4787 Β· 6 files
FileTypeDescriptionSize
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-116 switch (pin->id) β†’ read at NULL+offsetof(id)
  • dce110/hw_factory_dce110.c:137-139 hpd->regs = &hpd_regs[en] (via HW_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.

VERDICT.md
↓ download raw

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

  1. gpio_service.c:224 dal_gpio_service_open(service, id, en, mode, &pin).
  2. gpio_service.c:245-247 (DDC_DATA), :250-252 (DDC_CLOCK), :258-261 (HPD): the just-returned pin from create_ddc_data() / create_ddc_clock() / create_hpd() is passed straight into define_ddc_registers(pin, en) / define_hpd_registers(pin, en).
  3. The if (!pin) guard only runs at gpio_service.c:276 β€” after the dereference.
  4. 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).
  5. Producers of NULL: hw_ddc.c:224-227 (kzalloc failure), hw_ddc.c:219-222 (en outside [GPIO_DDC_LINE_MIN..MAX]), hw_hpd.c:149-152, hw_hpd.c:153-160 (kzalloc failure / en outside [GPIO_HPD_MIN..MAX], id != GPIO_ID_HPD).

Why it is only Low

  • The en-out-of-range arm is unreachable today: every caller obtains en from translate.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_id returns false β†’ dal_gpio_create_ddc / dal_gpio_service_create_irq return 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).

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4787

Confirmed kernel references

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.