# DF-4567 — intel_guc_ct.c: CTB ring math trusts GuC-firmware-written descriptor fields with only compiled-out GEM_BUG_ON validation

- **File**: `sys/dev/drm/i915/intel_guc_ct.c`
- **Severity**: Info (hardening / defense-in-depth — see reachability)
- **Confidence**: certain (the absent validation is statically provable)

## What the defect is

`ctb_write()` (sys/dev/drm/i915/intel_guc_ct.c:317-381) and `ctb_read()`
(:579-630) drive the CT send/receive rings entirely off the shared
`struct guc_ct_buffer_desc` (intel_guc_fwif.h:382-394) that lives in a page
*also written by the GuC firmware over DMA* (desc->head, desc->tail,
desc->size, is_in_error, fence, status are all in FW-writable shared memory;
the page is mapped WB at intel_guc_ct.c:189-201).

Every bound/invariant on those fields is enforced only by `GEM_BUG_ON` /
`GEM_WARN_ON`, which are **no-ops in production builds**
(sys/dev/drm/i915/i915_gem.h:56-61 — `#define GEM_BUG_ON(expr)
BUILD_BUG_ON_INVALID(expr)` unless `CONFIG_DRM_I915_DEBUG_GEM`):

- ctb_write:332-335 — `GEM_BUG_ON(desc->size % 4)`, `desc->head % 4`,
  `desc->tail % 4`, `tail >= size` : all no-ops.
- ctb_read:590-594 — same family plus `head >= size`: no-ops.

Consequences if the FW ever writes a bad descriptor (GuC bug, FW compromise,
or desc corruption from a GT reset — the reason upstream i915 later replaced
exactly these GEM_BUG_ONs with real error paths):

1. **Divide-by-zero panic**: `tail = (tail + 1) % size` at
   intel_guc_ct.c:366/369/373 and `head = (head + 1) % size` at :608/:624 —
   `size = desc->size / 4`; `desc->size == 0` ⇒ `% 0` ⇒ #DE ⇒ kernel panic.
2. **Wild OOB write**: the ENOSPC space check (:347) and every ring index in
   ctb_write are computed from FW-written `desc->head/tail/size`
   (:324-326). A descriptor with inflated `size`/`tail` (and `head` arranged
   so `used` looks small) makes the check pass and `cmds[tail] = header`
   (:365-374) land at an FW-chosen offset up to ~1 GiB past the 1-page CT
   object — kernel heap corruption with partially host-controlled data
   (header dword, fence, action payload).
3. **Space-check bypass / stream corruption** on the RECV side for shrunk or
   wrapped `size`/`tail` (mis-counted `available`, :597-605).

## Why it is only Info on this tree (reachability — verified)

The entire CT path is dead code in this tree, twice over:

1. `i915_modparams.enable_guc` defaults to **0** (i915_params.h:49
   `param(int, enable_guc, 0)`); `intel_uc_init_hw()` returns early unless
   `USES_GUC()` (intel_uc.c:44-48, :342-348), and `enable_guc` is a
   root-only loader tunable (mode 0400, i915_params.c:153-156).
2. Even with `enable_guc=1/3`, `guc_enable_communication()` only calls
   `intel_guc_ct_enable()` when `HAS_GUC_CT(i915)` (intel_uc.c:218-219) —
   and **no platform in this tree ever sets `info.has_guc_ct`**. The field
   is declared in the device-info FUNC list (intel_device_info.h:94) but
   `i915_pci.c` only ever sets `.has_guc = 1` (i915_pci.c:469, :520);
   `rg has_guc_ct` across sys/dev/drm finds no other assignment. So
   `intel_guc_ct_enable()`, `ctb_write()`, `ctb_read()`,
   `intel_guc_send_ct()` are **never called** on this tree, on any hardware.

The file *is* compiled into the i915 module (sys/conf/files:2453).

## PoC status

`untested` — no runtime PoC is possible or meaningful:
- the QEMU guest has bochs std-VGA (no Intel dGPU → no i915 GuC at all);
- statically, the code path cannot be selected on this tree (see above);
- triggering the defect additionally requires the *signed* GuC firmware to
  write an out-of-contract descriptor, which no unprivileged input can
  induce.

## Fix

Validate the FW-written descriptor fields for real in `ctb_write`/`ctb_read`
(replace the no-op GEM_BUG_ONs with `if (...) return -EPROTO;`), as upstream
i915 did when it hardened the CT code. See `fix.diff` in this directory
(authored against the read-only sys/ tree; never applied).
