# VERDICT — DF-3114 (hammer_ioc_gethistory ATKEY signed-overflow wrap)

**Status: reproduced** — impact: none directly (incorrect result /
out-of-contract information return; a same-family cousin of the DF-3111
key-wrap class).  Fix validated on the patched guest kernel.

## How it was reproduced

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS).
`/mnt/h1/histfile` has three distinct history TIDs
(0x0000000100008020/60/a0).  PoC test [H]: GETHISTORY with
`flags=ATKEY, key=nxt_key=0x7fffffffffffffff`.

Stock kernel, unprivileged (uid 1001): rc=0, **entries=3**, flags=0x9
(ATKEY|EOF) — the `++cursor.key_beg.key` at hammer_ioctl.c:354 wrapped to
INT64_MIN and the scan covered every record of the file, returning the whole
history for a key offset at which **no** record can exist (records are stored
as BASE+LEN; key INT64_MAX means "beyond end of file").  Identical result as
root (run.root.log).  Stable across 3 runs.

Root cause trace (stock source):
- hammer_ioctl.c:296-299 — validation allows key == nxt_key == HAMMER_MAX_KEY;
- hammer_ioctl.c:352-356 — REGFILE branch executes `++cursor.key_beg.key`
  (signed overflow, UB — wraps on this build);
- hammer_ioctl.c:386-397 — iteration from the wrapped key returns every
  record; add_history (hammer_ioctl.c:419-444) then admits them all because
  `hist->key` (INT64_MAX) is ≥ every `key - data_len`;
- no OOB: add_history guards `hist->count == HAMMER_MAX_HISTORY_ELMS`
  (lines 470, 493) before each `hist_ary[]` write; cursor remains pinned to
  `obj_id`/localization, so only the caller's own open file is scanned.

## Why it matters (bounded)

The wrap defeats the intended key-range semantics and returns data the API
contract says cannot exist for that offset; combined with DF-3112 (GETHISTORY
ungated) any local user can enumerate a file's full modification history with
a single call that should have returned nothing.  There is **no** memory-safety
consequence (bounded arrays, pinned cursor) — severity Low.

Secondary latent defect (fixed in the same diff, not reachable from userland):
the `default:` obj_type EINVAL at hammer_ioctl.c:367-370 is silently
overwritten by `error = hammer_btree_first(&cursor)` at line 386.

## Fix validation

`fix.diff` (2 hunks) — merged into `../DF-3112/fix_combined_for_validation.diff`,
applied to the guest `/usr/src`, kernel rebuilt (`make nativekernel
KERNCONF=X86_64_GENERIC`), rebooted:

- patched kernel, root run (`/tmp/poc /mnt/h1/histfile keep`): **[H] →
  rc=-1 errno=22 (EINVAL)** — wrap eliminated;
- patched kernel, unprivileged run: GETHISTORY → EPERM (DF-3112 gate),
  consistent fix interaction.

Transcripts: run.log / run.root.log (baseline), run.patched.root.log (patched).

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