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

Unguarded indirect call dev_priv->display.hpd_irq_setup() in intel_hpd_irq_handler() β€” guard asymmetry vs sibling call sites (latent NULL function-pointer call)

Summary

The storm-detection call is guarded only by storm_detected && display_irqs_enabled while both sibling call sites in the same file additionally check hpd_irq_setup != NULL; !I915_HAS_HOTPLUG platforms keep it NULL. Today statically dead (the gen3 handler gates hotplug ack behind HAS_HOTPLUG), but any future platform entry or IRQ refactor turns an HPD storm (flaky connector) into an indirect call through NULL: guaranteed panic from a connector event. Add-the-same-guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4736 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.7 KB ↓ raw
verdict.json β€” 3.7 KB view raw
env.txt β€” 1.0 KB view raw
guest_ichwd_inert.log β€” 202 B view raw
fix.diff β€” 419 B view raw

DF-4736 β€” Unguarded indirect call dev_priv->display.hpd_irq_setup() in intel_hpd_irq_handler()

Field Value
File sys/dev/drm/i915/intel_hotplug.c
Line 486–487
Severity Info (defense-in-depth; no live trigger on any platform in this tree)
CWE CWE-476 (NULL function-pointer call) latent

What the asymmetry is

intel_hpd_irq_handler() calls the platform HPD-IRQ reprogram hook without the NULL guard that the two sibling call sites in the very same file implement:

  • intel_hotplug.c:244 β€” if (dev_priv->display_irqs_enabled && dev_priv->display.hpd_irq_setup) (storm re-enable work)
  • intel_hotplug.c:532 β€” if (dev_priv->display_irqs_enabled && dev_priv->display.hpd_irq_setup) (intel_hpd_init)
  • intel_hotplug.c:486 β€” if (storm_detected && dev_priv->display_irqs_enabled) β†’ no hpd_irq_setup check β†’ indirect call through a possibly-NULL function pointer.

Why this is not exploitable today (full trace)

display.hpd_irq_setup is assigned exactly once per platform, in intel_irq_init() (i915_irq.c:4843–4916):

  • CHV/VLV: i915_hpd_irq_setup (i915_irq.c:4861/4869 via 4853/4861)
  • gen11+: gen11_hpd_irq_setup (4869)
  • gen8+ non-LP: bxt/spt/ilk_hpd_irq_setup (4878–4883)
  • PCH-split: ilk_hpd_irq_setup (4891)
  • GMCH (gen2–4/5): set only if (I915_HAS_HOTPLUG(dev_priv)) (4916)

The only platforms that leave it NULL are GMCH platforms with info.has_hotplug == 0 (i915G/i915GM β€” i915_pci.c:130–141 β€” and gen2). On exactly those platforms the gen3 IRQ handler gates the entire hotplug decode path:

  • i915_irq.c:4584–4586 (i915_irq_handler): if (I915_HAS_HOTPLUG(dev_priv) && iir & I915_DISPLAY_PORT_INTERRUPT) hotplug_status = i9xx_hpd_irq_ack(...) β€” with has_hotplug == 0, hotplug_status stays 0, i9xx_hpd_irq_handler() (the only caller of intel_hpd_irq_handler() on GMCH) is never reached.

intel_hpd_irq_handler() therefore never executes with a NULL hpd_irq_setup on any platform in the current i915_pci.c table. The guard at :486 is a latent-hazard hardening item: any future platform entry (or a refactor that assigns the handler earlier than the setup pointer) instantly turns an HPD storm into a NULL function-pointer call β†’ guaranteed panic from a display-connector event (attack surface: local physical/flaky-HPD, or guest-passthrough HPD storms).

Verification on the QEMU guest

The audit guest has no Intel display device at all (pciconf -l: only 8086:1237 hostb, 8086:7000 PIIX3, 8086:7010 ATA, 8086:7113 PM β€” see env.txt), so i915 never attaches and intel_hpd_irq_handler() is runtime-unreachable on this machine. The finding is verified statically (reachability trace above) and the environmental dead-end is verified live (env.txt). No runnable PoC exists for a guard asymmetry whose trigger path is dead on every supported platform; this pack documents the proof instead.

Fix

fix.diff β€” add the same guard used at :244/:532.

VERDICT.md
↓ download raw

DF-4736 VERDICT β€” INCONCLUSIVE (feature absent on guest; path statically dead on every platform today)

Status: inconclusive / impact: none / confidence: certain (that the asymmetry exists AND that it is unreachable in this tree).

What was investigated

Pass-2 audit of sys/dev/drm/i915/intel_hotplug.c. The candidate: intel_hotplug.c:486-487 performs

    if (storm_detected && dev_priv->display_irqs_enabled)
        dev_priv->display.hpd_irq_setup(dev_priv);

while both sibling call sites guard the same pointer:

  • :244 if (dev_priv->display_irqs_enabled && dev_priv->display.hpd_irq_setup)
  • :532 if (dev_priv->display_irqs_enabled && dev_priv->display.hpd_irq_setup)

If hpd_irq_setup were NULL while a storm is detected on an HPD-enabled pin, this is an indirect call through NULL β†’ immediate kernel panic.

Why it cannot fire in this tree (line-by-line)

  1. display.hpd_irq_setup is assigned only in intel_irq_init() (i915_irq.c:4853–4916). Every branch assigns it except the GMCH branch, which assigns it only if (I915_HAS_HOTPLUG(dev_priv)) (i915_irq.c:4915-4916).
  2. I915_HAS_HOTPLUG is info.has_hotplug (i915_drv.h:2634). The only platform entries without it are gen2 and i915G/i915GM (i915_pci.c:130–141 β€” intel_i915gm_info has no has_hotplug; the first has_hotplug = 1 entries are i945g/i945gm at i915_pci.c:143-158).
  3. On GMCH, intel_hpd_irq_handler() is reached only via i9xx_hpd_irq_handler(), which is called only when hotplug_status != 0 (i915_irq.c:4600-4601, 4633-4634 pattern), and the ack that produces hotplug_status is gated: if (I915_HAS_HOTPLUG(dev_priv) && iir & I915_DISPLAY_PORT_INTERRUPT) (i915_irq.c:4584-4586 in i915_irq_handler; same gate in i965_irq_handler for gen4+, and gen2 i8xx_irq_handler has no hotplug path at all). On !has_hotplug platforms hotplug_status stays 0 β†’ intel_hpd_irq_handler() never runs β†’ :486 is dead code there.
  4. On every platform where intel_hpd_irq_handler() CAN run, hpd_irq_setup was assigned in step 1.

Net: today the missing guard is unreachable; it is a latent NULL-call hazard for future platform additions. Severity Info, no runtime impact demonstrable.

Guest verification (environmental dead-end, live-checked)

Guest pciconf -l (env.txt): the only Intel devices are 440FX host bridge (8086:1237), PIIX3 ISA (8086:7000), PIIX3 ATA (8086:7010), PIIX4 PM (8086:7113). No Intel VGA/display controller β†’ i915 never attaches on this QEMU guest β†’ HPD interrupts, storm detection, and the entire file are runtime-unreachable here. A kernel-build PoC cannot exercise an IRQ-path NULL call without the hardware; a KLD stub that calls the function pointer with a NULL setup would only restate the tautology (calling NULL panics).

Attempts

  1. Static reachability trace (i915_irq.c + i915_pci.c + intel_hotplug.c) β€” killed exploitability for the current platform table (certain).
  2. Live guest environment check β€” no Intel GPU (env.txt).

Fix

fix.diff adds the guard, making :486 identical in shape to :244/:532. Not built/booted: an Info-latent finding with no observable behavior change on any existing platform (the guard is a no-op for every reachable configuration), and the guest has no i915 hardware β€” a patched-kernel comparison has nothing to compare. fix_status = not_testable for that reason.

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

n/a β€” Info hardening; no observable behavior differs on any existing platform (guard is a no-op for every reachable configuration) and the guest has no i915 hardware, so there is no baseline/patched comparison to run.

fix.diff β€” one-line guard textually identical to the guards at intel_hotplug.c:244 and :532.
↓ fix.diffper-fix-DF-4736

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (asymmetry + kill-chain with citations), VERDICT.md (full trace), env.txt (guest pciconf: no Intel GPU; uname), guest_ichwd_inert.log (unrelated ichwd negative check, same guest), fix.diff (one-line guard)

PoC changes

n/a β€” no runnable PoC: the trigger path is statically dead on every platform in the current i915_pci.c table and the guest has no Intel GPU; pack documents the reachability proof instead of a trigger.

Verified recommended fix

Guard the storm reprogram call at intel_hotplug.c:486 exactly like :244/:532: add '&& dev_priv->display.hpd_irq_setup' (see fix.diff).

Verdict

intel_hotplug.c:486-487 invokes dev_priv->display.hpd_irq_setup(dev_priv) without the NULL guard used by the sibling call sites at :244 and :532 in the same file. Reachability trace proves the pointer is non-NULL on every platform that can reach this code: intel_irq_init (i915_irq.c:4853-4916) leaves hpd_irq_setup NULL only on GMCH platforms with info.has_hotplug==0 (i915G/i915GM, gen2; i915_pci.c:130-141), and on exactly those platforms the gen3 IRQ handler gates the entire hotplug ack behind I915_HAS_HOTPLUG (i915_irq.c:4584-4586) so intel_hpd_irq_handler() is never called. The missing guard is therefore a latent NULL-function-pointer-call hazard for future platform additions, not a live bug: severity Info, impact none today. The audit QEMU guest has no Intel display device (pciconf: only 8086:1237/7000/7010/7113), so i915 never attaches and no runtime trigger can exist on this machine; verification is the static trace plus the live environment dead-end check.