# DF-5665 — VERDICT

**Status: untested** (Info-severity hardening finding; PoC waived by
contract — and the code is not compiled into the audit guest's kernel).
**Impact ceiling: kernel panic (local, speculative). No privilege
escalation, no info leak, no memory-corruption write primitive.**

## Scope of the pass-2 audit

Target: `sys/cpu/x86_64/misc/ktr.c` (95 LOC). The prompt's checklist items
(ring index arithmetic, buffer allocation, dump copyouts, version counters)
live in `sys/kern/kern_ktr.c` / `sys/sys/ktr.h`; they were pulled in and
analyzed as callers/consumers of this file. Every class was hunted and each
candidate was proven or killed with path:line evidence below.

## The one finding (mechanism certain, trigger speculative)

`FRAMEUP()` (sys/cpu/x86_64/misc/ktr.c:60-70) validates a candidate frame
pointer only for ~16 KiB proximity to the previous frame pointer:

- no monotonic-up requirement (kernel frames grow upward; a genuine chain
  never goes down),
- no 8-byte alignment requirement,
- no containment within the current kernel stack.

`cpu_ktr_caller()` then dereferences the accepted pointer
(`frameptr[0]` at ktr.c:66; `frameptr[1]` at ktr.c:92,94). A **stale
kernel** `%rbp` value left in a saved-rbp slot by a frame-pointer-less
assembly stub (trampolines, exception/IPI entry) that lands inside the
acceptance window but on unmapped KVA (e.g. an adjacent freed per-thread
kstack) makes the next read fault → panic. Attackers control neither the
stale value nor (absent a separate kernel bug) the call path shape; the
ceiling is a reboot. Filed as Info hardening, confidence speculative
(trigger), mechanism verified by line-level reading.

## Attacker-route analysis (why this is not exploitable)

1. **User `%rbp` injection is structurally impossible.** When an
   interrupt/syscall is taken from user mode, the user's `%rbp` can end up
   in a saved slot; the walk might pick it up as a candidate. FRAMEUP
   compares it against the current baseline, which always starts from
   `cpu_ktr_caller`'s own genuine kernel frame (ktr.c:86-88:
   `frame = rbp; frameptr = frame->f_frame`). A user candidate
   (< 0x0000_8000_0000_0000) XOR a kernel kstack address
   (>= 0xFFFF_8000_0000_0000) always has bits ≥ 14 set →
   `& ~16383ULL` ≠ 0 → rejected. The baseline only ever updates to
   accepted (kernel-canonical, close) values, so no sequence of steps can
   cross into user space. Worst case on a broken chain: `ktr_caller1 ==
   ktr_caller2 ==` the traced function's return address (fallback stays on
   the current frame) — no fault, garbage-free.
2. **Writes are confined by construction.** The only stores are
   `ktr->ktr_caller1/2` (ktr.c:92,94) into the ring entry returned by
   `ktr_begin_write_entry()` (sys/kern/kern_ktr.c:496-526), which is
   bounds-derived (`kcpu->ktr_buf + (kcpu->ktr_idx & ktr_entries_mask)`,
   kern_ktr.c:511) and NULL-guarded against `panicstr` and early boot
   (kern_ktr.c:505-508). No attacker-supplied size, count, or offset
   reaches this file.
3. **Ring payload sizes are compile-time constants.** The macro-side struct
   copy (`*(struct ktr_info_..._args *)&entry->ktr_data[0]`,
   sys/sys/ktr.h:193/214) is bounded by `CTASSERT(sizeof(...) <=
   KTR_BUFSIZE)` (sys/sys/ktr.h:155). Tracepoint arguments fill a fixed
   struct; they never feed sizes/counts.

## Negative results (classes considered and killed)

- **Ring index arithmetic / wraparound** (kern_ktr.c:511-512): `int
  ktr_idx` overflow after 2^31 entries wraps in practice;
  `(negative) & mask` on two's complement stays in 0..mask → in-bounds.
  Not this file; no OOB.
- **Per-CPU buffer allocation** (kern_ktr.c:231-239): compile-time
  power-of-2 `KTR_ENTRIES` (enforced, kern_ktr.c:99), `M_ZERO` → no
  uninit leak; boot-buffer memcpy sized 256 entries into a ≥256-entry
  destination. No overflow at any sane KTR_ENTRIES.
- **Boot publication ordering**: already filed as **DF-0104** (cpu_sfence
  at kern_ktr.c:240) — not re-reported.
- **Dump/copyout surface**: the kernel never copies ring entries to
  userspace. DDB `show ktr` only prints (kern_ktr.c:557-680; indices
  masked at :570/:649, `tstate[MAXCPU]` bounded, `printcpu` checked at
  :595). ktrdump(1) reads via kvm → /dev/mem, root:kmem 0640
  (live-verified). `debug.ktr.*` sysctls export four ints only.
- **Sysctl write abuse** (`debug.ktr.resynchronize`, test knobs,
  verbose): all writes pass `SYSCAP_NOSYSCTL_WR` privilege gate
  (sys/kern/kern_sysctl.c:1445-1448) → root-only. Test knobs are
  `#if KTR_TESTLOG`-gated off unless KTR_ALL.
- **`ktr_caller1/2` kernel-text pointers in the ring**: exposure requires
  DDB console or ktrdump/root. No unprivileged leak path (live-verified:
  no unpriv OID even exposes the ring).
- **Entry lifetime/UAF**: per-CPU buffers are kmalloc'd once
  (kern_ktr.c:233) and never freed anywhere (no teardown, not a module);
  no `panicstr`-time use (NULL-return guard). No UAF.
- **Torn/stale caller fields** (caller writes happen after `crit_exit`,
  kern_ktr.c:524 vs :532): trace incoherence only; same-slot reuse needs
  2048 same-CPU intervening logs inside the begin→finish window — not
  achievable in the straight-line macro body.
- **KTR_VERSION handling**: static const 4 (kern_ktr.c:179); consumers
  (ktrdump nlist switches) are userland; no runtime version mutation.

## Guest state

Live checks on `dfbsd-qemu` guest `DragonFly dfbsd 6.5-DEVELOPMENT #0 ...
X86_64_GENERIC x86_64`:

- `sysctl debug.ktr` → `unknown oid` — KTR (and hence this file) is not
  compiled into the stock kernel (`sys/platform/pc64/conf/files:110`:
  `optional ktr`). The file's entire runtime surface on the default
  configuration is dead code.
- `/dev/mem`, `/dev/kmem`: root:kmem 0640.

Guest left up and clean (no dirt: no kernel build, no crash, no writes).

## Fix validation

Not performed on the guest (would require a custom `options KTR` kernel
build for a speculative panic-only Info finding). `fix.diff` is
`git apply --check`-verified against the unmodified `sys/` tree: adds
strictly-above + 8-byte-alignment requirements to FRAMEUP. Genuine frame
chains always grow upward on x86-64, so the hardening cannot break any
legitimate walk; it converts "accept any near pointer" into "accept only a
plausible caller frame".

## Bottom line

The file is memory-safe against attacker input **by construction**: the
only attacker-influenceable pointer candidate class (user `%rbp`) is
unreachable past the kernel/user address-space split baked into the FRAMEUP
test, writes go to a fixed ring slot, and all sizes are compile-time
constants. The residual weakness — the heuristic's tolerance of stale
kernel rbp values — has a panic-only ceiling in a debug-only facility and
is filed as Info hardening with a one-hunk fix. Everything else on the
pass-2 checklist (ring math, allocation, dump paths, ordering) is either
in-bounds, root-gated, or already filed (DF-0104).

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