# DF-2655 VERDICT — REPRODUCED (silent data substitution; LZ4 sibling additionally panics via OOB write)

## What was claimed

`hammer2_decompress_ZLIB_callback()` (sys/vfs/hammer2/hammer2_strategy.c:229-271):
when `inflate(&strm_decompress, Z_FINISH)` returns anything other than
`Z_STREAM_END` (:257-261), the callback kprintf's, bzeros the scratch buffer,
then unconditionally bcopies the (zeroed) scratch into `bp->b_data` (:262),
zero-fills the tail and sets `bp->b_resid = 0` (:263-269) **without ever
setting `bp->b_error`/`B_ERROR`**.  `biodone()` therefore completes the read
as a full SUCCESS and read(2) hands the caller kernel-fabricated zeros for
that 64KB block.  Precondition: a compressed DATA block whose integrity
check PASSES but whose payload is not a valid zlib stream — i.e. crafted
media with recomputed checksums.

## How it was reproduced (stock kernel, no code changes)

1. **Image construction** (`mkimage.sh`): `newfs_hammer2` a 2GB image; mount;
   `hammer2 setcomp zlib:6 /mnt/zdir` (verified on media: dir inode
   comp_algo=0x63); write a 114688-byte compressible `zfile` (two 64KB ZLIB
   blocks, methods=0x33, stored 8192B each); default-LZ4 `lzfile` likewise;
   clean unmount; pristine copies kept.
2. **Offline patch** (`h2zsub patch zfile zlib 0`): resolves the file exactly
   as the kernel does — DIRENT bref (name embedded in check.buf,
   key=name-hash) -> `embed.dirent.inum` -> INODE bref keyed by inum
   (mirrors hammer2_xop_nresolve / hammer2_chain_inode_find) -> DATA bref at
   lbase 0.  The patcher VERIFIES the original XXH64 first (refuses
   otherwise), overwrites all 8192 stored bytes with 0xFF (invalid zlib
   header -> inflate returns Z_DATA_ERROR != Z_STREAM_END), then rewrites the
   DATA bref check.xxhash64 and cascades new XXH64s up the blockref chain
   (file inode -> root INDIRECT -> LOCAL root -> sroot -> volume header) and
   refreshes the three volhdr iCRC32s in every valid volhdr zone.  Result:
   fully checksum-valid metadata + a checksum-valid decompression bomb.
3. **Stock-kernel A/B/C** (`run_stock_ab.sh`):
   - A pristine: dd reads 114688B, md5 876569bdd45df2756a69a3fe85660f2a ==
     original.  Zero anomalies in dmesg.
   - B patched: dd **exit 0**, 114688 bytes read, md5
     b1e63222a373994ee242ce1a7e3da0fc, first 64KB = all 0x00 (nonzero byte
     count in first 64K: 0), `cmp` differs at byte 1, block 2 (lbase
     0x10000, unpatched) intact.  dmesg: `HAMMER2 ZLIB: Fatal error during
     decompression.`  **The read neither fails nor warns the user.**
   - B2/B3 determinism: identical fabricated md5 on two more fresh
     mounts (vn9, vn10); kprintf visible in both dmesg tails.

That is the complete substitution proof: same image, same file, checksums
valid, decompression demonstrably fails (kernel says so) — yet read(2)
returns SUCCESS with fabricated content.  No error path exists in
userspace to detect this (short of separately knowing the true content).

## LZ4 sibling (:182-220) — same missing b_error, PLUS an OOB write

Mechanically tested per directive.  The LZ4 callback fails identically
silent (`result < 0` -> kprintf, scratch zeroed, bcopied, b_resid=0, no
b_error).  Additionally the tail-fill

    if (result < bp->b_bufsize)
        bzero(bp->b_data + result, bp->b_bufsize - result);   /* :215-216 */

executes with a NEGATIVE result: start = bp->b_data − |result|, length =
b_bufsize + |result| — an out-of-bounds zeroing of |result| bytes BEFORE the
64KB bp->b_data.  |result| is `-(ip−source)−1` (hammer2_lz4.c:515-517) and
with an all-0xFF stream the walk consumes the whole input, so
|result| = stored size-prefix + 1 — **attacker-chosen via the image**.

- small (prefix 8, result −9): read(2) SUCCESS + zeros (lz4small-run.log,
  md5 b1e63222…), the 9-byte OOB landed in mapped heap — silent corruption,
  guest survived.
- big (prefix 16380, result −16381): OOB bzero began at bp->b_data − 16381
  (0xfffff80062a22003; bp->b_data page-aligned at 0xfffff80062a26000) in an
  unmapped page -> `Fatal trap 12 ... Stopped at memset+0xf0: movq %r10,(%rdi)`
  -> all CPUs stopped, machine dead at db> (panic.txt).  Fault-VA math
  proves the controlled magnitude.

The OOB write is a distinct memory-safety defect (flagged for a new finding;
High: heap OOB write, crafted media, controlled length).  It is fixed by the
same early-return hunk.

## Fix validation (kernel #1 = stock + fix.diff, 03:15 UTC build)

- Pristine zfile AND lzfile: md5s bit-identical to originals (fixrun1.log,
  fixrun2.log F1) — no regression.
- zlib-patched: `dd: /mnt/zdir/zfile: Input/output error` — read(2)=EIO;
  kprintf still logs for the operator (fixrun1.log).
- lz4-small patched: EIO (fixrun2.log F3).
- lz4-big patched: EIO, **no panic** — the operation that halted the stock
  kernel is cleanly failed (fixrun2.log F4), guest alive afterwards.
- (F5 in fixrun2.log hit the unrelated vn-device wedge from repeated mounts;
  the pristine-identity result was already established in F1.)

fix.diff matches the file's own error idiom (B_ERROR + b_error = EIO +
b_resid = b_bcount; same as the strategy-read default case at :428-437) and
adds the early return that also eliminates the negative-result bzero.

## Impact, reachability, honesty

- Primary claim: CONFIRMED silent data substitution — integrity attack,
  Medium, malicious-media precondition (root mounts image / attacker
  controls media bytes).  Not a privilege escalation; no memory-safety
  primitive on the ZLIB path itself.
- Sibling LZ4 OOB write: kernel heap OOB (zero-content) write with
  attacker-chosen length (up to stored block size, ~32KB) before a 64KB
  buffer — panic-class and potentially corruption-class; flagged separately.
- Everything was produced on the stock INVARIANTS kernel first, fix
  validated on a rebuilt kernel afterwards; guest reset afterwards.

## Kernel references

- sys/vfs/hammer2/hammer2_strategy.c:229-271 (ZLIB callback; :257-261 error
  path, :262 bcopy of zeroed scratch, :269 b_resid=0, no b_error)
- sys/vfs/hammer2/hammer2_strategy.c:182-220 (LZ4 callback; :206-212 error
  path, :215-216 negative-result OOB bzero, :218 b_resid=0, no b_error)
- sys/vfs/hammer2/hammer2_strategy.c:428-437 (in-file error idiom mirrored
  by the fix)
- sys/vfs/hammer2/hammer2_chain.c:5380-5428 (setcheck XXH64 over
  chain->bytes of stored payload — what the patcher recomputes)
- sys/vfs/hammer2/hammer2_lz4.c:515-517 (_output_error return value)

## Bottom line

REPRODUCED, deterministic, on the stock kernel: checksum-valid but
zlib-invalid block -> read(2) SUCCESS with fabricated zeros.  The LZ4
sibling reproduces the substitution AND exhibits an attacker-length
out-of-bounds kernel write (panic captured).  fix.diff turns both into
clean EIO with zero behavioral delta on pristine images (validated A/B on a
rebuilt kernel).

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