β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5587

Any fault in a vkernel cothread dies silently through unbounded exc_segfault recursion: SA_NODEFER + empty sa_mask re-entry on a %gs==0 thread hits kern_trap's mycpu load and recurses ~308k times until the host kills the vkernel β€” no panic, no dump, no diagnostic

Summary

Fault handlers are installed process-wide with SA_NODEFER+empty sa_mask while cothreads run with %gs=0 and fault signals unmasked: a cothread fault enters exc_segfault whose first kern_trap statement (mycpu load) immediately re-faults and re-delivers without bound - measured 307,833 re-entries 3/3 before silent host kill, no core path through the guest panic machinery (vs the real kernel's #DF -> dblfault_handler -> panic). Crash-diagnostics loss, latent (needs a cothread-side driver bug). exc_nesting guard + honest-death fix validated in the model and built into the guest VKERNEL64.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5587 Β· 12 files
FileTypeDescriptionSize
coresig.c β€” 3.8 KB view raw
corefixed.c β€” 2.1 KB view raw
build.sh β€” 107 B view raw
run.sh β€” 103 B view raw
build_and_run.log β€” 736 B view raw
fix_semantics.log β€” 192 B view raw
fix.diff β€” 1.1 KB view raw
VERDICT.md β€” 3.3 KB ↓ raw
README.md β€” 1.1 KB ↓ raw
verdict.json β€” 3.7 KB view raw
manifest.json β€” 1.0 KB view raw
env.txt β€” 526 B view raw

DF-5587 β€” PoC pack

Build (on the DragonFly guest)

sh build.sh                 # cc -static -O2 -o coresig coresig.c
cc -static -O2 -o corefixed corefixed.c   # fix-semantics demo

Run

./coresig                   # baseline: silent unbounded recursion death
./corefixed                 # with fix.diff guard semantics

Expected (baseline, 3/3 deterministic):

child wait status: exited=0 exit=-1 signaled=1 termsig=4 core=128
RECURSION_DEPTH=307833
SILENT_DEATH_CONFIRMED: ...
RC=0

Expected (fix semantics):

vkernel: recursive fault in exc_segfault
child: signaled=1 termsig=11 core=128 handler_entries=2
FIX_SEMANTICS_OK: honest SIGSEGV death after 2 entries with diagnostic
RC=0

Files

  • coresig.c β€” baseline reproducer (exact init_exceptions install flags)
  • corefixed.c β€” fix-semantics demo (exc_nesting guard)
  • build.sh / run.sh
  • build_and_run.log β€” full build + 3Γ— runs (untrimmed)
  • fix_semantics.log β€” corefixed run
  • fix.diff β€” exc_segfault re-entrancy guard + honest death
  • verdict.json, manifest.json, VERDICT.md
VERDICT.md
↓ download raw

DF-5587 β€” cothread faults die silently through unbounded exc_segfault recursion (SA_NODEFER + empty sa_mask + %gs==0)

What

init_exceptions() (sys/platform/vkernel64/x86_64/exception.c:222-229) installs exc_segfault for SIGBUS/SIGSEGV/SIGTRAP/SIGFPE process-wide with SA_SIGINFO | SA_NODEFER and an empty sa_mask. The vkernel's cothreads run with:

So any fault in any cothread (disk/net/console I/O threads executing guest-issued requests) is delivered to exc_segfault, whose first real work is kern_trap() β€” whose first statement is struct globaldata *gd = mycpu; (trap.c:585) β€” a %gs-relative load that faults when %gs==0. With SA_NODEFER and an empty mask the signal is re-delivered immediately on the same stack: unbounded recursion, no nesting guard, no SA_ONSTACK, until the host cannot push the signal frame and kills the whole vkernel process β€” no panic, no dump path, no diagnostic (a real kernel turns this class into #DF β†’ dblfault_handler β†’ panic with register state, trap.c:969-997).

Latent trigger (requires a cothread-side fault, i.e. a vkernel driver bug or bad DMA pointer): severity Info / hardening. The sibling deterministic recursion for lwkt threads with a valid %gs is DF-5338 (trap.c fall-through); this one never even reaches trap.c's switch.

Reproducer

coresig.c β€” installs the exact init_exceptions signal configuration (SA_SIGINFO|SA_NODEFER, empty mask) and a handler whose first dereference uses a NULL "%gs base" (the cothread condition), then takes one fault.

Result on the DragonFly guest (3/3 deterministic):

child wait status: exited=0 exit=-1 signaled=1 termsig=4 core=128
RECURSION_DEPTH=307833
SILENT_DEATH_CONFIRMED: handler re-entered 307833 times before the host
killed the process β€” no panic, no dump path, no diagnostic

corefixed.c β€” fix-semantics demo (the exc_nesting guard from fix.diff): the recursion is caught at depth 2, one diagnostic line is printed, the disposition is reset to SIG_DFL, and the child dies of the true faulting signal (SIGSEGV, core dumped) after exactly 2 handler entries.

Fix

fix.diff β€” re-entrancy guard + honest death (diagnostic, SIG_DFL, re-raise) in exc_segfault. Applied together with DF-5586's guard on the guest's /usr/src; VKERNEL64 rebuilt cleanly (build+link OK) and the in-vkernel fault storm still passes (STORM_RC=0, ALIVE).

Verdict summary

  • status: reproduced (mechanism: 307,833 silent re-entries β†’ host kill, 3/3; fix semantics: 2 entries + diagnostic + true SIGSEGV)
  • impact: dos-class within the VM (whole-vkernel death, silent), but latent (needs a cothread fault to fire); severity Info, confidence certain (mechanism), trigger speculative.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched reproduced

fix.diff guard semantics proven by corefixed (2 entries + diagnostic + true SIGSEGV vs 307833 silent); applied (combined with DF-5586's guard) to guest /usr/src, VKERNEL64 rebuilt cleanly, in-vkernel fault storm passes unchanged (STORM_RC=0, ALIVE).

findings/poc/DF-5587/fix_semantics.log; findings/poc/DF-5586/run_vk.log (patched-vkernel storm); fix.diff
↓ fix.diffper-fix-DF-5587

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5587/build_and_run.log (3x: RECURSION_DEPTH=307833, SILENT_DEATH_CONFIRMED, termsig=4 core=128); fix_semantics.log (corefixed: 'vkernel: recursive fault in exc_segfault', termsig=11, handler_entries=2, FIX_SEMANTICS_OK); code: exception.c:222-229 + cothread.c:143-148 (tls_set_gs(0)) + machintr.c:205-211 (fault signals unmasked) + trap.c:585 (mycpu first)

PoC changes

Authored from scratch (no seed): exact init_exceptions install flags + NULL 'TLS base' first dereference (the %gs==0 condition); shared MAP_ANON page for the cross-fork recursion counter; corefixed.c needed the shared-page counter too (COW hid the depth in the parent).

Verified recommended fix

exc_segfault re-entrancy guard: exc_nesting counter; on re-entry write(2) a diagnostic, reset the four fault dispositions to SIG_DFL and raise(signo) for an honest, core-dumping death

Verdict

exc_segfault is installed process-wide with SA_SIGINFO|SA_NODEFER and an empty sa_mask (exception.c:222-229); the vkernel's cothreads run with %gs=0 (tls_set_gs(0), cothread.c:143-148) and only the 8 interrupt signals masked (cpu_mask_all_signals, machintr.c:205-211) β€” so any cothread fault enters exc_segfault whose kern_trap first statement (mycpu, trap.c:585) immediately re-faults, and SA_NODEFER+empty-mask re-delivers on the same stack without bound: measured 307,833 handler re-entries (3/3 deterministic) before the host killed the process (SIGILL/core on delivery failure) β€” the whole vkernel dies with no panic, no dump path, no diagnostic, versus the real kernel's #DF->dblfault_handler->panic. Latent trigger (requires a cothread-side fault, e.g. a vkernel driver bug or bad DMA pointer in a disk/net/console cothread executing guest-issued I/O), so severity Info/hardening; the deterministic lwkt-thread recursion instance of the same install is DF-5338 (trap.c fall-through root). Fix semantics demonstrated: the exc_nesting guard from fix.diff converts the death to a one-line diagnostic + true SIGSEGV with core after exactly 2 handler entries; guard applied (combined with DF-5586's) into the guest /usr/src, VKERNEL64 rebuilt cleanly, in-vkernel fault storm still passes.