# DF-0893 — hammer_enter_undo_history unlocked RB/TAILQ race

Race in `hammer_enter_undo_history()` (`sys/vfs/hammer/hammer_undo.c:432-460`),
called from `hammer_generate_undo()` at `hammer_undo.c:125` **before**
`undo_lock` is acquired at `:133`. The function mutates the per-mount
RB tree (`rb_undo_root`), TAILQ LRU list (`undo_lru_list`), and `undo_alloc`
counter with no lock held. Two concurrent HAMMER v1 frontends (any unprivileged
user with write access to a HAMMER v1 mount) race these mutations:
`KKASSERT(onode==NULL)` panic on INVARIANTS-ON (GENERIC), TAILQ corruption /
arbitrary write (CWE-787) on a non-INVARIANTS build.

## How to reproduce

```sh
./build.sh && ./run.sh
```

`build.sh` compiles two programs:
- `race_harness` — the deterministic race transcription (primary proof).
- `hammer_race_live` — live trigger for a HAMMER v1 mount (needs root setup,
  see `setup_live.sh`; `run.sh` runs only `race_harness` by default).

`run.sh` runs `race_harness`. Expected output: every **BUGGY** trial reports
`RACE CONFIRMED` (KKASSERT trip / SIGSEGV / cycle-hang); every **FIXED**
trial reports `no violations detected`. On a fixed kernel the buggy trials
still confirm (the harness models the bug logic), but the kernel itself no
longer carries the unlocked code path.

## Files

| file | purpose |
|------|---------|
| `race_harness.c` | deterministic race transcription (primary proof) |
| `hammer_race_live.c` | live unprivileged trigger on a chowned HAMMER v1 mount |
| `setup_live.sh` | root-side HAMMER v1 image + mount setup |
| `run_live_loop.sh` | loop the live trigger to widen the race window |
| `df_tree.h` / `df_queue.h` | verbatim `sys/sys/tree.h` + `sys/sys/queue.h` |
| `shim/sys/{cdefs,spinlock}.h` | userspace shims so the kernel headers compile |
| `fix.diff` | git-apply-able fix (move `undo_lock` before the call) |
| `VERDICT.md` | full analysis: mechanism, reachability, impact, fix validation |
| `build.log` / `run.log` | full untrimmed build + decisive run output |
| `run_baseline.log` | harness output on the unpatched `#0` kernel |
| `fix_build.log` | full single-fix kernel build log |
| `fix_run.log` | harness output on the patched `#1` kernel |
| `fix_run_live.log` | live HAMMER v1 workload on the patched kernel (no regression) |
| `live_run.log` | live trigger attempts on the unpatched kernel (reachability) |
| `env.txt` | guest environment |

## Trigger preconditions (live path)

1. Default GENERIC kernel (HAMMER v1 compiled in — `options HAMMER`).
2. Root creates a HAMMER v1 image and mounts it (realistic admin setup):
   `vnconfig -c /dev/vn0 hammer.img && newfs_hammer -f /dev/vn0 &&
    mount_hammer /dev/vn0 /mnt/h0893 && chown user /mnt/h0893`.
3. Unprivileged user runs `hammer_race_live /mnt/h0893` from multiple threads.
4. Race fires when `undo_alloc` reaches `HAMMER_MAX_UNDOS` (1024) and two
   threads hit the LRU recycle simultaneously (timing-dependent; the
   deterministic harness is the reliable proof).
