# DF-3768 — i915 GGTT aperture fictitious-range registered, never unregistered (DFly)

## Finding (summary)
DragonFly-specific code in `sys/dev/drm/i915/i915_gem_gtt.c:2965-2970`
(`i915_gem_init_ggtt()`) registers the GMADR (GTT aperture) PCI BAR as a
"fictitious" physical range so that CPU mmaps of GTT-bound objects can be
backed by fake `vm_page`s (`i915_gem.c:2391` resolves user faults through
`vm_phys_fictitious_to_vm_page(ggtt->gmadr.start + ...)`).

The matching teardown never happens: `i915_ggtt_cleanup_hw()`
(`i915_gem_gtt.c:2992-3029`) does NOT call `vm_phys_fictitious_unreg_range()`.
The shim backing these calls (`sys/dev/drm/drm_vm.c:141-227`) keeps a fixed
array of 8 global segments (`VM_PHYS_FICTITIOUS_NSEGS`) and registers into
the **first free slot** — it does not detect duplicates.

Consequences (per driver attach/detach cycle — kldload/kldunload of
`i915kms.ko`, or any error-probe → re-probe of the PCI device):

1. **Unbounded kernel-memory leak**: every (re-)attach allocates a fresh
   fake `vm_page` array of `(aperture_size / PAGE_SIZE)` entries and keeps
   the old one alive (nothing frees it). A 256 MB mappable aperture ⇒
   65,536 fake pages (~6 MB+ per cycle on x86_64) — repeatable without bound.
2. **Slot exhaustion → graphics DoS**: after 8 cycles the segment array is
   full and `vm_phys_fictitious_reg_range()` returns `EBUSY`, failing
   `i915_gem_init_ggtt()` → the driver can never attach again until reboot.
3. **Stale segment after unload**: the un-freed registration keeps matching
   the BAR's physical range after the driver is gone; a subsequently
   attached device that lands on the same BAR address is resolved to the
   stale shadow array first.

Contrast in the same tree: the radeon driver performs the symmetric
lifecycle (`radeon_device.c:1493` register / `radeon_device.c:1566`
unregister), proving the omission is i915-specific and not a shim
limitation.

## Threat model
Trigger requires root (module load/unload or device re-probe). No
unprivileged trigger exists in this file. Impact is resource exhaustion /
availability of the graphics stack plus unbounded kernel heap growth —
**not** memory corruption. Severity: **Low**.

## Source-level proof points
- `sys/dev/drm/i915/i915_gem_gtt.c:2966-2970` — the registration (inside
  `#ifdef __DragonFly__`; no upstream counterpart).
- `sys/dev/drm/i915/i915_gem_gtt.c:2992-3029` — `i915_ggtt_cleanup_hw()`
  has no `vm_phys_fictitious_unreg_range` and no free of the shadow array.
- `sys/dev/drm/drm_vm.c:144-204` — 8-slot global array; first-free-slot
  insert; duplicate registration silently succeeds and leaks the older
  array; `EBUSY` when full.
- `sys/dev/drm/drm_vm.c:207-227` — unreg exists and frees the array, but is
  never called by i915.
- `sys/dev/drm/radeon/radeon_device.c:1493,1566` — symmetric lifecycle
  done correctly by radeon.
- `sys/dev/drm/i915/i915_gem.c:2391-2395` — why the registration must
  outlive mappings, and why the correct fix mirrors radeon's (unregister at
  ggtt cleanup after all VMAs/mappings are torn down), not "drop it".

## Reproduce (requires real Intel graphics hardware + root)
```
# on a DragonFly host with i915 hardware and i915kms.ko available:
for i in 1 2 3 4 5 6 7 8 9; do
    kldunload i915kms && kldload i915kms
done
# cycle 9: console shows "taking over the fictitious range ..." absent /
# probe fails with EBUSY; vmstat/kmem statistics show ~8 leaked arrays.
```
The audit guest has no Intel GPU (virtio 0x1111:1234) and no i915kms.ko,
so the runtime demonstration is `not_reproduced` on this guest — see
VERDICT.md. The logic-level claim is source-certain.

## Fix
`fix.diff` in this directory (against the read-only `sys/` tree; never
applied by the auditor): unregister the fictitious range in
`i915_ggtt_cleanup_hw()` after VMA teardown, mirroring radeon.
