**Finding:** In `hammer2_flush_core()` (sys/vfs/hammer2/hammer2_flush.c:681-690), the "LOST CHILD3" path handles the case where `chain->parent` changed while the flusher temporarily released the chain lock (lines 661-665): ```c if (chain->parent != parent) { if (hammer2_debug & 0x0040) { ... } KKASSERT(parent != NULL); /* :686 */ hammer2_chain_unlock(parent); /* :687 */ retry = 1; goto done; } ``` Every other transition direction is handled or known-filed: the parent **non-NULL → NULL** direction (chain deleted out of its parent during the unlock window) is DF-0813/DF-2568 territory in the `hammer2_flush()` retry loop. This finding covers the opposite direction at this site: `parent == NULL` at entry (flush of a detached chain — deleted-but-open inode chains, or destroy races; the code explicitly documents `parent can be NULL, usually due to destroy races`, hammer2_flush.c:504-506) and the chain **gaining** a parent during the unlock window at :662-665 (a concurrent frontend rename adopting the detached chain via `hammer2_chain_rename()` → `hammer2_chain_create()` reconnect, which only needs the chain's exclusive lock — exactly what the flusher released). Then: * INVARIANTS/unconditional DragonFly `KKASSERT(parent != NULL)` fires → panic. * Non-INVARIANTS semantics: `hammer2_chain_unlock(NULL)` immediately dereferences `chain->lockcnt` (hammer2_chain.c:1132-1138) → NULL-page fault → panic either way. ## Why Phase V is skipped (verdict: skipped) The trigger requires winning a scheduler race whose window is the duration of three lock operations (hammer2_flush.c:661-665) between the flusher thread and a frontend rename xop adopting the *same, currently-detached* chain. There is no deterministic unprivileged schedule for this on the single-CPU-ish guest: the probability mass per attempt is microseconds against a rename that must target exactly the chain being flushed at that instant. A stress harness cannot bias the interleaving (the adoption itself requires the rename xop to win the chain lock in the window, and the vast majority of flushes of detached chains complete without any concurrent adopter existing). Code-level reachability is proven above; runtime reproduction is left as a low-value lottery. Consistent with the severity family of the known mirror findings (DF-0813/DF-2568, Medium). ## Suggested fix ```diff --- a/sys/vfs/hammer2/hammer2_flush.c +++ b/sys/vfs/hammer2/hammer2_flush.c @@ -683,6 +683,8 @@ if (hammer2_debug & 0x0040) { kprintf("LOST CHILD3 %p->%p (actual parent %p)\n", parent, chain, chain->parent); } - KKASSERT(parent != NULL); - hammer2_chain_unlock(parent); + if (parent) + hammer2_chain_unlock(parent); retry = 1; goto done; ``` (With `parent == NULL` the retry loop in `hammer2_flush()` re-seeks `info.parent` from `chain->parent` and re-references it, so retry=1 is the correct disposition in both directions.)