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)
PoC verification
Evidence pack
findings/poc/DF-3177 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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; */) andh_gendoes not even exist instruct hpfsnode.hpfs_fhtovp(hpfs_vfsops.c:424-440) checks nothing β nothpfid_len, not the generation β and passeshpfhp->hpfid_inostraight toVFS_VGET. (There is a stale XXX comment admitting no checks exist.)hpfs_vget(hpfs_vfsops.c:529-544) thenbreads any 512-aligned sector the handle names and accepts it as a vnode iff its first u32 equalsFN_MAGIC(0xF7E40AAE) β withvp->v_typeand every consumedh_fnfield (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):
- 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). - File-content-as-fnode.
FN_MAGICis 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 entireh_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 hpfsnodeanh_gen(e.g. derived fromfn_readhist/mount-time random), emit it invptofh, and require a match infhtovp(reject stale handles). - In
fhtovp, validatehpfid_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.
No comments yet.