# DF-5607 — VERDICT

**Status: reproduced (3/3 deterministic on a real VKERNEL64 boot).**
Impact: dos (silent whole-vkernel death — strictly worse than a panic:
no message, no dump, no trace). Confidence: certain.

## What was proven, and how

The finding is the cothread.c-owned enabling condition of the DF-5587
dynamic. DF-5587 demonstrated the recursion with a standalone host
program (`coresig.c`) modeling the vkernel's signal installs. This pack
upgrades the proof to a **real vkernel**: a stock VKERNEL64
`kernel.debug` built from the guest's `/usr/src`, booted
`-m 512m -r /vkroot.img -n 2`, with an LD_PRELOAD shim (`cotdfault.so`,
no `sys/` modification) that:

1. interposes `sigblock()` for diagnostics — every
   `cpu_mask_all_signals()` (machintr.c:205-211, called at
   cothread.c:109 and cothread.c:143) passes through it;
2. interposes `nanosleep()` — the vktimer cothread reaches it via
   `cothread_sleep()` (cothread.c:186-189, called from
   `vktimer_thread`, systimer.c:244) *after*
   `tls_set_gs(0)` (cothread.c:149) executed; the shim confirms the
   caller is a cothread by reading its %gs base with the
   `get_tls_area(2)` syscall (nr 473) — NULL **only** for cothreads;
3. injects `pthread_kill(self, SIGSEGV)` there — indistinguishable
   from any real cothread-side fault (in-tree example: the deliberate
   `*(volatile int *)0 = 1` NULL-ring write in `vke_rx_thread`,
   if_vke.c:672-675).

## Observations (stock kernel)

* Console at the moment of injection (run.baseline.log):
  `COTDFAULT: ... injecting SIGSEGV ...` — then **silence**. The vkernel
  child dies without panic, without `Fatal trap`, without a core note on
  the console, without the ddb `Debugger()` path. The init.c fork
  wrapper (init.c:203-222) swallows the child's status and exits 0, so
  even the parent's exit status is unremarkable.
* The child's core (`baseline_core.txt`): `Program terminated with
  signal 4, Illegal instruction` — the host killed it when the
  recursive signal-frame pushes exhausted the cothread's pthread stack.
  Deepest frame: `#0 _get_mycpu () at ./machine/thread.h:77`
  (`movq %gs:globaldata` — the mycpu load that re-faults with %gs=0),
  under `#1 kern_trap (trap.c:585)` under
  `#2 exc_segfault (exception.c:269)` — the recursion ladder itself.
  The two vkernel cpu threads are parked in `nanosleep()` (gdb
  `info threads`), i.e. no cpu thread ever ran the panic path.
* 3/3 deterministic (three separate boots).

## Root cause chain (path:line)

* `sys/platform/vkernel64/platform/cothread.c:149` — `tls_set_gs(0, ...)`
  makes every mycpu dereference fatal in a cothread (by design).
* `sys/platform/vkernel64/platform/cothread.c:109` and `:143` — the
  cothread's signal discipline is `cpu_mask_all_signals()`, which
  blocks only SIGALRM|SIGIO|SIGQUIT|SIGUSR1|SIGTERM|SIGWINCH|SIGUSR2|SIGURG
  (`sys/platform/vkernel64/platform/machintr.c:205-211`) — **not**
  SIGSEGV/SIGBUS/SIGTRAP/SIGFPE.
* `sys/platform/vkernel64/x86_64/exception.c:227-233` — those fault
  signals are handled process-wide by `exc_segfault`, installed
  `SA_SIGINFO|SA_NODEFER` with an empty `sa_mask`, so a fault taken
  *inside* the handler is re-delivered on the same stack, unbounded.
* `sys/platform/vkernel64/x86_64/trap.c:585` — `kern_trap()`'s first
  statement is the mycpu load that re-faults.

Masking the fault signals in the cothread instead is NOT a correct fix:
a blocked synchronous signal makes the host retry the faulting
instruction forever (silent livelock). The actionable fix is the
re-entrancy guard in `exc_segfault` (cross-file, shared with DF-5587);
cothread.c's contribution is the invariant documentation and the fact
that its mask discipline is what leaves the thread exposed.

## Fix validation (performed in this run)

`fix.diff`: `__thread int exc_nesting` guard; on re-entry print
`vkernel: recursive fault in exc_segfault`, restore SIG_DFL, unblock,
`raise(signo)`. Applied to the guest's `/usr/src` (validation copy
only), VKERNEL64 rebuilt (log RC=0), **same injection re-run**
(run.fixed.log):

* console: `vkernel: recursive fault in exc_segfault` then death —
  a diagnosable failure for the first time;
* core (`fixed_core.txt`): `Program terminated with signal 11,
  Segmentation fault` with the `raise → _thr_send_sig → lwp_kill` path
  — the honest SIGSEGV, exactly 2 handler entries instead of ~308k.

Baseline vs patched: silent-SIGILL-recursion vs diagnostic-SIGSEGV.
Bad behavior gone. (This also upgrades DF-5587's fix from
simulated-only to real-vkernel-validated.)

## Why not uid0

The vkernel is a host userland process; this bug class kills the
vkernel instance (DoS of the virtual kernel) and provides no memory
corruption primitive — the recursion is deterministic death, not
control over saved state.

## Environment

`env.txt`; guest DragonFly 6.5-DEVELOPMENT X86_64_GENERIC #0 Thu Jul 2
2026; VKERNEL64 built in-guest with the DF-5474 defsym workaround;
vkernel boots SMP (`-n 2`) with `/vkroot.img` (rig lineage
DF-5337/DF-5579).

## 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.
