# DF-4791 — VERDICT

**Finding:** unbounded recursion between `ttm_shrink()` and
`ttm_bo_swapout()` in TTM memory accounting exhausts the 16 KiB kernel
stack; depth is attacker-controlled via the number of swappable
VRAM-resident buffer objects on `ttm_bo_global::swap_lru`.

**Status: not_reproduced on the audit guest — feature absent.**
The audit guest is QEMU with std-VGA: no radeon/amdgpu/i915 hardware,
no `/dev/dri` (env.txt, run.log). The trigger builds cleanly on the
guest (build.log: BUILD_OK) and exits 2 at `open("/dev/dri/card0",
ENOENT)`. The bug is proven structurally below; runtime proof requires
a GPU-driven DRM device, which this guest cannot provide (kernel DRM
drivers in this tree bind only real hardware).

## The recursion chain (every hop verified in this tree)

1. Any accounting-checked allocation under memory pressure:
   `ttm_mem_global_alloc_zone` (sys/dev/drm/ttm/ttm_memory.c:502-521)
   fails `ttm_mem_global_reserve` (used_mem > max_mem/emer_mem,
   ttm_memory.c:477-481) and, because `ctx->no_wait_gpu == false`,
   calls **`ttm_shrink`** (ttm_memory.c:517).
2. `ttm_shrink` (ttm_memory.c:213-229) drops `glob->lock` and calls
   **`ttm_bo_swapout`** (ttm_memory.c:222 → sys/dev/drm/ttm/ttm_bo.c:1768).
3. `ttm_bo_swapout` picks the next BO from `swap_lru`
   (ttm_bo.c:1776-1785). Which BOs sit there? `ttm_bo_add_to_lru`
   (ttm_bo.c:182-187) adds **every BO with a ttm that is not SG and not
   already swapped** — including freshly-created, never-touched VRAM
   BOs whose `ttm_tt` exists in `tt_unpopulated` state (ttm pages are
   populated lazily). An unprivileged /dev/dri client creates those at
   will via GEM create.
4. For such a BO (`bo->mem.mem_type != TTM_PL_SYSTEM`), swapout must
   move it to SYSTEM|CACHED first (ttm_bo.c:1807-1819) using its local
   `ctx = { .interruptible=false, .no_wait_gpu=false }` (ttm_bo.c:1809).
5. The move (`radeon_bo_move` VRAM→SYSTEM → `radeon_move_vram_ram`'s
   staging TT bind, or directly `ttm_bo_handle_move_mem` for a
   non-FIXED destination, ttm_bo.c:331-347) reaches
   **`ttm_tt_bind` → `ttm_tt_populate`** (sys/dev/drm/ttm/ttm_tt.c:341)
   → driver populate → **`ttm_pool_populate`**
   (sys/dev/drm/ttm/ttm_page_alloc.c:894-917).
6. `ttm_pool_populate` accounts each page:
   **`ttm_mem_global_alloc_page`** (ttm_page_alloc.c:910) →
   `ttm_mem_global_alloc_zone` — and we are *still above the zone
   limit* (that is why step 1 is swapping), `no_wait_gpu` is still
   false (step 4's ctx) → **`ttm_shrink` again** (ttm_memory.c:517).

   **→ step 2 with the *next* swap_lru entry** (the current BO was
   removed from the LRU at ttm_bo.c:1800 before the move, so each level
   descends to a fresh BO). The `count` in `ttm_mem_global_alloc_zone`
   bounds *iterations per level*, never nesting depth; the only depth
   bound is the swap_lru run length — attacker-controlled.

## Why it overflows

Per nesting level the kernel stack gains ≥10 frames:
`ttm_shrink` → `ttm_bo_swapout` → `ttm_bo_handle_move_mem` →
`radeon_bo_move` → `radeon_move_vram_ram` → `ttm_bo_move_ttm` →
`ttm_tt_bind` → `ttm_tt_populate` → `radeon_ttm_tt_populate` →
`ttm_pool_populate` → `ttm_mem_global_alloc_page` →
`ttm_mem_global_alloc_zone` → (next `ttm_shrink`).
~1-2 KiB per level. DragonFly x86_64 kernel stacks are `UPAGES * PAGE_SIZE`
= 4 × 4 KiB = **16 KiB** (sys/cpu/x86_64/include/param.h:126); lwkt
thread stacks are kmem allocations with no guard page, so overflow
smashes adjacent kernel memory before/instead of faulting. ~10-16
levels suffice; the trigger parks 64.

## Attack prerequisites

* Machine with a GPU driven by this tree's radeon or amdgpu driver
  (both share the TTM core; amdgpu has the same VRAM→system staging
  path via `amdgpu_move_vram_ram`).
* Unprivileged local user able to open `/dev/dri/card0` (video/operator
  class membership on a desktop system).
* ~zone-limit worth of populated GTT BOs to push `used_mem` over
  `max_mem` (zone sized from the vm.dma_reserved pool at driver load,
  ttm_memory.c:334 — typically tens of MiB) plus N≥32 untouched VRAM
  BOs as recursion fuel. All GEM ioctls, unprivileged.

## Impact

Kernel stack overflow from unprivileged context = kernel memory
corruption (adjacent-kmemory smearing) / guaranteed panic. Primary
classification: local DoS with memory-corruption ceiling; controlled
exploitation beyond panic is not claimed.

## Not a false positive / not already-fixed

* The whole chain exists in this tree today — every hop cited above.
* The DFly kobject/workqueue compat introduces no guard that would cap
  nesting (`linux_kobject.c` only stubs sysfs; workqueue is real).
* Upstream Linux *removed this entire mechanism* in 2021
  (commit b12dacf5fa64 "drm/ttm: nuke memory accounting shrinker"),
  citing the shrinker's re-entrancy/deadlock problems — strong evidence
  the mechanism, not just our trigger, is unsound.

## Verification honesty

* Guest run attempted: build OK, run exits 2 (no DRM device) — run.log.
* No kernel was patched (fix.diff is authored, never applied to sys/),
  so no fix validation run was possible either — see verdict.json
  `fix_status: not_testable`.

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