β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3177

hpfs NFS file handles are a bare 32-bit LSN β€” generation field commented out of vptofh, no hpfid_len/ino validation in fhtovp, and vget accepts any sector whose first u32 is FN_MAGIC: forged handles turn attacker-written file content into live vnodes, composing into remote arbitrary-device R/W and heap corruption on exported mounts

Summary

hpfs_vptofp emits hpfid_ino = h_no only; hpfid_gen is commented out (:452) and h_gen does not exist in struct hpfsnode. hpfs_fhtovp checks nothing - not hpfid_len, not a generation, not ino sanity - and feeds hpfid_ino straight to VFS_VGET (:431, with a stale XXX admitting it). hpfs_vget then breads the named 512-byte sector and mints a vnode iff its first u32 == FN_MAGIC (:529-544), taking v_type and every consumed h_fn field (allocation tree, sizes, parent) from that sector. NFS permission checks are enforced on LOOKUP, but READ/WRITE/GETATTR go straight through fhtovp, so an export-permitted client can (a) address any sector by forging the 32-bit LSN, and (b) on a RW export, first WRITE ordinary file content that begins with FN_MAGIC plus forged fnode/alblk structures at 512-byte alignment - vget then accepts the attacker's own file content as filesystem metadata, firing the already-filed crafted-fnode sinks (DF-3139 al_len==0 infinite-extent arbitrary raw-device R/W; DF-0857/3137/3138 heap OOB) remotely with no crafted image at all. Twin of DF-3097 (HAMMER1) but with a strictly weaker handle (32 bits, zero generation) and a worse sink composition. Requires an admin-created NFS export (hpfs never user-mountable). Not run on guest (needs nfsd/mountd + raw ONC-RPC harness; the vget any-magic-sector acceptance half already proven by the DF-3137/3141 packs). Fix: h_gen + fhtovp validation + vget ino bound.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3177 Β· 3 files
FileTypeDescriptionSize
README.md β€” 4.1 KB ↓ raw
manifest.json β€” 936 B view raw
verdict.json β€” 3.3 KB view raw

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

Confirmed kernel references

Detail

Exploit chain

(unverified) NFS client permitted by export -> forged fh{ino=X} where sector X is attacker-written file content starting with FN_MAGIC -> hpfs_fhtovp passes ino unchecked -> hpfs_vget breads sector X, magic matches, vnode minted with attacker h_fn -> hpfs_read via NFS READ walks forged alblk -> DF-3139/0857-class arbitrary device R/W or heap corruption

Evidence (decisive lines)

['README.md β€” code trace with path:line for every claim', 'sys/vfs/hpfs/hpfs_vfsops.c:450-453 (generation commented out)', 'sys/vfs/hpfs/hpfs_vfsops.c:424-440 (no len/gen/ino checks in fhtovp)', 'sys/vfs/hpfs/hpfs_vfsops.c:529-544 (any-magic-sector acceptance in vget)', 'findings/poc/DF-3141 (same guest: vget accepted crafted fnodes at chosen LSNs)']

PoC changes

no PoC written (no seed; transport requires an NFS export + raw RPC client)

Verified recommended fix

Add and check a generation number in vptofh/fhtovp, validate hpfid_len and ino < su_btotal in fhtovp (see README.md fix sketch).

Verdict

NOT RUN ON GUEST (transport-gated): the trigger is a forged NFS file handle delivered by an export-authorized client to a root-exported hpfs mount; the harness guest runs no NFS server and setting one up (nfsd+mountd+export+raw ONC-RPC forgery from a permitted peer) was out of scope for a Medium non-corruption finding. The mechanism is code-certain: hpfs_vptofp emits handles carrying only a 32-bit LSN with the generation field commented out (hpfs_vfsops.c:450-453), hpfs_fhtovp checks neither hpfid_len nor any generation nor ino sanity (hpfs_vfsops.c:424-440), and hpfs_vget accepts ANY 512-byte sector whose first u32 == FN_MAGIC as a vnode with all h_fn fields attacker-supplied (hpfs_vfsops.c:529-544). The vget acceptance half was proven on this same guest by the DF-3137/DF-3141 crafted-image packs (valid fnode headers at chosen LSNs accepted by vget). Threat: on a RW-exported hpfs mount, an export-permitted client writes FN_MAGIC-carrying forged fnode structures as ordinary file content and addresses them with forged handles, reaching the already-filed crafted-fnode sinks (DF-3139 arbitrary raw-device R/W; DF-0857/3137/3138 heap OOB read/write) remotely, with no crafted image. hpfs itself cannot be user-mounted (get_fscap -> SYSCAP_RESTRICTEDROOT), so both the export and the handle forgery premise sit behind root configuration.