# DF-5728 VERDICT — REPRODUCED (panic/dos, 2/2 boots) + fix validated

## Root cause (source-traced)

* `sys/ddb/db_expr.c:84-95` — `db_term()`'s `tLPAREN` branch calls
  `db_expression()` with **no depth limit**:
  ```c
  if (t == tLPAREN) {
      if (!db_expression(valuep)) {        /* recursion */
  ```
* `db_expression` → `db_shift_expr` → `db_add_expr` → `db_mult_expr` →
  `db_unary` (`db_expr.c:225-229,193-223,165-191,127-163`) → back into
  `db_term` — six frames per `(`.
* Input length is capped only by `db_line[120]` (`sys/ddb/db_lex.c:42`),
  i.e. up to ~118 consecutive `(` after a command word (`p `).
* DDB executes on the interrupted context's kernel stack: x86_64
  `UPAGES=4` → 16 KB with a guard page (`sys/cpu/x86_64/include/param.h`).
* Measured frames from the guest kernel (`objdump -d /boot/kernel/kernel`):
  db_term 32 B (2 push+sub8+ret), db_expression 16 B, db_shift_expr 64 B
  (5 push+sub16+ret), db_add_expr 64 B, db_mult_expr 64 B, db_unary 32 B
  → **272 B per nesting level**. 60 levels = 16.3 KB ≥ the whole stack;
  120 levels = 32.6 KB.

## Reproduction (live, socket-serial DDB rig)

Rig: `serial_vm.sh` (QEMU chardev socket serial, same disk as `vm.sh`) +
`drive_ddb.py` (paced 8-byte sends). DDB entered via root
`sysctl debug.enter_debugger=ddb` (`sys/ddb/db_sysctl.c:75`) — the
thread-context entry class exonerated for clean exit by DF-5722's controls.

* **Run 1** (`run1-fullserial.log`, `run1-panic60.serial.raw`):
  - `p (1+(2*3))` → `7` (sanity, evaluator healthy)
  - `p ` + `(`×30 + `1` → `Syntax error`, `db>` returns (survives: 8.2 KB)
  - `p ` + `(`×60 + `1` →
    ```
    DOUBLE FAULT - KERNEL STACK GUARD HIT!
    Fatal double fault
    rip = 0xffffffff802ca73c        <- db_unary+0xc (pure descent)
    rsp = 0xfffff801184cf000        <- page-aligned: kstack guard page
    panic: double fault
    ```
    → 570 MB crashdump, guest dead.
* **Run 2** (`run2-sweep.serial.log`, fresh boot): first bomb `(`×40 →
  descent survived (10.9 KB < free stack) but the unwind's
  `db_error("Syntax error")` → `db_printf` → `db_force_whitespace` at depth
  crossed the guard:
  ```
  rip = 0xffffffff802cba0c          <- db_force_whitespace+0xc
  rsp = 0xfffff80117e77000          <- page-aligned guard page
  panic: double fault
  ```
  → second crashdump, 2/2 boots.
* **Run 3** (`run3-controls.serial.log`, stock kernel, no bomb):
  balanced nesting depth 20 evaluates (`1`), a 117-frame unary chain
  (`-----…-1`) evaluates (`ffffffffffffffff` = -1, ~2.9 KB — unary chains
  alone are NOT fatal), and `c` exits DDB cleanly. (The guest then
  soft-wedged post-exit — the DF-5722 DDB-exit fragility family, unrelated
  to this finding; evidence was already captured and the box was reset.)

Threshold variance (30 ok/60 dead in run 1; 40 dead in run 2) is explained
by the interrupted thread's remaining stack at DDB entry — both bounds are
far below the 118-level maximum the lexer accepts, so the bug is
deterministically triggerable at `p ` + `(`×118.

Note: crashdumps from runs 1/2 were written to the pre-revert disk state and
discarded by the `with-src` snapshot reverts; the serial logs above carry
the complete fatal frames (rip/rsp/trace), which is the evidence of record.

## Impact classification

`panic` / DoS. The guard page converts the overflow into a clean double
fault + panic + dump (no silent adjacent-kernel-memory corruption was
observed; x86_64 DFly kstacks are guard-paged on this kernel). Reachable
from the DDB console only — same privileged-adjacent actor as DF-2238/
2239/2241 → **Low severity**, availability-only. Not an escalation path:
the console actor can already `call`/`write`/`reset` by DDB design.

## Fix validation (mandatory for reproduced memory-corruption-class bugs)

`fix.diff` caps nesting at `DB_MAX_EXPR_DEPTH 16` (≈4.4 KB worst-case
recursion) in `db_expression()`, and resets `db_expr_depth` in
`db_flush_lex()` (`sys/ddb/db_lex.c`) because `db_error()` longjmps
(`sys/ddb/db_command.c:486`) past the decrement on every abort path —
`db_flush_lex()` is called by `db_error()` itself, so the counter cannot go
stale. The `db_unary` '-'/'*' self-recursion is NOT capped: bounded by the
120-char line to ~119 × 24-32 B ≈ 3.8 KB worst case (control run 3 confirms
survival at 117 frames).

Built with `make nativekernel KERNCONF=X86_64_GENERIC` in the guest
(`build.log`), installed, rebooted, and re-driven with the identical rig:

* baseline (stock kernel): `p ` + `(`×40/×60 → double fault (runs 1-2)
* patched kernel (`postfix-run.log`): `p ` + `(`×60 and `(`×118 →
  `Expression too deep`, `db>` returns, **no panic**; sane controls
  (`p (1+(2*3))` → 7; balanced-8 nesting evaluates; unary chain = -1).
  Usability tradeoff documented: balanced nesting deeper than 16 now errors
  instead of evaluating (legal-but-pathological input).

fix_status: fixed (baseline reproduced, patched not reproduced).

## Not-a-bug notes from the same trace

* `db_term`'s other recursions (`*`/`-` unary, `db_expr.c:106-121`) are
  single-frame-per-char chains — bounded ≤ ~3.8 KB by the line length.
* Divide-by-zero is guarded (`db_expr.c:147-150`); signed `/-1` #DE and the
  32-bit shift truncation are DF-2241/DF-2242 (not re-reported).
* Faults on bad `*addr` derefs in DDB land in `trap_fatal`→`kdb_trap`
  (`sys/platform/pc64/x86_64/trap.c:420,1096`) — the known DF-2241 wedge
  family, not re-reported.

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