# DF-2942 VERDICT — `sysref_activate` accepts termination-in-progress objects (negative-space ambiguity)

## Finding

The sysref protocol uses the same negative refcount space for two
mutually-exclusive states:

- **initializing**: `sysref_alloc()` sets `refcnt = -0x40000000`
  (`kern_sysref.c:164`) and the caller later calls `sysref_activate()`,
  which adds `0x40000001` (`-0x40000000 -> +1`);
- **terminating**: `_sysref_put()`'s `1 -> -0x40000000` cmpset
  (`kern_sysref.c:323`) enters termination-in-progress, then calls
  `ops.terminate` (`:324`).

`sysref_activate()`'s only gate is
`KASSERT(count < 0 && count + 0x40000001 > 0)` (`kern_sysref.c:280-281`),
which is *satisfied* by a terminating object (`-0x40000000 + 0x40000001 =
+1 > 0`). There is no `SRF_TERMINATING` distinction, so an activate that
lands in the termination window (or any caller that re-activates a
terminating object) resurrects the object to `+1` while its class
teardown is concurrently executing.

## Reproduction (guest, stock INVARIANTS kernel #0)

`sysref_probe.c` (synthetic `sysref_class`, terminate callback mirroring
`devfs_cdev_terminate`'s protocol). Trigger A drives a full lifecycle and
calls `sysref_activate()` from *inside* the terminate callback,
deterministically simulating a racing misuser:

```
terminate#1 entry refcnt=-1073741824            <- termination-in-progress
DF-2942: sysref_activate() on TERMINATING obj, before=-1073741824
DF-2942: after activate refcnt=1 (RESURRECTED; kern_sysref.c:280 KASSERT passed, no diagnostic)
terminate#2 entry refcnt=-1073741824            <- SECOND termination
A: result term_count=2 refcnt=0
A: CONFIRMED DOUBLE TERMINATION
```
(`run.log:1-13`) — the layer accepted the activation with **zero
diagnostics on the INVARIANTS kernel** and then invoked the class
terminate callback a **second time**.

Run 1 (probe variant re-activating on *every* termination) shows the
unbounded form: 292 consecutive terminate re-entries with the object
bouncing `+1 <-> -0x40000000`, each cycle nesting
`sysref_put -> _sysref_put -> ops.terminate -> sysref_put`, until the
kernel stack exhausted (`run.1.unbounded.log`: `Fatal double fault`,
`panic: double fault`).

## Why this matters (impact ceiling)

For a real class the second terminate re-executes the teardown that
already ran once. For the only in-tree consumer (cdev,
`devfs_core.c:2487-2507`): `devfs_cdev_terminate` would call
`devfs_release_ops()` a second time (decrementing the ops refcount past
zero — `devfs_core.c:2666` has no floor either — and potentially freeing
the tracking struct twice), then `lockmgr(&devfs_lock, LK_RELEASE)` an
extra time (lock underflow), then a second terminal `sysref_put` — i.e.
a double-terminate converts directly into lock-count and refcount
corruption for the class. The unbounded variant is a guaranteed
stack-exhaustion panic (demonstrated).

**Attacker reachability: none in-tree today.** The only live
`sysref_activate` caller is `devfs_new_cdev` (`devfs_core.c:2443`),
invoked exactly once immediately after `sysref_alloc` — the single-call
discipline is trivially maintained. The hazard is latent: the layer
*permits* the misuse and even its INVARIANTS asserts bless it. Filed as
Info hardening, confidence certain on the mechanism, speculative on any
future exploit path.

## Kernel references

- `sys/kern/kern_sysref.c:278-285` — `sysref_activate` and the accepting
  KASSERT (`:280`)
- `sys/kern/kern_sysref.c:164` — initialization sets the same
  `-0x40000000` sentinel
- `sys/kern/kern_sysref.c:314-327` — termination entry
  (`1 -> -0x40000000` at `:323`, `ops.terminate` at `:324`)
- `sys/sys/sysref.h:107-118` — `struct sysref` flags: no TERMINATING bit
- `sys/vfs/devfs/devfs_core.c:2443` — the only in-tree activate call
- `sys/vfs/devfs/devfs_core.c:2487-2507` — real-class terminate that a
  double invocation corrupts

## Fix

Distinguish the two negative-space states with a flag (see `fix.diff`):
set `SRF_TERMINATING` on the `1 -> -0x40000000` transition, clear it in
`sysref_alloc`/`sysref_ctor`, and require
`(sr->flags & SRF_TERMINATING) == 0` in `sysref_activate`'s assert.
Fix authored post-verification; not build-validated (Info hardening
finding; the fix's observable effect is the new assert itself).
