# DF-2866 — pagertab[] has 7 slots but obj_type has 8 values: OBJT_MARKER dispatch reads past the array

`sys/vm/vm_pager.c:152-160` + `sys/vm/vm_object.h:118-127` +
`sys/vm/vm_pager.h:128-169`.

## The bug

```c
enum obj_type { OBJT_DEFAULT, OBJT_SWAP, OBJT_VNODE, OBJT_DEVICE,
                OBJT_MGTDEVICE, OBJT_PHYS, OBJT_DEAD,
                OBJT_MARKER };               /* == 7 */

struct pagerops *pagertab[] = { ... 7 entries, 0..6 ... };
```

Every dispatcher indexes `pagertab[object->type]` with no bounds check:
`vm_pager_deallocate()` (vm_pager.c:340) and the
`vm_pager_get_page`/`vm_pager_put_pages`/`vm_pager_has_page` inlines
(vm_pager.h:134/150/168).  An object with `type == OBJT_MARKER` (7)
reads one `struct pagerops *` **past the end** of `pagertab` and calls
through it (`pgo_haspage` at +24).

OBJT_MARKER objects are real, in-tree, kernel-stack-allocated fake
objects used as list-scan markers (sys/vm/vm_object.c:1847/1960,
sys/vm/swap_pager.c:2171, sys/vm/vm_swapcache.c:220).  All current
scan sites check `type == OBJT_MARKER` before dispatching, so the bug
is *latent* — but any future (or present, un-audited) path that hands
such an object, or any object whose `type` byte is corrupted, to a
pager dispatch gets an uncontrolled indirect call.  Same defect family
as DF-0944 (vm_fault.c MGTDEVICE NULL page deref).

## PoC

`pgtmark.c` — KLD harness, two stages:

1. `kldload` prints `pagertab[0..7]` (index 7 = the OOB slot; on this
   kernel build it reads NULL — whatever the linker placed after the
   array).
2. `sysctl vm.pgtmark_fire=1` constructs a stack `vm_object` with
   `type = OBJT_MARKER` and calls `vm_pager_has_page()` — exactly what
   a dispatcher-reaching path would do.

```sh
./build.sh
./run.sh
```

Expected (stock kernel): stage 2 faults loading `0x18(%rax)` —
`pagertab[7] == NULL -> pgo_haspage` — and panics.  With the fix
(OBJT_MARKER slot = `&deadpagerops`) it returns FALSE and prints
`NO PANIC`.

## Observed (stock kernel, run.log + panic.txt)

```
PGTMARK: pagertab[7] = 0   <-- OOB SLOT
PGTMARK: dispatching OBJT_MARKER(7) object via vm_pager_has_page(); pagertab[7] = 0
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
```

NULL on this link layout → clean fault/panic.  A layout where a
non-zero .data word follows the array yields a call through an
attacker-uncontrolled pointer instead — uncontrolled dispatch either
way.

The trigger is synthetic (KLD-constructed marker) because no in-tree
path currently reaches the dispatch with a marker — that is what
"latent" means here; the PoC proves the dispatch primitive and the
missing bounds, not an existing attacker path.

## Fix

`fix.diff` (hunk 1): give `pagertab` an eighth slot,
`&deadpagerops /* OBJT_MARKER */`, making every dispatch through a
marker-object type land in the dead pager (returns FAIL/AGAIN/FALSE)
instead of off the end of the array.  (Hunks 2-6 of the same diff are
the DF-2865 cmpxchg reservation fix — the two were validated on one
combined kernel build.)
