# 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.
