# DF-4568 — intel_guc_ct.c: CT teardown frees the shared CT page while GuC interrupts are still enabled and the handler still points at CT code

- **File**: `sys/dev/drm/i915/intel_guc_ct.c` (ordering root cause shared with `intel_uc.c`)
- **Severity**: Info (hardening — see reachability)
- **Confidence**: certain (teardown order statically provable)

## What the defect is

Tearing the CT channel down races against the GuC interrupt handler:

1. `guc_disable_communication()` (sys/dev/drm/i915/intel_uc.c:226-237) calls
   `intel_guc_ct_disable(&guc->ct)` at **intel_uc.c:231 — before**
   `gen9_disable_guc_interrupts(i915)` at **intel_uc.c:233** (which is the
   call that masks the GuC events **and** `synchronize_irq()`s them,
   i915_irq.c:555-567).
2. Inside `intel_guc_ct_disable()` (intel_guc_ct.c:887-904), the shared CT
   page is deregistered and **freed/unmapped** via
   `ctch_close()` :898 → `ctch_fini()` :290 → `i915_vma_unpin_and_release(&ctch->vma,
   I915_VMA_RELEASE_MAP)` (intel_guc_ct.c:214-218). Only *after* the free, at
   :901-902, are `guc->send`/`guc->handler` switched to the nop versions.
3. A GuC event that is already asserted (or arrives) in that window
   dispatches `guc->handler` — still
   `intel_guc_to_host_event_handler_ct` (intel_guc_ct.c:846-851) — which
   runs `ct_process_host_channel()` (:815-840) on the freed page:
   `ctb_read()` dereferences `ctb->desc` (:581) and **writes**
   `desc->head = head * 4` (:628); on `-EPROTO` it also writes
   `ctb->desc->is_in_error = 1` (:838).

That is a use-after-free read+write (through the released i915 VMA mapping)
in interrupt context. The `ctch_is_open(ctch)` guard at :822 is
check-then-use against a pointer that `i915_vma_unpin_and_release` clears
without any lock shared with the IRQ path, so it does not close the window.

## Why it is only Info on this tree

Same double unreachability as DF-4567: no platform in this tree sets
`info.has_guc_ct` (declared intel_device_info.h:94; only `.has_guc = 1`
exists, i915_pci.c:469/:520), so `intel_guc_ct_disable()` can never be
reached (`guc_disable_communication` only calls it under `HAS_GUC_CT`,
intel_uc.c:230-231), and `enable_guc` defaults to 0 anyway
(i915_params.h:49). Additionally the teardown paths that call it
(`intel_uc_fini_hw`, reset) are privileged driver-lifecycle contexts. The
file is compiled (sys/conf/files:2453) but the function is inert.

## PoC status

`untested` — no runtime reproduction possible: the guest GPU is QEMU bochs
std-VGA (no i915 device), and the code path cannot even be selected on this
tree.

## Fix

Quiesce before freeing: disable + synchronize GuC interrupts and switch
`guc->send`/`guc->handler` to nop **before** `intel_guc_ct_disable()` tears
down the channel (and, defense-in-depth, before `ctch_close()` inside
`intel_guc_ct_disable()` itself). See `fix.diff` (authored against the
read-only sys/ tree; never applied).
