# DF-5220 VERDICT — execlists engine-init failure kfree()s engine with live timeline list node

## Bottom line

The defect is **proven statically, line by line**; the driver-level trigger
cannot execute on this guest because it has no Intel graphics device
(QEMU virtual VGA, chip 0x1234:0x1111 — `pciconf -lv` in env.txt), so the
**finding itself is INCONCLUSIVE at runtime**: not reproduced end-to-end,
not refuted. The corruption **mechanism** the defect depends on was
**reproduced deterministically (3/3 runs)** with a standalone KLD
(`repro_kld.c`, run.log / run.2.log / run.3.log): an intrusive-list unlink
of a neighbor writes an 8-byte kernel pointer (`0xffffffff82600540`) into a
freed-and-then-reallocated slab chunk, at the exact offset of the dead
node's back-pointer. The guest remained healthy and the module unloaded
cleanly after every run.

## The defect (static trace)

Protocol: `i915_timeline_init()` links every timeline — embedded engine
timelines included — onto the global `i915->gt.timelines` list
(sys/dev/drm/i915/i915_timeline.c:28 `list_add(&timeline->link,
&i915->gt.timelines)`). The ONLY sanctioned unlink is
`i915_timeline_fini()` → `list_del(&timeline->link)` (i915_timeline.c:75),
reached for embedded engine timelines exclusively via
`intel_engine_cleanup_common()` (sys/dev/drm/i915/intel_engine_cs.c:680).

1. Gen8+ (HAS_LOGICAL_RING_CONTEXTS) engine bring-up:
   `intel_engines_init()` (intel_engine_cs.c:383) runs
   `class_info->init_execlists(engine)` = `logical_render_ring_init()` /
   `logical_xcs_ring_init()` (intel_lrc.c:2476/2515).
2. Those call `logical_ring_setup()` (intel_lrc.c:2404) →
   `intel_engine_setup_common()` (intel_engine_cs.c:482) →
   **`i915_timeline_init(..., &engine->timeline, ...)`** — the embedded
   timeline is now linked onto gt.timelines. (engines are kzalloc'd at
   intel_engine_cs.c:286.)
3. Then `logical_ring_init()` (intel_lrc.c:2424) →
   `intel_engine_init_common()` (intel_engine_cs.c:602) which pins the
   kernel context (creating its per-engine ring + a HEAP timeline, also
   linked onto gt.timelines, intel_ringbuffer.c:1190 /
   execlists_context_deferred_alloc intel_lrc.c:2848) and can then FAIL at
   `intel_engine_init_breadcrumbs()` (intel_engine_cs.c:628) or
   `init_status_page()`/`init_phys_status_page()` (intel_engine_cs.c:633-
   638) with an allocation-class error.
4. **The execlists error path returns WITHOUT any unwind**
   (intel_lrc.c:2430-2431 `if (ret) return ret;`, and wrappers return it
   verbatim). Contrast the legacy ring path which self-unwinds through
   `err: intel_engine_cleanup_common(engine)` at
   intel_ringbuffer.c:1480-1483.
5. `intel_engines_init()`'s cleanup loop then does, for the failing engine
   (`id == err_id`): **`kfree(engine)`** (intel_engine_cs.c:417) — the
   engine is freed while `engine->timeline.link` is STILL ON
   `gt.timelines`, and while the kernel-context ring pinned in step 3 is
   still pinned (no `__intel_context_unpin` runs for it).
6. `i915_gem_init()` fails → `goto err_context` (i915_gem.c:6013-6017) →
   `i915_gem_contexts_fini()` (i915_gem_context.c:593) →
   `destroy_kernel_context()` → ring teardown →
   `intel_ring_free()` (intel_ringbuffer.c:1213-1223) →
   `i915_timeline_put()` → `__i915_timeline_free()` (i915_timeline.c:93-100)
   → `i915_timeline_fini()` → **`list_del(&timeline->link)` on a node whose
   neighbor is the FREED engine node** → `__list_del` writes
   `prev->next`/`next->prev` **through the freed engine's memory**
   (use-after-free WRITE; 2× 8-byte kernel-pointer writes at fixed offsets
   in a freed, likely-reallocated slab chunk).
7. Secondary: the stale node itself remains on gt.timelines (dormant
   use-after-free for any later walk — i915_timelines_park
   i915_timeline.c:52-67, i915_gem.c:3775/4209, i915_request.c:240 — and
   the WARN at i915_gem.c:6302 on unload).

This mirrors the mechanism demonstrated by the KLD: kfree-while-linked,
then a neighbor's unlink writes into the freed chunk — deterministic
(slab LIFO) observed 3/3 times.

## Trigger & reachability (honest)

- Requires: Intel Gen8+ graphics (execlists) AND an allocation failure at
  the breadcrumbs / status-page stage of engine bring-up (kernel-memory
  exhaustion at driver attach: boot or kldload of the drm modules).
- i915 attach happens at boot / module load; an unprivileged user cannot
  time it. Root can retry kldload/kldunload under memory pressure.
- On this guest: NO Intel GPU exists → driver path cannot run at all
  (env.txt: pciconf shows vgapci0 chip=0x11111234, virtual VGA).
- Therefore: severity capped at Medium (kernel UAF write class, but
  environmental/privileged trigger).

## Runtime evidence

- `build.log` — clean KLD build (cc 8.3, -Werror, no warnings).
- `run.log`, `run.2.log`, `run.3.log` — three identical successful
  demonstrations; `D.le_prev=0xffffffff82600540` appears in a zeroed,
  reallocated chunk after `kfree(A)` + `LIST_REMOVE(C)`.
- `env.txt` — guest uname, compiler, PCI display device, kldstat, dmesg
  (no i915/drm).

## Fix validation

Not possible on this guest (baseline cannot run — no Intel GPU). fix.diff
was authored from the line-accurate trace: it makes the execlists wrappers
unwind `intel_engine_setup_common()` on `logical_ring_init()` failure via
`intel_engine_cleanup_common()`, exactly matching the legacy path's
existing error contract (intel_ringbuffer.c:1480-1483). fix_status:
not_testable.

## Class / bucket

memcorrupt (UAF write). The DF-5219 companion finding (same audit,
timeline kref leak in `intel_engine_create_ring`'s error path) shares the
`i915_timeline_get/put` protocol surface.

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