# VERDICT — DF-4819

**Finding:** i915 GEM shrinker is dead code in the DragonFly port — never
registered with any memory-reclaim path — so purgeable/unbound GPU buffer
object pages are never reclaimed under system memory pressure (OOM-killer DoS).
DF-4507 family (that finding covered TTM page *pools*; this one covers i915
GEM *object* pages).

**Structural verdict: DEFECT CONFIRMED (certain).** Proven at three levels:

1. **Shim level** — `sys/dev/drm/include/linux/shrinker.h` contains only
   `struct shrinker { count_objects; scan_objects; seeks; }`,
   `struct shrink_control`, `DEFAULT_SEEKS`, `SHRINK_STOP`. There is no
   `register_shrinker`, no `register_oom_notifier`,
   no `register_vmap_purge_notifier` anywhere in the shim or in all of `sys/`.
2. **Driver level** — `sys/dev/drm/i915/i915_gem_shrinker.c`:
   - `i915_gem_shrinker_register()` (lines 516-533) only assigns function
     pointers and `seeks`; the actual registrations at lines 521-532 are inside
     `#if 0`.
   - `i915_gem_shrinker_count()` (313-351) and `i915_gem_shrinker_scan()`
     (353-395) bodies are `#if 0`; both compile to `return 0` stubs.
   - The OOM notifier `i915_gem_shrinker_oom` and vmap purge notifier
     `i915_gem_shrinker_vmap` are entirely `#if 0`'d (397-508).
   - `i915_gem_shrinker_unregister()` (541-548) is an empty `#if 0` body.
3. **Kernel level** — `vm_lowmem` (the one pressure event the DRM tree knows
   about, registered at `sys/dev/drm/ttm/ttm_page_alloc.c:447`) is never
   `EVENTHANDLER_INVOKE`'d anywhere in `sys/kern`, `sys/vm`, `sys/sys` or
   elsewhere (grep: 0 sites) — exactly as DF-4507 documented.

**Binary confirmation on the guest** (`run.guest.log`, DragonFly
6.5-DEVELOPMENT X86_64_GENERIC, 2026-06-29 build):

```
$ nm /boot/kernel/i915.ko | grep -iE 'shrinker|register_shrinker'
0000000000050cb0 t i915_gem_shrinker_count      <- local stub, dead body
00000000000512a0 T i915_gem_shrinker_register   <- assigns ptrs nobody calls
0000000000050cc0 t i915_gem_shrinker_scan       <- local stub, dead body
...
PASS  i915.ko has NO register_shrinker reference
PASS  drm.ko (shim) has NO register_shrinker symbol at all
```

**Why the *impact* is not demonstrated at runtime:** the audit guest is a
QEMU/KVM VM with the emulated std VGA device (`vgapci0 ... chip=0x11111234`)
— no Intel GPU, so `i915` never attaches (`kldstat` shows no drm/i915
modules). Reproducing the DoS (unprivileged user pins N GB of GEM shmem,
driver marks it available-for-reclaim, system memory pressure hits, kernel
OOM-kills processes instead of purging) requires real i915 hardware, which no
QEMU configuration in this harness provides (no i915 emulation exists
upstream). Recorded as `status=untested`, `reproduced=0`, `impact=none`
consistent with the DF-4507 family precedent in `poc_results`; the code-level
claim itself is `confidence=certain`.

**The live code that *does* run was audited and is sound:**
`i915_gem_shrink()` (145-280) is reachable, but only via explicit call sites:
- `i915_gem.c:2935` — shmem page-alloc failure retry inside
  `i915_gem_object_get_pages_gtt` (first pass purgeable, second pass nothing);
- `i915_gem_gtt.c:2449` — `i915_gem_gtt_prepare_pages()` dma-map failure
  retry loop (bounded: shrinks other objects until `i915_gem_shrink` returns
  0, then `-ENOSPC`);
- `i915_gem.c:6324` / `6353` — suspend/hibernate (`i915_gem_freeze`,
  `i915_gem_freeze_late`); plus an `I915_SELFTEST_ONLY` fault-injection call
  in `i915_gem_gtt.c:417`.

Pass-2 sweep of that live walk — all clean:
- `shrinker_lock()` (39-65): `mutex_trylock_recursive()` maps to DFly
  `lockowned()`, which for exclusive locks tests `lk_lockholder == curthread`
  (`sys/kern/kern_lock.c:1365-1380`) — the RECURSIVE case genuinely means
  "current thread holds it", so the walk never proceeds without holding
  `struct_mutex`. (struct_mutex is only ever taken LK_EXCLUSIVE in DRM.)
- The FAILED-case busy-wait is bounded by `need_resched()` →
  `any_resched_wanted()` (real in DFly); worst case short CPU burn, no hang.
- LRU walk (211-268): objects are moved to a stack-local `still_in_list`
  under `mm.obj_lock`; the lock is dropped (250) only while
  `struct_mutex` is held throughout (163), and final-unref/free_work is
  serialized by `struct_mutex`, so `obj` cannot be freed across the drop —
  `scanned += obj->base.size >> PAGE_SHIFT` (262) is safe. `list_splice_tail`
  (266) is empty-list safe (`_list_splice` returns early on empty,
  `sys/dev/drm/include/linux/list.h`). No self-deadlock: the object being
  shrunk is skipped because `can_release_pages()` requires
  `i915_gem_object_is_shrinkable()` and the in-progress object has
  `mm.pages == NULL` at that point.
- Arithmetic: `count`/`scanned` are `unsigned long` (u64); `target` can be
  `-1UL`; page sums cannot wrap in practice; `can_release_pages()`
  compares `atomic_t > unsigned int` correctly after integer promotion.
- `i915_gem_shrinker_taints_mutex()` (550-559): early-returns because
  `CONFIG_LOCKDEP` is never defined in the DFly build — no-op.

**Recommended fix (feature-sized, not a one-liner):** implement a real
`register_shrinker()` in `sys/dev/drm/include/linux/shrinker.h` backed by a
DFly VM-pressure mechanism (e.g. a `vm_lowmem` event actually
`EVENTHANDLER_INVOKE`'d from `vm/pageout`, or a dedicated kmem-reclaim
callback), then remove the `#if 0` from the registration block
(i915_gem_shrinker.c:521-532) and from the count/scan bodies (313-395),
adapting `spin_lock` → `lockmgr` in the restored `count` as the scan already
does. `fix.diff` is intentionally omitted: a verifiable fix requires new VM
infrastructure plus i915 hardware to validate against, not a patch this
evidence pack could honestly test on the available guest.

**Artifacts:** `check_shrinker_wiring.sh` (verification script, two modes),
`run.log` (host static proof, 6×PASS), `run.guest.log` (guest binary proof +
runtime context), `env.txt`, `code_hash.txt`, `README.md`, this file,
`manifest.json`, `verdict.json`.

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
