# DF-5799 — hammer2_cleanup_devvp() strips `si_mountpoint` from live/foreign devices

**File:** `sys/vfs/hammer2/hammer2_vfsops.c` (+ `sys/vfs/hammer2/hammer2_ondisk.c`)
**Class:** protection removal / cross-filesystem state corruption (Dillon bucket: hammer2)
**Severity:** High · **Confidence:** certain · **Status: REPRODUCED** (and fix validated)

## What

`hammer2_cleanup_devvp()` (`sys/vfs/hammer2/hammer2_ondisk.c:216-235`) sets
`e->devvp->v_rdev->si_mountpoint = NULL` on **every** device entry it processes,
unconditionally. It is reached on paths where the devices are *not* hammer2's to
clean up:

1. **Routine secondary PFS mount** — `hammer2_vfs_mount()` matches the device to
   an existing live hmp and then calls `hammer2_cleanup_devvp(&devvpl)`
   (`sys/vfs/hammer2/hammer2_vfsops.c:1358-1360`) on the *caller-supplied*
   device list. The devices still back a **live, mounted** hammer2 hmp. The
   cleanup strips their `si_mountpoint` marker. On any multi-PFS hammer2
   installation (the standard DragonFly layout: `/`, `/var`, `/tmp`, … are
   separate PFS labels on one device), the second mount of the device silently
   disables the kernel's raw-device write protection for as long as any mount
   from that device exists. Even a **failed** secondary mount attempt (unknown
   label) strips the marker, because cleanup runs before the label lookup.
2. **Failed mount over a foreign filesystem's device** — when
   `vfs_mountedon()` rejects the mount (`sys/vfs/hammer2/hammer2_vfsops.c:1092-1099`),
   the error path calls `hammer2_cleanup_devvp(&devvpl)` on devices whose
   `si_mountpoint` was set by **another filesystem** (ufs, msdosfs, ext2fs,
   cd9660, ntfs, hpfs, udf all set it), clearing *their* marker.

## Why it matters

`si_mountpoint` is the **only** state behind two kernel protections:

* `vfs_mountedon()` (`sys/kern/vfs_subr.c:1887-1895`) — the double-mount
  guard used by ffs, msdosfs, ext2fs, cd9660, ntfs, hpfs, udf, and hammer2.
* The devfs raw-write gate (`sys/vfs/devfs/devfs_vnops.c:1007-1022`): any
  `open(O_WRONLY)` of a mounted block device returns `EBUSY` unless the mount
  is read-only (uid-independent; only `FSCRED` bypasses). After the marker is
  stripped, **any subject with write permission on the device node can
  overwrite the media under a live read-write mount** — including crafting
  on-disk metadata that the kernel's filesystem parsers then consume, and
  breaking ffs softdep assumptions (`sys/vfs/ufs/ffs_softdep.c:4266-4267`
  dereferences `si_mountpoint`).

Additionally, the marker lifecycle upstream holds the `struct mount *` of the
*first* mount; when that mount unmounts while later mounts keep the device,
the marker points at a freed mount — the devfs gate then reads
`si_mountpoint->mnt_flag` out of freed memory (stale-pointer read).
HAMMER1 fixed the ownership aspect correctly
(`sys/vfs/hammer/hammer_ondisk.c:362-366` clears only when
`si_mountpoint == hmp->mp`); hammer2 never got that fix.

## Reproduce (guest-live)

```
scp poc.sh dfbsd:/root/ && vm.sh run_root 'csh /root/poc.sh'
```

Expected output on a **vulnerable** kernel (see `run.log`):

```
[3] dd: /dev/vn0: Device busy          # gate active after first mount
[4] mount -t hammer2 /dev/vn0@LOCAL … mount rc=0   (routine secondary mount)
[5] 65536 bytes transferred … dd rc=0  # BUG: raw write under TWO live mounts
[6] (failed @NOSUCH mount)   dd rc=0   # BUG: even a failed attempt strips it
[7] dd: /dev/vn1: Device busy          # UFS gate active
[8] mount -t hammer2 /dev/vn1@X … "Device busy"   (failed h2 attempt)
[9] 65536 bytes transferred … dd rc=0  # BUG: raw write under live UFS mount
```

On the **patched** kernel (see `run.fixed.log`) every `dd` returns
`Device busy` at steps 5/6/9 while all mounts still succeed — fix validated
by full kernel rebuild (`build.log`, `fix.diff`).
