# DF-3769 — gen8 3-level PPGTT insert walk: no effective runtime bound on pdpe advance

## Finding
`gen8_ppgtt_insert_pte_entries()` (`sys/dev/drm/i915/i915_gem_gtt.c:1037-1092`)
hand-rolls the page-table walk instead of using the bounded
`gen8_for_each_pdpe` macro. Its pdpe wrap check at
`i915_gem_gtt.c:1075` compares against `GEN8_PML4ES_PER_PML4` (512), which
is only correct for 4-level VMs. On a 3-level VM (Broadwell default when
48-bit PPGTT is off; `i915_pdpes_per_pdp()` == 4), `pdp->page_directory`
is a 4-entry `kmalloc_array` (`__pdp_init`, `i915_gem_gtt.c:763-776`). If
`idx->pdpe` ever reached 4, the code would read
`pdp->page_directory[4]` (`i915_gem_gtt.c:1082`) out of bounds, then
`pd->page_table[idx->pde]` (another OOB deref of attacker-shaped heap
data), then `kmap_atomic_px()` it and write 8-byte PTEs.

The only guard is `GEM_BUG_ON(idx->pdpe >= i915_pdpes_per_pdp(&ppgtt->vm))`
(`i915_gem_gtt.c:1081`), and **`GEM_BUG_ON` is a compile-time no-op in
production kernels** (`i915_gem.h:60`, `#define GEM_BUG_ON(expr)
BUILD_BUG_ON_INVALID(expr)` unless `CONFIG_DRM_I915_DEBUG_GEM`).

## Why this is latent (not exploitable today)
For `idx->pdpe` to advance past 3, the scatterlist must supply **more than
4 GiB of pages** to a single VMA bound in a VM whose `total` is exactly
2^32. The node/sg invariants forbid that:
- `vma_create()` (`i915_vma.c:151`) sets `vma->size = obj->base.size`,
  and `i915_vma_insert()` allocates `node.size == round_up(vma->size)`
  inside `[0, vm->total)` via drm_mm;
- `vma->pages` is the object's sg table, whose page count equals
  `round_up(obj->base.size)/PAGE` — never more than the node;
- the walk breaks out at `!iter->sg` (`i915_gem_gtt.c:1059`) **before**
  the pde/pdpe advance whenever the sg ends exactly at the 4 GiB boundary.

So the bug needs a future invariant break (e.g. a badly reworked
view/sg path) to become live memory corruption. Defense-in-depth /
hardening finding — same code shape as upstream Linux v4.19 (fixed
upstream only by the v5.x rewrites that retired this walker).

## Recommended fix
Make the wrap check level-aware and clamp, e.g.
`if (++idx->pdpe == i915_pdpes_per_pdp(&ppgtt->vm)) { idx->pdpe = 0; ret = true; break; }`
— for 3lvl the caller (`gen8_ppgtt_insert_3lvl`) then relies on the
existing sg termination. See `fix.diff`.

## Reproduction
Not reproducible at runtime: requires invariant break + Broadwell-class
hardware; audit guest is virtio-only (no i915 device, no i915kms.ko).
Source-level verification only. Verification skipped per contract
(Info hardening finding).
