# DF-0033 — PoC

`fdtol_race.c` — unsynchronized `fdtol->fdl_refcount++` (under per-proc
`p_token`) racing `fdl_refcount--` (under the shared `fd_spin`) in the
`rfork(RFPROC|RFTHREAD)` fdshare path → lost-update → premature `kfree(fdtol)`
→ **UAF on `M_FILEDESC_TO_LEADER`**. Plus an unlocked `fdl` list splice in
`filedesc_to_leader_alloc` (self-admitted "NOT MPSAFE").

## Status: REPRODUCED

Three distinct kernel panics from unprivileged user `maxx` (see `VERDICT.md`
and `panic.txt`):

1. `panic: filedesc_to_refcount botch: fdl_refcount=0` — the `KASSERT` at
   `kern_descrip.c:2627` catching the refcount underflow directly.
2. `panic: BADFREE2` — slab double-free of the prematurely-freed `fdtol`.
3. `panic: memory chunk … is already allocated!` — slab corruption on the next
   `kmalloc`, stack: `chunk_mark_allocated ← _kmalloc ←
   filedesc_to_leader_alloc ← fork1 ← sys_rfork` (the exact cited path).

The race is intermittent (CVSS AC:H); reproducibility needs several short
bursts of hammering.

## The bug (confirmed, line-level)

`fork1()` fdshare branch (`sys/kern/kern_fork.c`):
```c
:324  lwkt_gettoken(&p1->p_token);        /* per-PROC token */
…
:568  fdtol = p1->p_fdtol;
:569  fdtol->fdl_refcount++;              /* ONLY p_token held */
```
`fdfree()` (`sys/kern/kern_descrip.c`):
```c
:2622 spin_lock(&fdp->fd_spin);           /* shared fd-table spinlock */
…
:2675 fdtol->fdl_refcount--;              /* ONLY fd_spin held */
```
`p1->p_token` is per-process; peers sharing `p_fd` hold **different** p_tokens,
so the increment and decrement do not share a serialization lock for the same
`int` word → lost update. `fdl_refcount` is a plain `int` (`filedesc.h:110`).

## Build & run (unprivileged, disposable VM)

```
./build.sh                    # cc -O2 -o fdtol_race fdtol_race.c
./run.sh                      # looped bursts until panic or 12 rounds
# or directly:
./fdtol_race <secs> <peers>   # e.g. ./fdtol_race 20 10
```

Run as the unprivileged user (e.g. `maxx`, uid 1001). A panic drops the guest
into DDB and lands in `dfbsd-qemu/boot.log` on the host (serial console).

## Expected output (bug present)

A kernel panic — one of:
- `panic: filedesc_to_refcount botch: fdl_refcount=0`
- `panic: BADFREE2`
- `panic: memory chunk <addr> is already allocated!`

## Files

| File | Purpose |
|------|---------|
| `fdtol_race.c` | controlled racer (bounded concurrency + slab pressure) |
| `build.sh` / `run.sh` | exact build/run |
| `build.log` / `run.log` | full logs |
| `panic.txt` | the three panic signatures + stacks from `boot.log` |
| `env.txt` | guest uname, compiler, sysctls |
| `VERDICT.md` | full narrative: mechanism, evidence, exploit-chain characterization |
| `fix.diff` | git-apply-able fix: take `fd_spin` around `++` and the splice |
| `manifest.json` | machine-readable catalog |
