# DF-0746 — PoC evidence pack

**Finding:** Use-after-free READ in `l2cap_rtx`: `req->lr_id` is read after
`zfree(l2cap_req_pool, req)`.
**File:** `sys/netbt/l2cap_misc.c:190` (free via `:173`) and `:192` (read).
**Severity:** Low (CWE-416 Use After Free, read-only).

## Reachability on this guest

The runtime netbt L2CAP path (`BTPROTO_L2CAP` socket + RTX timeout) is
**unreachable on this KVM guest** and, even more, the read is **not emitted
in the shipped module**:

- `netbt/l2cap_misc.c` is `optional bluetooth` (`sys/conf/files:1614`) and
  `X86_64_GENERIC` has no `device bluetooth` — the file is **not in the
  running kernel**, only in the loadable module `/boot/kernel/netbt.ko`.
- `BLUETOOTH_DEBUG` is **not** defined in `sys/netbt/Makefile`, so `DPRINTF`
  expands to `((void)0)` (`sys/netbt/bluetooth.h:145`) and `req->lr_id` is
  never evaluated at runtime.
- Decisive: the disassembly of `l2cap_rtx` in the **unfixed** `netbt.ko`
  (`disasm_unfixed_l2cap_rtx.txt`) contains zero references to `req` after
  the `callq l2cap_request_free`, and is byte-for-byte identical to the
  **fixed** module (`disasm_fixed_l2cap_rtx.txt`).
- A live trigger would also require a Bluetooth radio (absent on guest).

This is the same harness-precedent cluster as DF-0745 (sibling double-free on
the same function) and DF-0393/0594/0616/0732/0733 (wifi/netgraph/bt-unreachable).
**The primary proof is the deterministic userspace harness.**

## Why no escalation

This is a single-byte **read** of freed memory whose value (in the immediate
path) is the original id set by the caller, going only to `kprintf`/dmesg.
There is no write primitive, no control-flow hijack, no victim-object
corruption — read-only hardening bug, no path to `uid=0`. (Per AGENT.md
Phase 6: a genuinely read-only primitive has no escalation chain.)

## Files

| file | what |
|------|------|
| `harness.c`        | BUGGY transcription of `l2cap_rtx` + `l2cap_request_free` + `vm_zone` model. Two tests: stale-read (returns original id) and slab-reuse read (returns attacker-shaped byte). Prints `UAF READ CONFIRMED`. |
| `harness_fixed.c`  | FIXED transcription: `lr_id` captured into a local before the free. Prints `FIXED: no read of *req after zfree`. |
| `build.sh`         | `cc -O2 -Wall -Wextra -o harness harness.c` + fixed variant. |
| `run.sh`           | runs both harnesses. |
| `build.log`        | full build output (final, clean). |
| `run.log`          | full BUGGY + FIXED run (the decisive output). |
| `fix.diff`         | `git apply`-able unified diff against `sys/netbt/l2cap_misc.c`. |
| `fix_build.log`    | `make` of `netbt.ko` with the fix applied — 0 errors, 0 warnings. |
| `fix_run.log`      | copy of the FIXED-harness output. |
| `disasm_unfixed_l2cap_rtx.txt` | objdump of `l2cap_rtx` in the unfixed `netbt.ko`. |
| `disasm_fixed_l2cap_rtx.txt`   | objdump of `l2cap_rtx` in the fixed `netbt.ko` — **identical** to the unfixed form. |
| `env.txt`          | uname, kern.version, cc, kernel-config / module / Makefile notes. |
| `panic.txt`        | (placeholder — no panic; code-level UAF with no runtime manifestation). |
| `VERDICT.md`       | full narrative analysis. |
| `manifest.json`    | machine-readable artifact catalog. |

## Reproduce

```sh
ssh dfbsd-maxx /bin/sh      # unprivileged user (uid 1001)
cd poc/DF-0746
./build.sh && ./run.sh
# BUGGY  -> "UAF READ CONFIRMED: req->lr_id dereferenced after zfree"
# FIXED  -> "FIXED: no read of *req after zfree; DPRINTF used the saved local"
```

## Expected

| | BUGGY transcription | FIXED transcription |
|---|---|---|
| Test 1 (stale) | `UAF READ CONFIRMED: req->lr_id dereferenced after zfree` (value `0x42` = original) | `FIXED: no read of *req after zfree` |
| Test 2 (reuse) | `UAF READ CONFIRMED: freed req->lr_id returns attacker-shaped byte` (value `0xDD`) | n/a (FIXED harness scribbles reuse then prints from saved local) |

## Fix validation (Phase 8)

1. `fix.diff` applies cleanly to `/usr/src` (`patch -p1`, `Hunk #1 succeeded at 185`).
2. `cd /usr/src/sys/netbt && make` builds `netbt.ko` with the fix — **0 errors,
   0 warnings** (-Werror), 105024 bytes.
3. Disassembly: unfixed and fixed `l2cap_rtx` are **byte-for-byte identical**
   (`0x5710`, 14 instructions) — proves both that the fix doesn't perturb
   production codegen AND that the production module never emitted the UAF
   read in the first place (DPRINTF is a no-op).
4. Logic transcription: `harness_fixed.c` prints the FIXED marker.

The runtime path needs Bluetooth hardware (absent), so the kernel cannot be
exercised live here; the harness transcription + module build + objdump
contrast is the validation. See `VERDICT.md` for the complete analysis.
