# DF-5725 — VERDICT

**reproduced** (deterministic, twice) — **impact: panic** (plus a real, demonstrated
kernel-text corruption primitive: first byte of an instruction at a valid,
unrelated address permanently replaced with 0xCC while DDB believes no
breakpoints are inserted; the run then ends in `panic: trap: critical section
count mismatch!`, crash dump, reboot).

## Environment

- DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 x86_64,
  stock `X86_64_GENERIC` kernel (guest `/sys/ddb/db_break.c` MD5
  `00d1f40d09dd788691f6224b391d3e26` = audited tree).
- QEMU/KVM, 1 vCPU (UP), 4 GB, socket-backed serial console driven from the
  host (`serial_vm1.sh` + `drive_ddb.py`, paced 8-byte chunks).
- DDB entry: root-gated `sysctl debug.enter_debugger=ddb` (unprivileged write
  → EPERM, verified by DF-5721's probes); `debug.debugger_on_panic=1`.

## What was run (exact)

Run 1 and Run 2 (identical, `run1-orphan-panic.serial.log`,
`run2-orphan-panic.serial.log`):

```
db> x/bx db_invltlb_cmd                 # read pristine byte: 0x0f
db> b 0xffffffff85000000                # BAD bp set FIRST  (tail of list)
db> b db_invltlb_cmd                    # GOOD bp set SECOND (head of list)
db> show breaks                         # both listed
db> c                                   # resume: insert pass head-first
Fatal trap 12: page fault while in kernel mode
  fault virtual address = 0xffffffff85000000
  current thread = pri 6 (CRIT)
db> x/bx db_invltlb_cmd
db_invltlb_cmd: cc                      # ORPHANED INT3 (flag says NOT inserted)
panic: trap: critical section count mismatch! -1892583040/-1
  (dump, reboot)
```

Control (`control-run.serial.log`): GOOD bp alone → `c` → machine runs; re-enter
DDB → byte already restored to `0x0f` at entry (db_stop_at_pc →
db_clear_breakpoints with flag TRUE); delete; `c`; machine alive.  The defect is
specifically the fault-aborted insert pass.

## Why (line-accurate)

1. `db_set_breakpoint()` (`sys/ddb/db_break.c:88-111`) never initializes
   `bkpt_inst` and never validates the address.
2. `db_set_breakpoints()` (`db_break.c:161-180`) walks the list head-first:
   for each bp it **reads** the original byte (`db_break.c:171`,
   `db_get_value`) and **writes** `BKPT_SET` (`db_break.c:174-176`).  On
   x86_64 `db_read_bytes` (`sys/platform/pc64/x86_64/db_interface.c:241-251`)
   accesses the raw address; an unmapped address faults.  The fault is not
   fielded by `db_nofault` on this path — it routes to
   `trap_fatal()` → nested DDB entry (observed; the trap dump is in both run
   logs, `current thread = pri 6 (CRIT)` — the resume path runs in a critical
   section).
3. The abort leaves earlier-listed breakpoints patched with INT3 while
   `db_breakpoints_inserted` remains FALSE (`db_break.c:178` unreached) —
   proven by `db_invltlb_cmd: cc` at the nested prompt.
4. From that point every recovery path is broken:
   - `db_clear_breakpoints()` (`db_break.c:187-196`) restores only
     `if (db_breakpoints_inserted)` — the desynced flag makes it a no-op, so
     nothing ever restores the byte;
   - a later insert pass re-reads the patched byte as the original
     (`db_break.c:171`) — `bkpt_inst` poisoned with 0xCC — and the eventual
     clear *writes back* 0xCC: permanent first-byte corruption of the
     instruction at that address;
   - empirically the nested CRIT-context trap state is a hair-trigger: the
     next trap on that thread trips `panic: trap: critical section count
     mismatch!` (observed in both runs; panic trace runs through
     `Debugger() ← sysctl_debug_enter_debugger ← userland_sysctl` — the
     original entry frame — confirming the abort happened mid-resume).
5. Because a stop requires `--bkpt->count == 0` exactly (`db_run.c:100`), the
   corrupted state also silently swallows the skip-count bookkeeping.

With `debug.debugger_on_panic=0` the fatal trap would panic-reboot directly at
step 2; with the default (1) the machine survives only until the first nested
trap, which panics anyway (both runs).

## Threat model

DDB requires the root-gated console (serial break, panic, or
`debug.enter_debugger`).  The corrupted byte and the panic are caused by an
operator *typo* (`b` at an unmapped address) — the corruption lands at an
address the operator did NOT type and persists after deleting every
breakpoint.  Severity: Low (privileged surface), but it is genuine
kernel-text corruption (wrong-code execution primitive if the target address
is chosen) caused by a debugger state-machine defect, not by the by-design
debugger memory-write capability.

## Fix validation

`fix.diff`: initialize+save `bkpt_inst` at `db_set_breakpoint()` time (before
linking; a bad address faults at the prompt — the pre-existing, recoverable
behavior for bad DDB reads — and nothing is added to the list), and use the
saved byte in `db_set_breakpoints()` (never re-read patched memory).

Validated on a single combined-fix kernel (this fix + DF-5726 + DF-5727,
`fix_build.log` / `fix_run.log`): `b 0xffffffff85000000` is rejected at set
time, `show breaks` lists only the good breakpoint, `c` resumes with **no
fatal trap**, and `db_invltlb_cmd` reads `0x0f` (never `0xcc`) at every
observation point; no panic; machine stays up.  Residual (documented): a page
that is valid at set time but unmapped later (module unload) can still abort
the insert pass mid-resume — the poisoning leg is gone by construction, the
orphan leg would need per-bp fault isolation in the MD layer.

## Rig notes

- The SMP rig (`serial_vm.sh`, 6 vCPU) wedged unrecoverably after a DDB
  resume (same failure DF-5721 recorded in `smp-E1-wedge.serial.log`); all
  clean sessions ran on the UP rig (`serial_vm1.sh`).
- Crash dumps written by the two baseline panics were discarded with
  `vm.sh reset with-src` before the fix build.

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