# DF-0839 — hammer_ioc_set_version leaks sync_lock and finalize_lock on undo-fail

## Summary

`hammer_ioc_set_version()` (sys/vfs/hammer/hammer_ioctl.c) acquires the mount's
`finalize_lock` (exclusive) and `sync_lock` (exclusive via `hammer_sync_lock_ex`),
then on the undo-upgrade failure path (`goto failed` at line 651) jumps past the
matching `hammer_sync_unlock` / `hammer_unlock` at lines 664–665. Both locks are
permanently leaked.

- **Severity**: Medium (root-only ioctl → local DoS)
- **Impact**: deadlock/DoS — sync_lock held exclusively forever deadlocks all
  subsequent HAMMER write/flush operations (20+ sync_lock_sh/ex call sites).
- **Root cause**: missing unlock on the error cleanup path.

## How to reproduce

```sh
./build.sh   # cc -o trigger trigger.c
./run.sh     # as root, against a file on a mounted HAMMER v<4 filesystem
```

The trigger issues `HAMMERIOC_SET_VERSION(cur_version=4)` on a HAMMER v3 mount.
On a **valid** filesystem the undo upgrade succeeds and locks are released
normally (no bug). The lock leak only fires when `hammer_upgrade_undo_4()`
returns an error — which requires `hammer_bnew()` to fail.

### Failure-path reachability analysis

`hammer_bnew()` with `isnew=1` calls `hammer_io_new()` which calls `getblk()`
and always returns 0. Therefore the undo-upgrade failure path is **effectively
unreachable** on a validly-mounted filesystem under normal conditions. The
finding's stated trigger ("memory pressure or crafted UNDO blockmap causing
hammer_bnew fail") requires extraordinary conditions:

- Extreme memory pressure (getblk/alloc failures)
- A crafted filesystem image with a corrupted UNDO blockmap whose `alloc_offset`
  translates to an out-of-range volume number via `hammer_xlate_to_undo()`
  (causing `hammer_get_volume()` to return NULL → error)

On the default **GENERIC** kernel (INVARIANTS ON), if the failure were
triggered:
1. `sync_lock_refs` stays at 1 (never decremented)
2. `hammer_done_transaction()` at hammer_transaction.c:131 trips
   `KKASSERT(trans->sync_lock_refs == 0)` → **immediate kernel panic**

On a **production** kernel (INVARIANTS OFF):
1. The KKASSERT is a no-op
2. `hmp->sync_lock` stays exclusively locked forever
3. Every subsequent HAMMER write/flush operation that calls
   `hammer_sync_lock_sh()` or `hammer_sync_lock_ex()` deadlocks permanently

Per the task instructions, since the failure path is too narrow to trigger
with a valid image, a **deterministic code-level trace** is used as the proof.

## Expected output

On a valid HAMMER v3 mount (success path — no bug triggered):
```
[probe] issuing HAMMERIOC_SET_VERSION(cur=4) on /mnt/htest/testfile (fd=3)
[probe] ioctl rc=0 errno=0 head.error=0 head.flags=0x0
[probe] undo-upgrade succeeded (no leak this run)
```

If the undo-upgrade failure path were hit on GENERIC, expect a kernel panic
(KKASSERT in hammer_done_transaction). On production, expect a permanent hang
on the next HAMMER sync/flush operation.
