# DF-5728 — DDB `db_expression` unbounded '(' recursion overflows the 16 KB kernel stack

## What

`sys/ddb/db_expr.c` has no recursion-depth limit. Each `(` token drives six
nested frames (`db_term` → `db_expression` → `db_shift_expr` →
`db_add_expr` → `db_mult_expr` → `db_unary`, back into `db_term`).
Measured on the guest's `X86_64_GENERIC` kernel: **272 bytes of kernel
stack per nesting level** (db_term 32 + db_expression 16 + db_shift_expr 64
+ db_add_expr 64 + db_mult_expr 64 + db_unary 32).

DDB runs on the *interrupted context's* kernel stack (UPAGES=4 → 16 KB,
with a guard page). The ddb input line buffer is 120 bytes
(`sys/ddb/db_lex.c:42`), so a single console line of `(`s requests up to
120 levels = **~32.6 KB** of recursion — twice the stack. The 16 KB guard
page catches it: **double fault → `panic: double fault` → crashdump**.

Trigger line (typed at a `db>` prompt):

```
p (((((((((((((((((((((((((((((((((((((((((((1     <- 60 '(' = 16.3 KB: fatal
```

Empirical threshold varies with the interrupted context's remaining stack:
run 1 (sysctl/thread-context entry) survived 30 levels (8.2 KB) and died at
60; run 2 died at 40 — during the `db_error("Syntax error")` print at depth
(`db_printf` → `db_force_whitespace`), i.e. the *error path* at depth needs
headroom too. Two independent boots, 2/2 panics.

## Files

* `serial_vm.sh`     — boots the guest with a writable socket serial console
                       (same disk/kvm/pidfile as `dfbsd-qemu/vm.sh`)
* `drive_ddb.py`     — paced serial driver (8-byte chunks, UART FIFO limits)
* `run1-fullserial.log`  — full chardev log, run 1: sanity + 30 survives + **60 → DOUBLE FAULT**
* `run1-panic60.serial.raw` — driver-side capture of the same session
* `run2-sweep.serial.log`   — run 2: first bomb (40) → **DOUBLE FAULT** (rip db_force_whitespace+0xc)
* `run3-controls.serial.log` — controls on stock kernel: `p (1+(2*3))`=7,
                       balanced-20 nesting=1, 117-frame unary chain=-1,
                       clean `c` exit
* `fix.diff`         — DB_MAX_EXPR_DEPTH=16 cap (+ reset in db_flush_lex)
* `build.log`        — patched kernel build (nativekernel) + install
* `postfix-run.log`  — patched kernel: same bombs, no panic
* `panic.txt`        — the fatal double-fault frames from both runs

## Reproduce (baseline panic)

1. `./serial_vm.sh up`  (or `vm.sh reset with-src`, then this rig)
2. `dfbsd-qemu/vm.sh run_root 'sysctl debug.enter_debugger=ddb' &` — enters DDB
   on the serial console (thread-context entry, the clean-exit class)
3. `DF5728_SOCK=/tmp/opencode/df-5728-ser.sock ./drive_ddb.py "$(python3 -c 'print("p "+"("*60+"1")')" 8`
4. Expected: `DOUBLE FAULT - KERNEL STACK GUARD HIT!` / `panic: double fault`,
   crashdump, host-side guest unresponsive (serial shows panic frames).

## Success criterion

Kernel panic (`panic: double fault`, rsp page-aligned on the kstack guard
page) from a single DDB console line. With `fix.diff`: the same input yields
`Expression too deep` and the session continues — no panic.

## Threat model

Anyone with write access to the (serial) console of a system where
`debug.enter_debugger`-style DDB entry or break-to-debugger is available:
serial-terminal servers, IPMI/SoC serial, hypervisor console pipes. Same
actor class as the other DDB findings (DF-2238/2239/2241): console access is
treated as privileged-adjacent (PR:H) → Low severity, availability-only.
