# DF-2983 — phys_pager_putpages panic: wiring faults create queue-managed pages in OBJT_PHYS objects

## What

`phys_pager_putpages()` unconditionally executes `panic("phys_pager_putpage called")`
(sys/vm/phys_pager.c:114).  The pager's design assumption is that every page in an
OBJT_PHYS object is `PG_UNQUEUED` (unmanaged, never on a page queue) because
`phys_pager_getpage()` sets that flag (phys_pager.c:99).

That assumption is violated by the page-fault **wiring path**: `TRYPAGER()` is false
for any fault carrying `VM_FAULT_WIRE_MASK` (sys/vm/vm_fault.c:383-385), so a wiring
fault on a not-yet-resident page of an OBJT_PHYS object never calls the pager; the
page is allocated by `vm_page_alloc()` (no PG_UNQUEUED) and zero-filled at
vm_fault.c:2327 — a **queue-managed** page in a phys object.

When such a page is later unwired (`munlock` / process exit: `vm_fault_unwire()` →
`vm_page_unwire(m, 1)`, vm_fault.c:2694 → vm_page.c:3297-3315) it lands on the active
queue, dirty after any write.  Under memory pressure the pagedaemon launders it:

```
vm_pageout_scan_inactive: type != OBJT_SWAP && != OBJT_DEFAULT  -> swap_pageouts_ok=1
    (vm_pageout.c:1186-1189 — the code assumes "not anonymous" == "vnode")
vm_pageout_page() -> vm_pageout_clean_helper()  (PG_UNQUEUED check at :329 passes)
    -> vm_pageout_flush() -> vm_pager_put_pages() (vm_pageout.c:509)
    -> phys_pager_putpages() -> panic  (phys_pager.c:114)
```

Reproduced stack (serial console):

```
panic: phys_pager_putpage called
cpuid = 3
phys_pager_putpages() at phys_pager_putpages+0x12
vm_pageout_flush() at vm_pageout_flush+0x16d
vm_pageout_clean_helper() at vm_pageout_clean_helper+0x358
vm_pageout_page() at vm_pageout_page+0x46b
vm_pageout_thread() at vm_pageout_thread+0x1fc8
Debugger("panic")
```

## Attack surface / privilege

The only in-tree producer of OBJT_PHYS objects is SysV shm with
`kern.ipc.shm_use_phys=1` (the default, sys/kern/sysv_shm.c:124,540-542).

**Trigger privilege:** on DragonFly `mlock(2)`/`mlockall(2)` are gated by
`caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT)` (sys/vm/vm_mmap.c:1030 and :1059;
the `#ifdef pmap_wired_count` RLIMIT branch is dead code — no platform defines
`pmap_wired_count`).  The reproducer therefore runs as root: any privileged process
that legally uses SHM + mlock (databases, chrome-style shm + mlock daemons) crashes
the kernel as soon as the pagedaemon launders.  Not reachable by uid!=0 on a stock
system today — but it is a live landmine: the vm_mmap.c `#ifdef` shows an
rlimit-gated unprivileged mlock was intended, and enabling it turns this into an
unprivileged panic instantly.

## Impact

Kernel panic — full system crash (local availability), triggered by ordinary
documented syscall usage (no bug in the caller).  Severity: Low on stock DF
(requires SYSCAP_RESTRICTEDROOT to wire), certain reproducibility.

## Reproduce (baseline)

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC), 4 GB RAM + 4 GB swap.

```
./build.sh                                  # cc -O2 -o df2983 df2983.c
# as root (mlock requires SYSCAP_RESTRICTEDROOT on DF):
./run.sh                                    # stage A: shmget 512MB, mlock, write,
                                            #          munlock -> dirty managed pages
                                            # stage B: 1.5GB unreclaimable phys hog
                                            # stage C: anonymous eater (eat3g.c)
# ~1-2 minutes under memory pressure:
#   panic: phys_pager_putpage called        (see panic.txt)
```

Note: the original `df2983.c` embeds the anon eater (512 MB); the decisive run used
the external `eater.c` (3 GB) started after the main program finished staging,
because 2.5 GB of hog alone did not push the pagedaemon into laundering fast enough.

## Fix

`fix.diff` (two hunks):

1. **Root cause** — sys/vm/vm_fault.c: in the no-backing-object zero-fill branch,
   mirror `phys_pager_getpage()` for OBJT_PHYS pages: set `PG_UNQUEUED` so wiring
   faults stop producing queue-managed pages in phys objects.  This also preserves
   SHM data semantics (phys pages then never get reclaimed/zeroed by the daemon).
2. **Defense in depth** — sys/vm/phys_pager.c: `phys_pager_putpages()` returns
   `VM_PAGER_FAIL` for every page instead of panicking (pages stay dirty+resident;
   contents preserved).

Validated: with the fix applied (nativekernel rebuild, same guest), the identical
PoC + 3 GB eater ran to completion with the guest staying up; re-run of the eater
confirmed sustained laundering pressure with no panic.  See VERDICT.md.
