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

guc_ctl_log_params_flags() NULL-derefs guc->log.vma: the port compiled out intel_guc_log_create() but left its consumer live β€” instant kernel panic whenever GuC is enabled (enable_guc!=0) on Gen9+

Summary

The flags computation dereferences guc->log.vma which is ALWAYS NULL on this port (the only assignment lives in intel_guc_log_create whose sole call site is #if 0); intel_uc_init_hw calls init_params unconditionally before firmware upload - page-0 fault in both debug and release builds. Admin enabling the supported GuC feature (root-only param, default 0) gets an immediate panic at GPU init. Same dead-code-divergence family as DF-4567/4568. Guard-the-NULL fix (git-apply-verified).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4729 Β· 7 files
FileTypeDescriptionSize
README.md β€” 4.3 KB ↓ raw
VERDICT.md β€” 4.5 KB ↓ raw
verify.sh β€” 1.7 KB view raw
static_proof.log β€” 3.0 KB view raw
env.txt β€” 609 B view raw
fix.diff β€” 686 B view raw
verdict.json β€” 3.0 KB view raw

DF-4729 β€” intel_guc: guc_ctl_log_params_flags() NULL-derefs guc->log.vma (log buffer is never created on this port)

File: sys/dev/drm/i915/intel_guc.c β€” pass 2 (GLM 5.3 re-audit) Severity: Low Β· CWE-476 Β· confidence: certain (defect), reachability gated on i915.enable_guc

What

intel_guc_init_params() (intel_guc.c:340-379) unconditionally computes params[GUC_CTL_LOG_PARAMS] = guc_ctl_log_params_flags(guc) (intel_guc.c:359), and guc_ctl_log_params_flags() (intel_guc.c:294-333) starts with:

u32 offset = intel_guc_ggtt_offset(guc, guc->log.vma) >> PAGE_SHIFT;   // :296

On this DragonFly port intel_guc_log_create() is compiled OUT β€” its only call site is inside #if 0 at intel_guc.c:202-206 (the same #if 0 also guts intel_guc_log_init_early, intel_guc.c:71-73). The only assignment to log->vma in the tree is inside intel_guc_log_create() (intel_guc_log.c:489), which is never called. drm_i915_private is zero-initialized, so guc->log.vma == NULL forever.

intel_guc_ggtt_offset(guc, NULL) β†’ i915_ggtt_offset(NULL) (i915_vma.h:202-208):

GEM_BUG_ON(!i915_vma_is_ggtt(vma));   /* vma->flags β€” NULL deref in debug builds */
...
return lower_32_bits(vma->node.start); /* NULL+offset read in release builds */

Either way: page-0 fault β†’ kernel panic, in the driver-load / GPU-resume path (intel_uc_init_hw() at intel_uc.c:377 β†’ called from i915_gem_init_hw, i915_gem.c:5746).

Reachability (why Low, not higher)

  • enable_guc defaults to 0 on this port (i915_params.h:49, param(int, enable_guc, 0)); USES_GUC() is false unless root sets i915.enable_guc=1/-1 (module param / loader tunable, sysctl mode 0400 β€” root-only). The auto-enable branch (sanitize_options_early, intel_uc.c:110-111) only fires for the auto value -1, which is not the default here.
  • Requires Intel Gen9+ hardware with GuC (HAS_GUC).
  • With GuC enabled, the panic is unconditional and immediate at intel_uc_init_hw β€” before firmware upload even matters: there is no fw-fetch-status check before intel_guc_init_params(guc) (intel_uc.c:377), so even a missing firmware blob does not save it.

Threat model: an admin who enables the (advertised, supported) GuC/HuC feature gets an instant kernel panic at load/resume. Not attacker-triggerable without root. Same dead-code-divergence family as the pass-2 intel_guc_ct hardening findings (DF-4567/4568): the port disabled a subsystem's setup but left its use compiled in.

Reproduce (static proof β€” runnable)

Run ./verify.sh in this directory (uses only rg/sed against the repo):

  1. intel_guc_log_create has no live call site (only #if 0 at intel_guc.c:203).
  2. log->vma is assigned nowhere else.
  3. guc_ctl_log_params_flags dereferences it, and is called unconditionally from intel_guc_init_params ← intel_uc_init_hw:377 ← i915_gem.c:5746.
  4. i915_ggtt_offset() derefs vma in both debug and release builds.
  5. enable_guc default is 0 β†’ gate.

Why not verified on the QEMU guest

The audit guest (DragonFly 6.5-DEVELOPMENT, QEMU vgapci chip=0x11111234) has no Intel Gen9+ display device; i915 never attaches, so the GuC path cannot be exercised. No hardware emulation knob is available to inject an Intel GPU. Status recorded as untested (permitted enum), impact would be panic on target hardware.

Guard the flags computation on the log buffer actually existing (consistent with this port having disabled GuC logging):

--- a/sys/dev/drm/i915/intel_guc.c
+++ b/sys/dev/drm/i915/intel_guc.c
@@ -291,9 +291,15 @@ static u32 guc_ctl_debug_flags(struct intel_guc *guc)

 static u32 guc_ctl_log_params_flags(struct intel_guc *guc)
 {
-   u32 offset = intel_guc_ggtt_offset(guc, guc->log.vma) >> PAGE_SHIFT;
+   u32 offset;
    u32 flags;

+   /* The port does not create the log buffer (intel_guc_log_create
+    * is compiled out); do not hand the firmware a bogus log params
+    * dword β€” and do not dereference a NULL vma. */
+   if (!guc->log.vma)
+       return 0;
+
+   offset = intel_guc_ggtt_offset(guc, guc->log.vma) >> PAGE_SHIFT;
+
    #if (((CRASH_BUFFER_SIZE) % SZ_1M) == 0)

(Returning 0 leaves GUC_LOG_VALID unset β†’ GuC logging disabled, which matches the port's intent. The alternative β€” re-enabling intel_guc_log_create() β€” requires the relay/debugfs infrastructure this port also removed.)

VERDICT.md
↓ download raw

VERDICT β€” DF-4729

  • status: untested (permitted enum; contract category would be "skipped/not_testable")
  • reproduced: 0 Β· impact: none (demonstrated) β€” would be panic on target hardware
  • confidence: certain (defect logic), reachability root/config-gated

Narrative

Pass-2 audit of sys/dev/drm/i915/intel_guc.c (known: DF-1826 shared_data_vaddr dangling β€” not re-reported; verified not to have a second distinct lifetime consumer: shared_data_vaddr is read only at intel_guc_submission.c:635, which is DF-1826's manifestation).

New finding: a dead-code divergence panic. This port compiled out the GuC log subsystem's construction (#if 0 at intel_guc.c:71-73 and :202-206 β€” intel_guc_log_init_early and intel_guc_log_create) but kept its consumption: guc_ctl_log_params_flags() dereferences guc->log.vma (intel_guc.c:296). log->vma is assigned in exactly one place in the tree β€” intel_guc_log_create() at intel_guc_log.c:489 β€” which has zero live call sites. guc->log.vma is therefore NULL for the driver's lifetime.

Call chain (all verified live):

i915_gem.c:5746  i915_gem_init_hw()            [driver load / resume]
 └─ intel_uc.c:377   intel_uc_init_hw() β€” no fw-fetch-status guard before the call
     └─ intel_guc.c:359 intel_guc_init_params()
         └─ intel_guc.c:296 guc_ctl_log_params_flags()
             └─ intel_guc.h:137-143 intel_guc_ggtt_offset(guc, NULL)
                 └─ i915_vma.h:202-208 i915_ggtt_offset(NULL)
                     β”œ debug GEM build: GEM_BUG_ON(!i915_vma_is_ggtt(NULL)) β†’ vma->flags read β†’ page-0 fault
                     β”” release build: lower_32_bits(vma->node.start) β†’ NULL+offset read β†’ page-0 fault

Either build panics. The panic fires before firmware upload is attempted, so even a missing firmware file does not avoid it.

Why untested (not false_positive)

The defect is statically certain (run ./verify.sh, output in static_proof.log). It is not false_positive: the cited path is live whenever USES_GUC() is true. It is not verified at runtime because the audit guest has no Intel Gen9+ display device (QEMU vgapci chip=0x11111234, see env.txt) β€” i915 cannot attach, and no emulation knob exists to inject one. The trigger precondition (i915.enable_guc=1/-1, default 0 at i915_params.h:49, root-only to set) additionally gates it behind an admin action, which is why this is filed Low rather than High.

Exploit chain

None β€” configuration-gated denial of service (kernel panic at GPU init) on hardware an attacker cannot summon. No path to uid=0.

Negative results (classes hunted and killed in this file)

  • intel_guc_init error unwind double-intel_uc_fw_fini: killed β€” intel_uc_fw_fini is fetch_and_zero-based (intel_uc_fw.c:278-287), idempotent.
  • guc_shared_data_create pin_map-failure unwind leaking the pin: released via i915_vma_unpin_and_release(&vma, 0) (intel_guc.c:177) β€” the helper unconditionally unpins; no leak.
  • intel_guc_init_params SOFT_SCRATCH overflow: killed β€” GUC_CTL_MAX_DWORDS = SOFT_SCRATCH_COUNT - 2 (intel_guc_fwif.h:132), writes SOFT_SCRATCH(1..14) βŠ‚ SOFT_SCRATCH(0..15).
  • intel_guc_send_mmio len/response bounds: GEM_BUG_ON-asserted only in debug builds, but len/response_buf_size come solely from in-kernel constant action arrays (this file :504/:530/:544/:561, submission, huc) β€” not attacker-influenced; count = min(response_buf_size, send_regs.count-1) bounds the copyout.
  • guc->send/guc->handler pointer swap vs in-flight send race (suspend vs execbuf): upstream design; requires GuC submission (disabled by default, enable_guc=0); no memory-safety consequence demonstrated β€” noted, not filed.
  • IRQ path intel_guc_to_host_event_handler_mmio (intel_guc.c:462-487): irq_lock-protected read-modify-write of SOFT_SCRATCH(15), mask-bounded β€” clean.

Fix validation

Not possible on this guest (no target hardware): fix_status: not_testable. fix.diff guards the flags computation on guc->log.vma != NULL, returning 0 (GuC logging disabled β€” consistent with the port's removal of the log subsystem).

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 authored (NULL guard in guc_ctl_log_params_flags) but not build-validated: no Intel Gen9+ GPU on the audit guest, so neither baseline panic nor patched behaviour can be exercised.

findings/poc/DF-4729/fix.diff
↓ fix.diffper-fix-DF-4729

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4729/static_proof.log (steps 1-8: dead call site, sole assignment, deref :296, unconditional caller :359, live chain via intel_uc.c:377 <- i915_gem.c:5746, i915_ggtt_offset deref, enable_guc=0 default); VERDICT.md (full narrative + killed candidates); verify.sh (runnable)

PoC changes

No PoC seed existed; authored a runnable static proof script (verify.sh) instead of a hardware PoC since the finding is hardware-gated.

Verified recommended fix

Guard guc_ctl_log_params_flags() to return 0 when guc->log.vma is NULL (log subsystem compiled out on this port).

Verdict

Pass-2 finding: guc_ctl_log_params_flags() (intel_guc.c:296) dereferences guc->log.vma, which is provably always NULL on this port because intel_guc_log_create()'s only call site is compiled out (#if 0, intel_guc.c:202-206) and log->vma is assigned nowhere else (intel_guc_log.c:489 is dead). intel_uc_init_hw (intel_uc.c:377) calls intel_guc_init_params unconditionally before firmware upload, so any root-enabled GuC load (i915.enable_guc=1/-1; default 0 at i915_params.h:49) on Gen9+ hardware takes i915_ggtt_offset(NULL) -> NULL deref -> kernel panic in both debug and release builds. Runtime verification impossible on the audit guest (QEMU virtual VGA chip=0x11111234, i915 never attaches); the static chain is machine-checked by verify.sh/static_proof.log. Severity Low: panic-only, hardware- and root-config-gated.