# DF-2866 — VERDICT

**Status: reproduced (dispatch primitive, stock kernel) / fixed
(patched kernel).**
**Impact: panic (demonstrated: uncontrolled indirect call through an
out-of-bounds `pagerops *`).  Latent in-tree reachability — no current
path dispatches a marker; the PoC constructs one, proving the missing
bounds check is real and what it does when hit.**

## What was run

KLD harness `pgtmark.c` on the stock INVARIANTS guest kernel
(see ../DF-2865/env.txt).

### Stage 1 — the OOB slot itself (run.log)

```
PGTMARK: &pagertab=0xffffffff81120fc0 OBJT_MARKER=7 (pagertab has 7 legal slots 0..6)
PGTMARK: pagertab[0..6] = <7 valid pagerops pointers>
PGTMARK: pagertab[7] = 0   <-- OOB SLOT
```

`pagertab[7]` is not a pagerops — it is whatever the linker placed
after the array (zero on this build).

### Stage 2 — dispatching an OBJT_MARKER object (run.log + panic.txt)

```
PGTMARK: dispatching OBJT_MARKER(7) object via vm_pager_has_page(); pagertab[7] = 0
Fatal user address access from kernel mode from sysctl ...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x18
instruction pointer = pgtmark_fire_sysctl.part.0+0x53:  movq 0x18(%rax),%rax
```

The inlined `vm_pager_has_page()` (vm_pager.h:168) loaded
`pagertab[7]` into `%rax` and dereferenced `+0x18` (pgo_haspage) —
kernel-mode fault at address 0x18, guest down at `db>`.  With a
non-zero word after the array (different link layout) the same path is
a call through an uncontrolled pointer instead of a NULL fault — the
bounds check is missing either way.

## Honesty notes

- The trigger is synthetic: a KLD builds a stack `vm_object` with
  `type = OBJT_MARKER` and calls `vm_pager_has_page()`.  All three
  in-tree marker scan sites (sys/vm/vm_object.c:1847,1960;
  sys/vm/swap_pager.c:2180; sys/vm/vm_swapcache.c:728) currently check
  for markers before operating, so nothing in-tree reaches the
  dispatch with one today.  The finding is a latent OOB-dispatch
  hazard (the DF-0944 family), rated Low.
- The demonstrated primitive is nevertheless exact: 8-value enum,
  7-entry table, unbounded index — any marker (or type-corrupted
  object) reaching any of the four dispatchers panics or worse.

## Patched kernel (fix.diff hunk 1: `&deadpagerops` slot for OBJT_MARKER)

Combined build with DF-2865's fix (fix_build.log); stage 2 on the
patched kernel (run.patched.log):

```
PGTMARK: pagertab[7] = 0xffffffff81121380   <-- now &deadpagerops (== pagertab[6])
PGTMARK: dispatching OBJT_MARKER(7) object via vm_pager_has_page(); pagertab[7] = 0xffffffff81121380
PGTMARK: vm_pager_has_page returned 0 -- NO PANIC (fixed kernel)
```

`pagertab[7]` now resolves to `&deadpagerops`; the dead pager's
`pgo_haspage` returns FALSE.  Guest stayed up, module unloaded.

## Kernel references

- sys/vm/vm_pager.c:152-160 (7-entry pagertab)
- sys/vm/vm_object.h:118-127 (8-value enum obj_type, OBJT_MARKER=7)
- sys/vm/vm_pager.c:337-341 (vm_pager_deallocate, unbounded index)
- sys/vm/vm_pager.h:128-140,142-152,165-169 (get/put/haspage inlines,
  unbounded index)
- Marker producers: sys/vm/swap_pager.c:2171, sys/vm/vm_swapcache.c:220,
  sys/vm/vm_object.c:1847,1960
