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

i915 GEM shrinker is dead code: never registered with any memory-reclaim path β€” purgeable/unbound GPU buffer pages are never reclaimed under system memory pressure (DF-4507 family)

Summary

The DFly shim has no register_shrinker at all (struct-only header), so the registration block and the count/scan bodies are #if 0; vm_lowmem (the only VM-pressure event in the DRM tree) has 0 EVENTHANDLER_INVOKE sites. Unbound/purgeable GEM shmem is invisible to reclaim: under pressure the kernel OOM-kills instead of purging memory userspace explicitly marked DONTNEED - reliable local availability DoS by any video-group user on i915 hw. Binary-proven on guest (nm: count/scan local dead stubs, no register_shrinker in i915.ko/drm.ko); runtime DoS not manifestable (std-VGA only). The i915 instance of the DF-4507 family. Feature-sized fix: shim register_shrinker + real VM-pressure event.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4819 Β· 11 files
FileTypeDescriptionSize
check_shrinker_wiring.sh β€” 3.4 KB view raw
run.log β€” 768 B view raw
run.guest.log β€” 828 B view raw
env.txt β€” 369 B view raw
code_hash.txt β€” 91 B view raw
build.sh β€” 94 B view raw
run.sh β€” 204 B view raw
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 6.3 KB ↓ raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 3.8 KB view raw

DF-4819 β€” i915 GEM shrinker is never registered with any reclaim path (DF-4507 family)

What this is

Structural verification that the i915 GEM shrinker in the DragonFly DRM port can never run from system memory reclaim:

  1. sys/dev/drm/include/linux/shrinker.h (the DFly linux-compat shim) declares only struct shrinker + SHRINK_STOP β€” register_shrinker does not exist at all in the shim (or anywhere in sys/).
  2. The i915 port compensates by #if 0-ing out every registration call site: register_shrinker, register_oom_notifier, register_vmap_purge_notifier (sys/dev/drm/i915/i915_gem_shrinker.c:521-532).
  3. The i915_gem_shrinker_count / i915_gem_shrinker_scan bodies are #if 0 stubs that return 0 (lines 313-351, 353-395); even the function pointers installed by i915_gem_shrinker_register() point at dead stubs that nothing will ever call.
  4. The only global pressure hook that exists in the DRM tree (vm_lowmem, registered by sys/dev/drm/ttm/ttm_page_alloc.c:447) is never EVENTHANDLER_INVOKE'd anywhere in the kernel (0 sites in sys/).

Net effect: an unprivileged user with /dev/dri access on i915 hardware can hold gigabytes of shmem-backed GEM pages (including I915_MADV_DONTNEED "purgeable" objects that userspace explicitly marked reclaimable) that the VM subsystem cannot reclaim under memory pressure β€” the box goes to the OOM killer instead of purging GPU memory. Same defect family as DF-4507 (TTM page pools) and complements it: DF-4507 = pool pages, DF-4819 = i915 GEM object pages.

Build

Nothing to compile β€” this is a code-structure defect. The verification is a shell script:

./build.sh          # no-op (explains why)

Run

./run.sh            # static proof against the workspace sys/ tree
# binary proof on a DragonFly guest:
sh check_shrinker_wiring.sh --module /boot/kernel/i915.ko

Expected output

  • Host static: six PASS lines β€” no register_shrinker in shim; registration #if 0'd; count/scan bodies #if 0 (return 0); vm_lowmem invoked 0 times; live i915_gem_shrink callers are explicit only (get_pages failure paths at i915_gem.c:2935, i915_gem_gtt.c:2449, freeze paths at i915_gem.c:6324/6353).
  • Guest binary: nm i915.ko shows i915_gem_shrinker_count/scan as local (t) stubs and no register_shrinker symbol/undefined reference; nm drm.ko shows no such symbol either.

Runtime impact demonstration

Not possible on this audit guest: it runs QEMU std VGA (chip=0x11111234), i.e. no Intel GPU, so i915 never attaches (kldstat clean). The DoS impact (unreclaimable GPU memory under pressure) requires real i915 hardware. This matches the precedent set by DF-4507 (recorded untested for the same reason). The code-level defect itself is certain β€” proven statically and against the compiled module binary.

VERDICT.md
↓ download raw

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

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix validation not possible: the fix requires new kernel VM infrastructure plus real i915 hardware to validate; the QEMU audit guest has no Intel GPU and the DoS baseline cannot be reproduced there in the first place.

n/a
per-fix-DF-4819

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: six PASS lines proving no registration path exists in the workspace sys/ tree; run.guest.log: nm of /boot/kernel/i915.ko shows i915_gem_shrinker_count/scan as local (t) stubs and no register_shrinker symbol in i915.ko or drm.ko, plus guest context (uname, QEMU std VGA, no drm modules loaded); VERDICT.md: full per-line narrative incl. pass-2 sweep of the live i915_gem_shrink walk (lockowned==curthread at kern_lock.c:1365, struct_mutex lifetime invariant, empty-list-safe splice, no self-deadlock, u64 arithmetic sound).

PoC changes

No seed PoC existed for this pass-2 finding; authored check_shrinker_wiring.sh (dual-mode: static tree proof + compiled-module nm proof). Base64 transfer to guest failed (DFly lacks base64(1)); ran the module-mode checks inline via vm.sh run_root instead β€” same commands, captured verbatim in run.guest.log.

Verified recommended fix

Implement register_shrinker in sys/dev/drm/include/linux/shrinker.h wired to a VM-pressure event that DragonFly actually invokes (e.g. make vm_pageout EVENTHANDLER_INVOKE(vm_lowmem,...)), then un-#if0 the i915 registration block and the count/scan bodies (converting spin_lock to lockmgr in the restored count callback).

Verdict

Structural defect CONFIRMED at source and binary level: the DFly linux-compat shim has no register_shrinker (sys/dev/drm/include/linux/shrinker.h is struct-only), the i915 registration block and the count/scan bodies are #if 0'd out (i915_gem_shrinker.c:313-395, 521-532), and vm_lowmem is never EVENTHANDLER_INVOKE'd anywhere in sys/ β€” so the i915 GEM shrinker can never run from memory reclaim. Verified statically (run.log, 6x PASS) and against the guest's compiled i915.ko/drm.ko symbol tables (run.guest.log: no register_shrinker symbol; count/scan are local dead stubs). The DoS impact (GPU shmem pages unreclaimable under pressure -> OOM kills) requires real i915 hardware, which the QEMU audit guest lacks (std VGA 1234:1111, i915 never attaches) β€” recorded untested/impact-none consistent with the DF-4507 family precedent; potential impact is local DoS (Medium).