# DF-3177 — hpfs NFS file handles are a bare 32-bit LSN with no generation,
# no length check and no ino sanity check: forged handles turn any
# magic-matching sector (including attacker-written file content) into a
# live vnode on exported mounts

**File:** `sys/vfs/hpfs/hpfs_vfsops.c:423-454` (`hpfs_fhtovp`, `hpfs_vptofh`),
consumed by `hpfs_vget` (`hpfs_vfsops.c:456-552`)
**Severity:** Medium (requires a root-created NFS export of an hpfs mount;
impact composes into the already-filed memcorrupt/raw-R-W sinks)
**Verification:** not run on the guest — the trigger transport is an NFS
RPC from an export-authorized client; the harness guest runs no NFS server.
The vget mechanism itself (accept-any-sector-with-magic) is *proven on this
same guest* by the DF-3137/DF-3141 packs' crafted images. Code trace below.

## The defect

* `hpfs_vptofh` (`hpfs_vfsops.c:450-453`) emits a file handle of exactly
  one useful field — `hpfid_ino = hpp->h_no` (a 32-bit sector number).
  The generation field is *commented out* (`/* hpfhp->hpfid_gen = hpp->h_gen; */`)
  and `h_gen` does not even exist in `struct hpfsnode`.
* `hpfs_fhtovp` (`hpfs_vfsops.c:424-440`) checks nothing — not
  `hpfid_len`, not the generation — and passes `hpfhp->hpfid_ino`
  straight to `VFS_VGET`.  (There is a stale XXX comment admitting no
  checks exist.)
* `hpfs_vget` (`hpfs_vfsops.c:529-544`) then `bread`s **any** 512-aligned
  sector the handle names and accepts it as a vnode iff its first u32
  equals `FN_MAGIC` (0xF7E40AAE) — with `vp->v_type` and every consumed
  `h_fn` field (allocation tree, sizes, parent) taken from that sector.

Consequences on an exported hpfs mount (root must create the export; the
client must be permitted by the export addr list, e.g. world-exported):

1. **Zero-entropy handle forgery.** NFS access checks are enforced on
   LOOKUP; READ/WRITE/GETATTR go straight through fhtovp. A client can
   sweep the 32-bit LSN space (or harvest fnode LSNs from directory
   reads) and address sectors it never looked up. On hpfs the
   directory-permission bypass is near-nil (hpfs maps all files to
   `hpm_uid`/`hpm_mode`), so the real teeth are (2).
2. **File-content-as-fnode.** `FN_MAGIC` is 4 attacker-choosable bytes of
   ordinary file *content*. On a RW export, a client writes a file whose
   data contains forged fnode structures at 512-byte alignment, then uses
   a forged handle naming those sectors: vget mints a vnode whose entire
   `h_fn` (alblk/alnode trees, `fn_size`, parents) is attacker-supplied.
   Every already-filed crafted-fnode sink then fires **without any
   crafted disk image** — e.g. DF-3139 (al_len==0 "infinite extent" →
   arbitrary raw-device sector R/W via pread/pwrite) and DF-0857/3137/3138
   (heap OOB read/write via unvalidated alblk fields) — i.e. remote
   kernel memory corruption / raw device access from an export-authorized
   NFS client.

Twin of DF-3097 (HAMMER1 zero-entropy handles, Low) but with a strictly
weaker handle (32 bits, no generation at all) and a strictly worse sink
composition (any-magic-sector acceptance in vget).

## Why not verified on the guest

Reproducing requires: (a) root-created NFS export of an hpfs mount,
(b) mountd+nfsd running, (c) raw NFS RPC forgery from a permitted client
(the stock Linux host or an in-guest client). The verification harness
runs no NFS server and this is a Medium non-corruption finding; the
in-file mechanism (vget accepts arbitrary LSN with matching magic) is
code-certain at `hpfs_vfsops.c:529-544` and was exercised on this same
guest by the DF-3137/DF-3141 packs (their crafted images placed valid
fnode headers at chosen LSNs and vget accepted them).

## Fix sketch

* Set and verify a generation: give `struct hpfsnode` an `h_gen`
  (e.g. derived from `fn_readhist`/mount-time random), emit it in
  `vptofh`, and require a match in `fhtovp` (reject stale handles).
* In `fhtovp`, validate `hpfid_len == sizeof(struct hpfid)` and sanity
  the ino (`ino < su_btotal`, sector-aligned) before calling VFS_VGET.
* Defense-in-depth in `vget`: reject ino >= `su_btotal` (mount-time
  validated census per DF-3178) so handles cannot name arbitrary
  sectors.
