# DF-5720 — VERDICT

**Status: reproduced** (deterministic, first clean attempt on the stock
`X86_64_GENERIC` snapshot guest, `DragonFly dfbsd 6.5-DEVELOPMENT #0 Thu Jul 2
06:02:54 UTC 2026 x86_64`). **Impact: none** (Info-class debugger parser
correctness bug — no memory unsafety, no privilege boundary crossed; DDB
requires console/root access). **Confidence: certain** — the wrong-modifier
behavior is directly visible in the serial transcript (`run.log`) with
in-line letter-modifier controls, and the root cause is line-verified in the
lexer.

## Root cause (line-accurate)

`sys/ddb/db_command.c:361-370`, standard command syntax:

```c
361:  t = db_read_token();
362:  if (t == tSLASH) {
363:      t = db_read_token();
364:      if (t != tIDENT && t != tNUMBER) {   /* tNUMBER is accepted … */
367:          return;
368:      }
369:      db_strcpy(modif, db_tok_string);     /* … but the copied buffer is
                                                 only filled for tIDENT */
```

The lexer never writes `db_tok_string` for numbers — `sys/ddb/db_lex.c:170-216`
stores digits in `db_tok_number` only; `db_tok_string` retains the **previous
identifier token**, which on a plain command line is the command's own name
(lexed at `db_command.c:295`). `modif` therefore becomes, deterministically:

- `x/4`      → `"x"`       (coincidentally a valid hex-format letter — the `4` is silently ignored)
- `examine/4` → `"examine"` (per-char format walk in `db_examine`, `sys/ddb/db_examine.c:78-193`)
- `print/4`  → `"print"`   (`db_print_cmd` switches on `modif[0]`; `'p'` has no case → prints only `\n`, `sys/ddb/db_examine.c:204-248`)
- `write/4`  → `"write"`   (`'w'` hits `default:` → `db_error("Unknown size\n")`, `sys/ddb/db_write_cmd.c:56-67`)

## Why Info (impact bounded)

- No memory unsafety: `modif[TOK_STRING_SIZE=120]` (`db_command.c:289`) is
  filled from `db_tok_string[120]`, which the lexer bounds at ≤119 chars +
  NUL and NUL-terminates on every `tIDENT` success path
  (`sys/ddb/db_lex.c:225-256`; the over-long path at `:245-249` longjmps out
  via `db_error` before any consumer runs).
- Every consumer switches on single characters with bounded defaults
  (`db_examine.c:58-59,208-209`, `db_write_cmd.c:56-67`, `db_run.c:310`,
  `db_watch.c:281-297`). The worst observed effect is a **format change**
  (a read command silently disassembling via the `'i'` letter) and a
  **refused write** (`write/4` → "Unknown size", fail-safe: no size
  expansion is possible — the stale string always begins with the command's
  own first letter, and no write-relevant command name begins with
  `b`/`h`).
- DDB itself is console/root-privileged (unprivileged writes to
  `debug.enter_debugger` are EPERM — DF-2246 verified that gate).

## Live evidence (run.log, from /tmp/opencode/df-5720-drain.log)

```
db> x/x db_active            <- control
db_active:      1
db> x/4 db_active            <- numeric modifier silently ignored
db_active:      1
db> examine/4 db_active      <- modif="examine": hex + 'a' pointer + 'i' disasm
db_active:      1               0x100000001addb %al,(%rax)
db_global_jmpbuf_valid+0x6:
db> print 0x41               <- control
      41
db> print/4 0x41             <- modif="print" -> 'p': no case -> EMPTY LINE
db> print/x 0x41             <- control (letter modifier works)
      41
db> write/4 db_active 1      <- modif="write" -> 'w' -> "Unknown size"
Unknown size
db> write/l db_active 1      <- control
db_active               1 = 1
```

All three bug markers present, all four controls normal. `db_active` value
verified unchanged (1) across the session; the `write/l` control wrote the
same value it read.

## Deliberate non-goals / delimitation

- The **db_fncall raw-jump → nested-fault → db_active leak** cascade is
  DF-5561 (filed against `sys/platform/pc64/x86_64/db_interface.c:152-195`;
  its pack cites `db_command.c:515` as the trigger). Not re-verified here.
- Verified **in-bounds**: the `MAXARGS=11` fence (`db_command.c:537`) checks
  before the write at `:542`, so the last write is `args[10]`; `nargs==11`
  hits the `default:` arm (`:602-605`) and never calls. `retval` is assigned
  on every switch arm.
- Verified **no breakpoint-driven recursive session**: `db_stop_at_pc`
  clears software breakpoints and watchpoints on every debugger entry
  (`sys/ddb/db_run.c:78-80`), so a `break db_printf`-style trap at the `db>`
  prompt cannot re-enter `kdb_trap` recursively; in-command faults are caught
  by the `db_nofault` / `db_global_jmpbuf` nets (`db_interface.c:141-153`).

## Cross-file lead observed during verification (NOT this finding)

After `c`, the guest does not resume userland: no serial getty echo, no sshd
banner (QEMU slirp fakes the host-side handshake, so that is not evidence of
a live stack), vCPU threads at ~5%. The controlled cycle
`ab_cycle.sh 5 none` (enter ddb via root sysctl, send **zero** commands,
`c`) reproduces the identical starvation — proving it is a property of the
`debug.enter_debugger` / `kdb_trap` / `stop_cpus`/`restart_cpus` resume path
(`sys/ddb/db_sysctl.c:56-76`, `sys/platform/pc64/x86_64/db_interface.c:160-200`),
not of any `db_command.c` behavior. DF-5561's baseline transcript shows
userland running immediately after `c` only because its leaked `db_active`
rerouted the first user-mode trap into a fresh `kdb_trap`; its patched run
(like this one) shows a silent " restarted" tail. Recommended follow-up for
the db_interface.c / mp owners; evidence in `exp.A-short-none.drain.log`.

## Fix

`fix.diff` — populate `modif` from `db_tok_number` for numeric modifier
tokens (alternative: strictly reject `tNUMBER` as a modifier; no consumer
defines a numeric meaning). Not kernel-rebuild-validated: Info-class
correctness fix, behavior-verified at the parser level (the patched branch
is unreachable-before/ reachable-after reasoning is trivial and no panic or
corruption is involved).

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