# DF-0812 — VERDICT

## Verdict: REPRODUCED (panic confirmed + fix validated)

**Unvalidated `redo_data_bytes` in HAMMER REDO recovery — kernel OOB read → panic / info leak on mount of crafted image.**

## Root Cause

`hammer_recover_redo_exec()` at `sys/vfs/hammer/hammer_recover.c:1296-1335`
handles `HAMMER_REDO_WRITE` records during stage-2 recovery. At line 1332-1335:

```c
error = vn_rdwr(UIO_WRITE, vp, (void *)(redo + 1),
                redo->redo_data_bytes,
                redo->redo_offset, UIO_SYSSPACE,
                0, proc0.p_ucred, NULL);
```

`redo->redo_data_bytes` is an `int32` read directly from the on-disk FIFO record
(`hammer_disk.h:662`). It is **never validated** against the record's payload
capacity (`hdr_size - sizeof(*redo) - sizeof(tail)`). The `vn_rdwr` call passes
this value verbatim as the copy length, which flows into `uiomove` →
`bcopy`/`memmove`, copying from `(redo + 1)` (the first byte after the 56-byte
`struct hammer_fifo_redo` inside the 16 KB `hammer_buffer`).

**Contrast with the UNDO path**: `hammer_recover_undo()` at
`hammer_recover.c:1053-1060` DOES validate:
```c
bytes = undo->head.hdr_size - sizeof(*undo) - sizeof(struct hammer_fifo_tail);
if (bytes < 0 || undo->undo_data_bytes < 0 || undo->undo_data_bytes > bytes) {
    hkprintf("Corrupt UNDO record, undo_data_bytes %d/%d\n", ...);
    return(EIO);
}
```

The REDO path has no equivalent check.

**CRC does not prevent the attack**: `hammer_crc_get_fifo_head()` at
`hammer_crc.h:196-200` computes the FIFO head CRC over `hdr_size` bytes
(the whole record). `redo_data_bytes` is inside the CRC-covered region (it's
part of the redo struct). An attacker changes `redo_data_bytes` and recomputes
the CRC — the CRC still passes. The extra bytes `vn_rdwr` reads past the record
are NOT CRC-checked.

## Mechanism (trigger → primitive → effect)

1. **Trigger**: A crafted HAMMER1 image containing a CRC-valid REDO_WRITE
   record with `redo_data_bytes` set to a value larger than the actual payload
   capacity. The attacker recomputes the FIFO head CRC over `hdr_size` bytes.

2. **Primitive**: On RW mount, HAMMER stage-2 recovery scans the UNDO/REDO
   FIFO. When it encounters the crafted REDO_WRITE record (in the extended
   REDO range, with no matching TERM), it calls `hammer_recover_redo_exec()`.
   The `vn_rdwr(UIO_WRITE, vp, (void*)(redo+1), redo->redo_data_bytes, ...)`
   call copies `redo_data_bytes` bytes from kernel heap starting at `redo+1`.

3. **Effect**:
   - **Panic (DoS)**: `redo_data_bytes = 0x7FFFFFFF` → `memmove`/`bcopy`
     walks ~2 GB of kernel virtual space → hits an unmapped page →
     `Fatal trap 12: page fault while in kernel mode`.
   - **Info leak**: `redo_data_bytes` = moderate value past the record →
     adjacent kernel heap bytes written to the recovered file via `vn_rdwr`.
     The attacker can then read the file to extract leaked kernel memory.

## Reproduction (#0 GENERIC, INVARIANTS ON)

### Real image proof (kernel panic):
1. `newfs_hammer` a 1 GB image, mount RW, write data + `fsync` (enables REDO),
   write more + `fsync` (generates REDO records), `sync`.
2. Copy the image while still mounted (preserves pending REDO records).
3. Run `craft_img` to scan for REDO records (type `0x0044`), patch
   `redo_data_bytes` to `0x7FFFFFFF`, recompute the FIFO head CRC.
4. Unmount the original, `vnconfig` + mount the patched image RW.
5. **Result**: `Fatal trap 12: page fault while in kernel mode` in `memmove+0x10a`
   (`repe movsq (%rsi),%es:(%rdi)`), with fault virtual address
   `0xfffff8007657a000`, `supervisor read data, page not present`.

Boot log excerpt:
```
HAMMER(TEST) recovery undo  300000000004b548-300000000004cf48 (6656 bytes)(RW)
HAMMER(TEST) Found REDO_SYNC 3000000000000000
HAMMER(TEST) recovery redo  300000000004b548-300000000004cf48 (6656 bytes)(RW)
HAMMER(TEST) Find extended redo  3000000000000000, 308552 extbytes
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xfffff8007657a000
fault code = supervisor read data, page not present
Stopped at memmove+0x10a: repe movsq (%rsi),%es:(%rdi)
```

### Deterministic harness proof:
`./harness` transcribes the `vn_rdwr` → `uiomove` → `bcopy` path with a
poisoned allocator, showing the OOB extent deterministically (4056 bytes past
a 96-byte record for `redo_data_bytes=4096`, or ~2 GB for `0x7FFFFFFF`).

## Impact Ceiling

- **Panic (DoS)**: Confirmed. Mounting a crafted HAMMER1 image RW causes an
  immediate kernel panic. This is a **root-only** precondition (mounting
  requires root, or `vfs.usermount=1` + a root-created image owned by the user).
  Threat model: "admin mounts a malicious filesystem image" (e.g., USB drive,
  downloaded image).
- **Info leak**: Kernel heap bytes adjacent to the REDO record in the
  `hammer_buffer` are written to the recovered file. Content is not
  attacker-controlled (it's whatever follows the record in kernel memory).
  Could leak kernel pointers, slab metadata, or other sensitive data.
- **No escalation**: This is a read-class primitive (OOB read via `vn_rdwr`).
  There is no write primitive — `vn_rdwr` writes TO the file FROM the kernel
  heap. No path to `uid=0`.

## Fix

`fix.diff` adds a validation check in `hammer_recover_redo_exec()` before the
`vn_rdwr` call, mirroring the UNDO path's `undo_data_bytes` validation at
lines 1053-1060:

```c
int redo_cap = redo->head.hdr_size -
    (int)sizeof(struct hammer_fifo_redo) -
    (int)sizeof(struct hammer_fifo_tail);
if (redo_cap < 0 || redo->redo_data_bytes < 0 ||
    redo->redo_data_bytes > redo_cap) {
    hkprintf("Corrupt REDO record, redo_data_bytes %d/%d\n", ...);
    break;
}
```

## Fix Validation (Phase 8)

- **Before (#0 unpatched)**: Same crafted image → `Fatal trap 12` in `memmove`.
- **After (#1 patched)**: Same crafted-image approach → `MOUNT_EXIT=0`, guest
  alive, recovery completes ("End redo recovery"), no panic.
- Patched kernel: `6.5-DEVELOPMENT #1: Sun Jul  5 20:29:37 UTC 2026`,
  SHA256 = `2201a08916929ab3e9f4044a266825c16b55ae960423c0e17ec4600289274fc9`.

## PoC Changes

- Wrote `craft_img.c` from scratch: scans the image for REDO records
  (type 0x0044), patches `redo_data_bytes` to a forged value, recomputes the
  FIFO head CRC using the kernel's own `iscsi_crc32`.
- Wrote `harness.c`: deterministic proof of the `vn_rdwr` OOB copy path.
- Copied `icrc32.c` from `sys/libkern/icrc32.c` (kernel CRC for userspace).

## Kernel References

- `sys/vfs/hammer/hammer_recover.c:1296-1335` — `hammer_recover_redo_exec` (the vulnerable function)
- `sys/vfs/hammer/hammer_recover.c:1332-1335` — `vn_rdwr(... redo->redo_data_bytes ...)` (unvalidated sink)
- `sys/vfs/hammer/hammer_recover.c:1053-1060` — UNDO path validation (the missing check's model)
- `sys/vfs/hammer/hammer_disk.h:658-668` — `struct hammer_fifo_redo` (`redo_data_bytes` at :662)
- `sys/vfs/hammer/hammer_crc.h:196-200` — `hammer_crc_get_fifo_head` (CRC covers only `hdr_size` bytes)
- `sys/vfs/hammer/hammer_redo.c:225,242` — legitimate `redo_data_bytes` assignment + CRC set
