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)
PoC verification
Evidence pack
findings/poc/DF-4736 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)β nohpd_irq_setupcheck β 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(...)β withhas_hotplug == 0,hotplug_statusstays 0,i9xx_hpd_irq_handler()(the only caller ofintel_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.
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)
display.hpd_irq_setupis assigned only inintel_irq_init()(i915_irq.c:4853β4916). Every branch assigns it except the GMCH branch, which assigns it onlyif (I915_HAS_HOTPLUG(dev_priv))(i915_irq.c:4915-4916).I915_HAS_HOTPLUGisinfo.has_hotplug(i915_drv.h:2634). The only platform entries without it are gen2 and i915G/i915GM (i915_pci.c:130β141 βintel_i915gm_infohas nohas_hotplug; the firsthas_hotplug = 1entries are i945g/i945gm at i915_pci.c:143-158).- On GMCH,
intel_hpd_irq_handler()is reached only viai9xx_hpd_irq_handler(), which is called only whenhotplug_status != 0(i915_irq.c:4600-4601, 4633-4634 pattern), and the ack that produceshotplug_statusis gated:if (I915_HAS_HOTPLUG(dev_priv) && iir & I915_DISPLAY_PORT_INTERRUPT)(i915_irq.c:4584-4586 ini915_irq_handler; same gate ini965_irq_handlerfor gen4+, and gen2i8xx_irq_handlerhas no hotplug path at all). On !has_hotplug platformshotplug_statusstays 0 βintel_hpd_irq_handler()never runs β :486 is dead code there. - On every platform where
intel_hpd_irq_handler()CAN run,hpd_irq_setupwas 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
- Static reachability trace (i915_irq.c + i915_pci.c + intel_hotplug.c) β killed exploitability for the current platform table (certain).
- 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_testablen/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.
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.
No comments yet.