# VERDICT — DF-3112 (HAMMER1 ioctl dispatcher privilege-gate bypass)

**Status: reproduced** (unprivileged local execution of 9 gated-class ioctl
commands) — impact: none directly (info disclosure / policy bypass; no memory
corruption).  Fix validated on a patched guest kernel (see bottom).

## How it was reproduced

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), cc 8.3.
A 1.1 GB HAMMER1 filesystem was created on `vn0`
(`newfs_hammer -f -L pocfs /dev/vn0`), mounted at `/mnt/h1`, with
`/mnt/h1/histfile` carrying 3 distinct history TIDs (write +
`hammer synctid` × 3).

`hammer_gate_poc.c` (uid 1001 `maxx`, plain `ioctl(2)` on an O_RDONLY fd):

- **[A] GETHISTORY**   → rc=0, 3 history entries  → gate bypassed
- **[B] GET_VERSION**  → rc=0, version string     → gate bypassed
- **[C] GET_INFO**     → rc=0, label 'pocfs'      → gate bypassed
- **[D] SYNCTID**      → rc=0, flusher TID        → gate bypassed
- **[E] SCAN_PSEUDOFS**→ rc=0, pfs 0 scanned      → gate bypassed
- **[F] PRUNE**        → EPERM (gated contrast — same dispatcher)
- **[G] ADD_SNAPSHOT** → EPERM (gated contrast — same dispatcher)

The same binary run **as root** shows [F]/[G] reaching the handler
(ADD_SNAPSHOT → EINVAL from its own `index >= count` validation), proving the
EPERM seen for the unprivileged run is exactly the
`caps_priv_check(SYSCAP_NOVFS_IOCTL)` result at hammer_ioctl.c:72 that
[A]-[E] overwrite.  Stable across 3 runs (see run.log, run.root.log).

## Root cause

sys/vfs/hammer/hammer_ioctl.c:72 computes the gate once; the nine case labels
listed in README.md assign `error = hammer_ioc_...(...)` without the
`if (error == 0)` guard used by every other case in the same switch.

## Impact ceiling

- Unprivileged disclosure of filesystem topology/state: volume list (compounds
  DF-0870), PFS configuration incl. shared/unique UUIDs and sync TIDs
  (GET_PSEUDOFS), snapshot TID inventory (GET_SNAPSHOT), per-file modification
  history (GETHISTORY), version/statistics (GET_VERSION/GET_INFO).
- Unprivileged SYNCTID drives global flusher synchronization
  (op=SYNC1/SYNC2 do full-mount flush cycles) — a resource/DoS amplifier
  comparable to sync(2) spam, but with TID-precision disclosure for backup
  tooling.
- On filesystems with corrupted/crafted records, GET_SNAPSHOT /
  GET_CONFIG / SCAN_PSEUDOFS copy disk-buffer contents to the caller without
  clamping to the record's `data_len` contract (hammer_pfs.c:363 uses the
  disk-controlled length directly as the copyout size) — the missing gate
  makes that path reachable by any local user.

No memory-safety violation was demonstrated from this finding alone.

## Fix validation

`fix_combined_for_validation.diff` (this finding's 9 dispatcher hunks + the
DF-3114 hunks) was applied to the guest's `/usr/src`, kernel rebuilt with
`make nativekernel KERNCONF=X86_64_GENERIC`, rebooted, and the identical PoC
re-run:

- unprivileged run: [A]-[E] now return **EPERM** (gate enforced), [F]/[G]
  unchanged (EPERM);
- root run: [A]-[E] succeed legitimately, [H] now returns **EINVAL**
  (DF-3114 fix).

Baseline vs patched transcripts: run.log / run.root.log (baseline) and
run.patched.log / run.patched.root.log (patched) in this directory.

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