# DF-1146 — i915_gem_fault missing offset bounds + partial-VMA GMADR offset (i915_gem.c)

## Verdict
**REPRODUCED (code-level, latent at runtime).** The DragonFly-specific port of
`i915_gem_fault` omits the object-size bounds check that upstream Linux
performs, and miscomputes the GMADR address for partial GGTT views. Both
confirmed by source trace. **Not triggerable at runtime on this guest** (no
Intel i915 GPU; i915 not in GENERIC) -> runtime **not_testable**. `fix.diff`
validated to **apply + compile** under `-Werror`.

## Mechanism (trigger -> primitive -> effect)

### Defect 1 — missing object-size bounds on the fault offset
- `i915_gem_fault` (`sys/dev/drm/i915/i915_gem.c:2229`) computes
  `page_offset = (unsigned long)offset >> PAGE_SHIFT;` at `:2263` and uses it
  throughout **with no check** that `offset < obj->base.size`.
- **Reachability amplifier:** `drm_gem_mmap_single`
  (`sys/dev/drm/drm_gem.c:1083-1109`) passes the user's mmap `size` straight
  into `cdev_pager_allocate(... size ...)` (`:1099-1100`) without clamping to
  `gem_obj->size`. So a process can `mmap()` a tiny GEM object with a giant
  length; the backing `vm_object` then covers more than the object, and faults
  at `offset >= obj->base.size` reach `i915_gem_fault` unguarded.
- Upstream Linux guards this: `if (page_offset >=
  round_up(obj->base.size, PAGE_SIZE) / PAGE_SHIFT) return VM_FAULT_SIGBUS;`
  in its `i915_gem_fault`. The DragonFly port dropped it.
- **Effect:** the GMADR address computed at `:2391-2392`
  (`ggtt->gmadr.start + vma->node.start + offset`) goes past this object's GTT
  slot, returning/writing pages from neighbouring GTT slots in the GMADR
  aperture (read+write of unrelated GPU mappings).

### Defect 2 — partial-VMA GMADR offset not subtracted
- For an `I915_GGTT_VIEW_PARTIAL` VMA, `compute_partial_view`
  (`:2129-2149`) sets `view.partial.offset = rounddown(page_offset, chunk)`
  and the VMA node maps only the chunk `[partial.offset, partial.offset+size)`.
- The GMADR computation `:2391-2392` adds the *object-relative* `offset` to the
  *chunk-relative* `vma->node.start` **without** subtracting
  `partial.offset * PAGE_SIZE`. So a fault into a partial VMA returns the wrong
  page (offset by `partial.offset` pages) — i.e. pages from a neighbouring GTT
  slot. (Upstream uses `remap_io_mapping` which handles this; the DFly port's
  hand-rolled `vm_phys_fictitious_to_vm_page` does not.)
- The correct idiom already exists in the driver: `i915_vma.c:905` uses
  `vma->ggtt_view.partial.offset << PAGE_SHIFT`.

## Threat model / reachability
- **Attacker:** any unprivileged user on a machine with an Intel i915 GPU who
  can open the dri render node and `mmap`/`I915_GEM_MMAP_GTT` a GEM object with
  a length larger than the object, then fault past its end; or trigger the
  partial-VMA miscompute. Classic local privesc surface on laptops/desktops
  with Intel graphics.
- **On this guest:** NOT reachable — only QEMU std VGA (`0x1234:0x1111`), i915
  not in GENERIC. Valid hard blocker: runtime-unreachable here, latent on
  Intel-i915-equipped HW.

## Exploit chain
None developed — valid hard blocker (no i915 GPU on this guest to attach the
driver, so the fault path is never reached). Demonstrated work is the
source-level confirmation + compiling fix. On real i915 HW the chain would be:
open dri render node -> `I915_GEM_CREATE` a small object -> `mmap` with an
oversized length -> touch pages past `obj->base.size` to read/write
neighbouring GTT slots. No `exploit.c` (cannot run on this guest).

## PoC changes
No trigger PoC seeded. This folder adds `fix.diff`, `build.sh`, `run.sh`,
`VERDICT.md`, `manifest.json`, `env.txt`, `build.log`, `README.md`.

## Recommended fix
In `i915_gem_fault`, add `if (offset >= obj->base.size) { ...; return
VM_PAGER_ERROR; }` right after computing `page_offset`, and subtract
`vma->ggtt_view.partial.offset << PAGE_SHIFT` from the GMADR address when the
VMA is a PARTIAL view. Implemented in `fix.diff` (2 hunks). (An optional
additional defense-in-depth — clamping the mmap `size` in `drm_gem_mmap_single`
to `gem_obj->size` — is noted but left out of this per-finding diff because
`drm_gem.c` is shared by all DRM drivers and the fault-handler bounds check
alone fully closes the i915 OOB.) **Supersedes** a partial fix; **matches**
the finding's cited root cause at `i915_gem.c:2263` and `:2391-2392`.
