# DF-2677 — SysV shmat() vs IPC_RMID TOCTOU: VM object ref leak → vm_object_terminate2 panic

## What

`sys_shmat()` (sys/kern/sysv_shm.c) holds `shm_token`, then can **block**
(inside `vm_object_hold()` and `vm_map_find()`); LWKT releases the token
across blocking points.  The attach is only accounted at the very END of
the syscall (`shmseg->shm_nattch++`, sysv_shm.c:~1055).  A concurrent
`shmctl(seg, IPC_RMID, NULL)` — serialized only by `shm_token` — running
inside that dropped-token window sees `shm_nattch == 0`
(sysv_shm.c:~444), calls `shm_deallocate_segment()` → `vm_object_deallocate()`
→ `vm_object_terminate()`.  The in-flight shmat then does
`vm_object_reference_locked()` on the `OBJ_DEAD` object and maps it, and
terminate (unconditional check, not INVARIANTS-gated) panics:

```
panic: vm_object_terminate2: object with references, ref_count=1
```

Worse interleavings (RMID after the reference but before `nattch++`) free
the segment while a live mapping exists: the later `shmdt` decrements
`shm_nattch` of a **recycled** shmseg slot (attacker re-creates segments via
shmget), corrupting the new segment's accounting — bounded kernel-memory
corruption; and `shm_delete_mapping` may double-deallocate a live segment's
object.

## Trigger

Unprivileged local users (two cooperating processes — or one process with
two threads: shmat vs shmctl need no special rights beyond the segment's
perms).  Reliably reproduced with `kern.ipc.shm_use_phys >= 2` (a documented,
root-set performance tunable; the kernel's shmget pre-allocation loop holds
the object token and massively widens shmat's blocking window).  At the
default (`=1`) the window still exists in principle but is far narrower;
~5 min of racing did not reproduce it (see panic.txt matrix).

## Contents

| file | what |
|---|---|
| `shm_rmid_race.c` | PoC: creator (shmget + IPC_RMID churn) + 8-16 faulter processes (shmat + write-fault storms) |
| `fix.diff` | count the attach before any blocking op; unwind on vm_map_find failure |
| `panic.txt` | two captured panics (via sys_shmdt and via sys_shmctl) + source-annotated race window |
| `verdict.json`, `manifest.json` | machine verdict |

## Build & run (as any unprivileged user)

```sh
cc -O2 -Wall -o shm_rmid_race shm_rmid_race.c
# (root, once, widens the window dramatically:)
sysctl kern.ipc.shm_use_phys=2
su -m nobody -c ./shm_rmid_race
```

## Expected (vulnerable kernel)

Within ~a minute at `shm_use_phys=2`: console shows
`panic: vm_object_terminate2: object with references, ref_count=1` with
`shm_deallocate_segment` ← `vm_object_deallocate` ← `sys_shmctl` (or
`sys_shmdt`) in the trace; guest dies at ddb.

## Expected (patched kernel)

The race runs to completion (`round complete, no panic`), repeatedly;
no terminate2 panic.
