# DF-5729 — DDB `db_dump_td_tokens` can dereference an unpublished lwkt_tokref (snapshot race)

## Status: SPECULATIVE — source-proven ordering flaw, nanosecond window, not driven live

## Root cause

`sys/kern/lwkt_token.c:658-663` (`lwkt_gettoken`; identical pattern in
`lwkt_gettoken_shared` :729-733 and `lwkt_trytoken` :795-799):

```c
ref = td->td_toks_stop;
KKASSERT(ref < &td->td_toks_end);
++td->td_toks_stop;                                /* publish FIRST */
cpu_ccfence();
_lwkt_tokref_init(ref, tok, td, TOK_EXCLUSIVE|TOK_EXCLREQ);   /* init AFTER */
```

The thread's token-ref list is published (`++td_toks_stop`) *before* the
ref's `tr_tok`/`tr_owner`/`tr_count` are initialized. Any snapshot reader
that walks `[td_toks_base, td_toks_stop)` between the two stores observes a
slot containing stale bytes: either a leftover (valid) pointer from a
previously released token, or zeros for a never-used slot.

`sys/ddb/db_ps.c:184-202` (`db_dump_td_tokens`, reached from the `ps`
command via db_ps.c:149/172 for every thread on the runq/allq) is exactly
such a snapshot reader, and it dereferences without a guard:

```c
for (ref = &td->td_toks_base; ref < td->td_toks_stop; ++ref) {
        tok = ref->tr_tok;                 /* db_ps.c:194 — no NULL check */
        db_printf(" %p[tok=%p", ref, ref->tr_tok);
        if (tok->t_ref && td == tok->t_ref->tr_owner)   /* db_ps.c:197 — deref */
```

DDB freezes all other CPUs at entry (`CPUx stopping CPUs ... stopped` in
every session log). If a CPU is halted inside the 3-instruction window of
`lwkt_gettoken` — or, equivalently, the sampled thread's `td_toks_stop`
points at an uninitialized slot — the `ps` walk reads `tr_tok == NULL`
(fresh array slot) and dereferences `tok->t_ref` at a small offset:
NULL-deref page fault **inside DDB**, which routes through
`trap_fatal` → `kdb_trap` (`sys/platform/pc64/x86_64/trap.c:420,1096`) —
the known DDB-wedge family (DF-2241 class): nested DDB re-entry / wedge of
the session. A stale non-NULL `tr_tok` merely prints a wrong token
(cosmetic).

## Why untested (honest)

The window is `++stop; fence; 3 stores` — a few ns. DDB's stop-IPI must
land with a CPU exactly inside it. With tokens acquired continuously by
every subsystem, the per-entry probability is on the order of 1e-6 or less
per CPU; driving it on demand is not practical on this rig. The positive
control (healthy `ps` walk in DDB on the same kernel, DF-5728 postfix
session) behaves normally.

## Fix

`fix.diff`: (a) reorder all three publishers to init-then-publish (the
memory-ordering-safe direction: the ref is fully initialized before any
snapshot reader can see it — external visibility timeline unchanged);
(b) belt-and-braces NULL-guard in `db_dump_td_tokens` for any other
transient state. The (a) hunk should get upstream review for the
`cpu_ccfence()` placement relative to `_lwkt_tokref_init`'s stores.

## Severity

Low / speculative. Same console-actor threat model as the other DDB
findings; worst case is a DDB-context page fault (wedge/panic), best case a
misprinted token.

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