# DF-5586 — vkernel `exc_segfault()` tail `splz()` is unguarded: deferred IPIs / interrupt work dispatched inside the interrupted context's critical section (protocol divergence from pc64 `doreti`'s explicit TD_CRITCOUNT gate)

## What

`sys/platform/vkernel64/x86_64/exception.c:269-271` — the vkernel's
kernel-mode fault return path:

```c
	save = errno;
	kern_trap((struct trapframe *)&ctx->uc_mcontext.mc_rdi);
	splz();                                   /* ← unconditional */
	errno = save;
```

The real kernel runs pending interrupts on trap return through `doreti`,
which **refuses to dispatch anything while the interrupted thread holds a
critical section** — `sys/platform/pc64/x86_64/ipl.s:132-146`:

```
 * If we are in a critical section we cannot run any pending ints.
 ...
	testl	$-1,TD_CRITCOUNT(%rbx)	/* can't unpend if in critical sec */
	jne	5f			/* → straight to doreti_iret */
```

and the generic kernel only ever invokes `splz()` through guarded wrappers
(`lwkt_maybe_splz()`, `sys/kern/lwkt_thread.c:1091-1101`: critcount==0 ∧
gd_intr_nesting_level==0 ∧ td_nest_count<2; `splz_check()`, :1068-1078).
The vkernel's own `splz()` (`sys/platform/vkernel64/platform/machintr.c:122-158`)
has **no guard of any kind**, and `exc_segfault` is the only caller that
invokes it at the *interrupted* context's arbitrary crit/nesting state
(`go_user`'s loop, `cpu_idle`, and `crit_exit` all dispatch from base
level).

Consequence: when a fault is taken while the interrupted vkernel-kernel
context (a) holds a critical section with a deferred IPI pending
(`ipisig` deferred it via `need_ipiq()`, exception.c:92), or (b) is nested
inside another interrupt's signal (`ipisig`/`kqueuesig`/`timersig` all run
with `gd_intr_nesting_level` raised and empty `sa_mask`), the tail `splz()`
executes IPI callbacks (`lwkt_process_ipiq`), soft/hard interrupt threads
(`sched_ithd_soft` / `sched_ithd_hard_virtual`), `vktimer_intr`, and
`kqueue_intr` **inside the critical section / inside the outer interrupt
handler** — precisely the interleaving DragonFly critical sections exist to
prevent. No TD_NEST_COUNT accounting is done anywhere on this path (pc64
bumps it around exactly these dispatches, ipl.s:280-286).

Reachable fault sources at kernel-mode crit/nesting (no vkernel *bug*
required for the class, though frequency is timing-dependent):
- guest-KVA demand VPTE installs on the `MAP_VPAGETABLE` window
  (`init.c:608-624`) — the vkernel's normal kernel page-fault mechanism —
  occurring inside any of the (ubiquitous) vkernel-kernel critical
  sections while a cross-CPU IPI (guest-userland TLB-shootdown churn) is
  deferred;
- single-step / breakpoint SIGTRAPs during vkernel DDB sessions
  (`kern_trap` T_TRCTRAP/T_BPTFLT, trap.c:716-725; the spurious-SIGTRAP
  comment at trap.c:732-742 acknowledges these arrive in kernel mode);
- any recoverable fault inside IPI/kqueue/timer callback execution.

Impact class: lost critical-section atomicity on guest-kernel data
(torn reads/lost updates on structures whose only lock is the critical
section) → guest-kernel memory corruption inside the VM (panic or worse).
The sibling fall-through defect of the same file family is DF-5338
(trap.c); this finding is the *dispatch-side* divergence in exception.c.

## Reproducer

`splzcrit.c` — faithful element-for-element model of the dispatch
structure on the DragonFly host: SA_NODEFER/empty-mask fault handler with
a *recoverable* fault (mmap-in-handler = trap_pfault/vm_fault SUCCESS) and
tail splz (vkernel mode) vs the doreti TD_CRITCOUNT gate (pc64 mode);
deferred-SIGUSR1 "IPI" callback that reads/writes crit-protected state
mid-section.

- **vkernel mode**: `INVARIANT_BROKEN`, `callback_critcount=1`,
  `callback_torn=1` — the IPI callback ran inside the critical section and
  observed torn state (3/3 runs).
- **pc64 mode**: `INVARIANT_OK`, `callback_critcount=0`, no torn state
  (3/3 runs).

`storm.c` + `build_image.sh` + `run_vk.sh` — in-vkernel fault storm on a
real booted VKERNEL64 (`-n 2`): 500 iterations × 4 guest-user fault classes
(intdiv/SIGFPE, ud2/SIGILL, RO-write/SIGSEGV, PROT_NONE/SIGSEGV) plus a
shared-memory mprotect-churn sibling driving cross-CPU TLB-shootdown IPIs
for the whole storm. Result: `STORM_DONE ... STORM_RC=0`, vkernel `ALIVE`
— normal guest-user fault storms never reach `exc_segfault` (they return
via vmspace_ctl → go_user → user_trap, trap.c:1407-1439), which is the
runtime kill of the "SA_NODEFER under fault storms" lead (with DF-5338
owning the one deterministic recursion instance).

## Fix

Guard the tail `splz()` exactly like doreti/lwkt_maybe_splz (fix.diff,
applied and rebuilt on the guest; patched vkernel passes the identical
storm; pc64-mode model run demonstrates the guarded semantics).

## Verdict summary

- status: **reproduced** (dispatch violation + torn-state corruption:
  deterministic 3/3 in the faithful model on the DragonFly host; code
  divergence proven line-by-line against the real kernel's own gate)
- demonstrated impact ceiling in the *live* vkernel: none observed
  (needs a kernel-side recoverable fault to coincide with crit+deferred
  IPI; DDB single-step makes it deterministic under debugging) — severity
  Low, bucket memcorrupt, confidence likely.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
