# DF-2635 — VERDICT

**Status: reproduced (impact: panic, confidence: certain).**

## Narrative
Forged image identical to DF-2636's recipe except the flipped field is
`meta.inum` (inode block +0x58), changed from 1 to 0x42 — type stays 1
(DIRECTORY) so the mount is fully functional.

Guest run (stock INVARIANTS kernel, DragonFly 6.5-DEVELOPMENT #0):

1. `mount_hammer2 /dev/vn1@testvol /mnt/h2635` — **succeeded** (console:
   `hammer2_mount: ... rdonly=0`, `no recovery needed`).
2. `ls -la /mnt/h2635`, `echo hello > /mnt/h2635/f`, `cat`, `sync` —
   all succeeded (the RW mount is fully usable; iroot's wrong meta.inum is
   not consulted on these paths).
3. `umount /mnt/h2635` — **panic**, exactly the predicted walk-off:

```
Fatal user address access from kernel mode from umount at ffffffff80960353
Fatal trap 12: page fault while in kernel mode
fault virtual address      = 0x0
instruction pointer        = 0x8:0xffffffff80960353
current process            = 852 (umount)
Stopped at hammer2_inode_drop+0x1e3: movq (%rax),%rdx
```

`fault virtual address = 0x0` confirms the degeneration:
`struct hammer2_inode.next` is at offset 0, so the runaway walk
`xipp = &(*xipp)->next` collapses to NULL and the loop condition `*xipp`
reads address 0. The trap is *inside hammer2_inode_drop* called from the
umount path — the exact code at sys/vfs/hammer2/hammer2_inode.c:624-625,
with the wrong bucket chosen at :617 because `iroot->meta` was overwritten
post-hash by hammer2_vfsops.c:456 (`iroot->meta = ripdata->meta`).

## Pre-conditions / trust boundary
Crafted or checksum-forged hammer2 image (mount requires root — same
boundary as DF-0763/DF-2616/DF-2624 family). All media bytes are
otherwise structurally valid; only meta.inum != 1 on the PFS root inode.

## Exploit ceiling
Controlled NULL-pointer read panic (DoS) at unmount, plus in-memory inode
aliasing/lookup inconsistency for the mount's lifetime (iroot unreachable
by inum; a second inode for the same media chain can be created by
inum-based lookups such as nlookupdotdot). No write primitive.

## Fix validation
fix.diff (a) preserves `meta.inum = 1` across both wholesale meta
overwrites in hammer2_vfsops.c (the inode is hashed under 1), and
(b) hardens `hammer2_inode_drop`'s removal walk against falling off the
list (`while (*xipp && *xipp != ip)` + warn on desync). Single-fix kernel
built with `make nativekernel`, installed, rebooted: the same forged image
mounts, is usable, and **unmounts cleanly** (fix_run.log) while the
baseline panicked in the same session.
