# DF-5019 — VERDICT

**Status: reproduced (panic), fixed (validated). Impact: local DoS — deterministic
kernel panic; root-gated. Confidence: certain.**

## What was claimed

`TBRIDGE_GETRESULT` (tbridge.c:265-297) performs no check that a
`TBRIDGE_LOADTEST` preceded it. With a testcase module registered
(`tbridge_curtest != NULL`) but no LOADTEST issued, `tbridge_testcase`
(the proplib dict) is still NULL, and the testcase kthread's mandatory
`tbridge_test_done()` hits `KKASSERT(tbridge_testcase != NULL)`
(tbridge.c:196) — a deterministic kernel panic on any INVARIANTS kernel.
Without INVARIANTS the NULL dict walks through proplib NULL-tolerantly and
ends at `prop_dictionary_copyout_ioctl(NULL)` → ENOTSUP.

Distinct from known DF-2048 (`tbridge_curtest` NULL because no testcase
module is loaded — dereferenced one line earlier at tbridge.c:266).

## How it was proven (DragonFly 6.5-DEVELOPMENT guest, KVM)

1. **Stock kernel + kldload'able tbridge.ko**: module built from in-tree
   `sys/dev/misc/tbridge/Makefile`, loaded, testcase kmod `mytcase.ko`
   (registers tb_run → `tbridge_test_done(RESULT_PASS)`) loaded, PoC ran
   `ioctl(TBRIDGE_GETRESULT)` with no LOADTEST → **no panic**
   (`run.kmod.log`): out-of-tree kmods compile without `INVARIANTS`
   (no `-include opt_global.h`), so the KKASSERT is compiled out; the ioctl
   returned errno 45 (ENOTSUP) from the NULL-dict copyout. This bounds the
   exposure of the kmod path to a benign error.
2. **tbridge compiled into the kernel** (`device tbridge` appended to a copy
   of X86_64_GENERIC → KERNCONF DF5019, `make nativekernel`, INVARIANTS
   active — stock X86_64_GENERIC ships `options INVARIANTS`,
   sys/config/X86_64_GENERIC:56): same PoC →
   `panic: assertion "tbridge_testcase != NULL" failed in tbridge_test_done
   at /usr/src/sys/dev/misc/tbridge/tbridge.c:196` (`panic.txt`, `run.log`),
   guest wedged in DDB, ssh dead. Deterministic, single-shot, no race.

## Fix validation

`fix.diff` (guard `tbridge_testcase == NULL` → EINVAL at the top of
TBRIDGE_GETRESULT) applied inside the guest's `/usr/src` copy, kernel
rebuilt (build.fix.log), installed, rebooted, **identical PoC rerun**:
`GETRESULT returned -1, errno=22 (EINVAL)` — no panic, guest stayed up.

## Exploit chain / uid=0?

None. The device node is 0600 root:wheel (verified: crw------- root:wheel)
and the driver is `optional tbridge` (sys/conf/files:2122) — absent from
X86_64_GENERIC (only LINT64 lists it), so reaching it at all requires the
admin to build it in or kldload it, and then only root can open the node.
Impact ceiling: root-triggerable kernel panic of a debug/test driver
(realistically hit by dfregress runs: any GETRESULT after a failed/absent
LOADTEST — e.g. LOADTEST failed because no testcase was registered yet —
panics the regression kernel). The DF-2049 concurrency family similarly
stays root-gated.

## Notes

* `sys/sys/tbridge.h` is not self-contained: a testcase module that includes
  only it fails to compile (SYSINIT lives in `<sys/kernel.h>`); worked
  around in mytcase.c. Build-trap only.
* `tbridge_printf` underflow (DF-2047), `hz * timeout` overflow (DF-2050),
  concurrent-LOADTEST UAF (DF-2049) remain as known.

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