# DF-4521 — drm_mm range iterator yields the head_node sentinel and never
# terminates when `end` > mm end

## What

`drm_mm_for_each_node_in_range()` (macro in
`sys/dev/drm/include/drm/drm_mm.h:499-502`, seed `__drm_mm_interval_first()`
in `sys/dev/drm/drm_mm.c:171-176`) returns the **unallocated** `head_node`
sentinel (`allocated=false`, `start = mm end`, `size = -mm_size` wrapped)
into the caller's loop body whenever the query range extends past the last
allocated node, and — because `node_list` is circular — **wraps around and
iterates forever** when `end > head_node.start`.

The header itself documents the hazard (drm_mm.h:493-495: "the iterator may
walk over the special _unallocated_ &drm_mm.head_node, and may even continue
indefinitely") but nothing enforces it. `i915_gem_evict_for_node()`
(sys/dev/drm/i915/i915_gem_evict.c:292-296) violates the documented contract
by unconditionally adding one guard page to `end` (`end += I915_GTT_PAGE_SIZE`)
when the target node ends at the top of the GTT, so
`end == vm->total + 4K > head_node.start == vm->total`.

## Consequences (trigger chain, all lines verified in this tree)

1. unprivileged execbuf2 with `EXEC_OBJECT_PINNED` (softpin; only
   alignment/canonical form is validated — i915_gem_execbuffer.c:461-463 —
   and it becomes `PIN_OFFSET_FIXED` at :614-615) at
   `offset = vm->total - obj_size`, conflicting with an existing vma at the
   top of the GGTT;
2. `i915_vma_pin` → `i915_gem_gtt_reserve()` (i915_gem_gtt.c:3949-3975);
   `drm_mm_reserve_node()` → `-ENOSPC` → `i915_gem_evict_for_node()`;
3. with `color_adjust` set (i915_gem_gtt.c:3638-3639: `!HAS_LLC &&
   !USES_PPGTT` platforms / forced `i915_enable_ppgtt=0`), `end += 4K`;
4. the iterator then:
   - **never terminates** (livelock with `struct_mutex` held → local DoS), or
   - hands `&mm->head_node` to the loop body, which does
     `vma = container_of(node, i915_vma, node)` on kernel-owned memory
     (i915_gem_evict.c:318); `GEM_BUG_ON(!node->allocated)` (:317) is a
     **no-op in release builds** (i915_gem.h:60 → BUILD_BUG_ON_INVALID);
   - the fake vma is pinned/list_add'ed and finally unbound:
     `i915_vma_unbind` → `i915_vma_remove` → `drm_mm_remove_node(&vma->node)`
     **== `drm_mm_remove_node(&mm->head_node)`** (i915_vma.c:687-695).

`drm_mm_remove_node()` on the sentinel (proven live, see corrupt.log):
`rm_hole()` legitimately removes the head's hole, then
`drm_mm_interval_tree_remove()` rb-erases a node that was **never inserted**
→ the DFly shim's plain `RB_REMOVE` wipes the interval-tree root → **all
allocated nodes become invisible to range queries** (overlapping placements /
GTT aliasing become possible); `list_del(head)` then leaves `prev->next`
pointing at the first node so `add_hole(prev)` computes a **size-underflowed
bogus hole** (`[0x100000000, 0x10000000)`, size 0xffffffff10000000) that
permanently pollutes the hole trees.

## Reproduce (on the QEMU guest, root)

```
cd /tmp/DF-4521
sh run.sh        # scenario 1: sentinel walk — see dmesg / run.log
sh corrupt.sh    # scenario 2: sentinel remove + corruption probes — corrupt.log
```

The kld embeds the **real, unmodified** `/usr/src/sys/dev/drm/drm_mm.c` and
the real `drm_mm_for_each_node_in_range` macro; it drives them with the exact
parameters `i915_gem_evict_for_node()` would use for a top-of-GTT target
(4 GiB GGTT, node at `[0xfffff000, 0x100000000)`, walk range
`[0xffffe000, 0x100001000)`).

### Expected (baseline, unpatched)

```
[dfmm]   visit 1: start=fffff000 size=1000 allocated=1 color=0
[dfmm]   visit 2: start=100000000 size=ffffffff00000000 allocated=0 color=0   <== HEAD SENTINEL
[dfmm]   visit 3..8: (same nodes repeat — walk never terminates; capped)
[dfmm] RESULT: visited=8 head_sentinel_yielded=1 non_terminating=1
```

### Expected (after fix.diff applied to /usr/src/sys)

```
[dfmm]   visit 1: start=fffff000 size=1000 allocated=1 color=0
[dfmm] RESULT: visited=1 head_sentinel_yielded=0 non_terminating=0
```

## Notes

- The full i915 trigger cannot run on this guest (Bochs/QEMU vga, no Intel
  GPU), so the i915 arm is verified by source-line tracing; the drm_mm core
  defect and its corruption consequences are verified live with the real
  kernel code compiled into a kld.
- This behavior is inherited from Linux 4.12 (upstream drm_mm/i915 of that
  era are line-identical here); modern upstream i915 no longer uses this
  eviction path with an unclamped end.
