md_lookup_swap() reads only the low 32 bits of the 64-bit NFSv3 file size into a signed int β swap files in the 2-4 GiB window produce negative swap_nblks (negative swap vnode size, ~2^64-block swaponvp claim); >=4 GiB sizes silently wrap
Summary
fattr.size in NFSv3 spans wire words [5..6] (nfsuint64) but only fattribs.v3[6] is decoded into signed int 'size' (:286); swap_nblks = size/1024 (:296) into int. 2-4 GiB swap file -> negative swap_nblks; >=4 GiB -> mod-2^32 truncation (8 GiB -> swap silently disabled). The v2 twin at :292 lands a >=2 GiB u32 size in the same signed int. Consumer nfs_vfsops.c:666-701 doubles the negative count, sets VTONFS(vp)->n_size negative and passes it to swaponvp's u_quad_t nblks where it sign-extends to ~2^64 blocks - boot-path DoS; no memory-safety primitive. A legitimate server exporting a >2 GiB swap file, or any LAN forger answering the boot LOOKUP (DF-3205 vector), corrupts the diskless client's swap geometry. Fix: fxdr_hyper + accept only <= INT_MAX.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3211 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.0 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| verdict.json | β | 2.5 KB | view raw | |
| manifest.json | β | 878 B | view raw | |
| fix.diff | β | 825 B | view raw |
DF-3211 β md_lookup_swap() decodes only the low 32 bits of the 64-bit NFSv3 file size into a signed int β negative / truncated swap size
File: sys/vfs/nfs/nfs_mountrpc.c:286, 292-299; consumer sys/vfs/nfs/nfs_vfsops.c:666-701, sys/vm/vm_swap.c (swaponvp takes u_quad_t).
What
NFSv3 fattr.size is a 64-bit hyper (wire words [5]=hi, [6]=lo β sys/vfs/nfs/nfsproto.h:325
nfsuint64 nfsv3fa_size after type/mode/nlink/uid/gid). md_lookup_swap reads only:
size = fxdr_unsigned(u_int32_t, fattribs.v3[6]); /* :286 β low word only, into int */
...
nfsv3_diskless.swap_nblks = size / 1024; /* :296 β signed division */
- swap file with
size mod 2^32in[2^31, 2^32)(e.g. a 2β4 GiB swap file) βsizenegative βswap_nblksnegative. - swap file β₯ 4 GiB β size silently wrong (mod 2^32): e.g. 8 GiB β 0 β swap silently disabled.
- NFSv2 path has the same signed-int landing (
:292, v2 size is a u32 word [5]): β₯ 2 GiB v2 swap file β negativeswap_nblks.
Downstream (nfs_mountroot, nfs_vfsops.c:666-701)
if (nd->swap_nblks) β a negative value is truthy β swap_nblks *= 2 (still negative) β
VTONFS(vp)->n_size = nd->swap_nblks * DEV_BSIZE β negative file size on the swap vnode β
swaponvp(td, vp, nd->swap_nblks) β the int sign-extends into swaponvp's u_quad_t nblks
(sys/vm/vm_swap.c) β swap accounting believes ~2^64 blocks exist on an NFS file. Corrupt swap geometry /
bogus vm_swap_size at best; boot-time failure at worst.
Threat
Trigger: a legitimate NFS server exporting a >2 GiB swap file (misconfig-sized or simply large), or β per DF-3205 β any LAN forger answering the boot LOOKUP (xid is deterministic, source unfiltered). Impact ceiling: boot-path DoS / swap-accounting corruption. Not runtime reachable post-boot.
Fix
Decode the full hyper (fxdr_hyper(&fattribs.v3[5]), xdr_subs.h:83) and bound-check before storing into the
int field (see fix.diff).
VERDICT β DF-3211
status: untested (boot-path-only; not runtime-testable on the audit guest) impact: dos (boot-path: corrupt/negative swap geometry, boot failure; forged-reply vector covered by DF-3205's ceiling) confidence: certain (arithmetic proof; downstream behavior described from source)
Proof chain (path:line)
sys/vfs/nfs/nfsproto.h:306-335β v3 fattr wire layout:fa_type[0], fa_mode[1], fa_nlink[2], fa_uid[3], fa_gid[4], nfsv3fa_size[5..6] (nfsuint64), β¦ β size is a 64-bit hyper spanning words 5-6.sys/vfs/nfs/nfs_mountrpc.c:283-286β reply fattr decoded intofattribs.v3[21], thensize = fxdr_unsigned(u_int32_t, fattribs.v3[6]);β only word [6] (low 32 bits) is read, high word [5] discarded; destinationint size(:227) is signed. * low word β [2^31, 2^32) βsize < 0(e.g. swap file of 3 GiB β low word 0xC0000000 β size = -1073741824). * file β₯ 4 GiB β wraps mod 2^32 (8 GiB β low word 0 β swap "disabled").sys/vfs/nfs/nfs_mountrpc.c:295-296βnfsv3_diskless.swap_nblks = size / 1024;βswap_nblksisint(sys/vfs/nfs/nfsdiskless.h:70) β negative KB for the 2β4 GiB window. v2 twin at :292:fxdr_unsigned(u_int32_t, fattribs.v2[5])into the same signedsize(v2 fattr size is u32 at word [5], nfsproto.h:314) β same negative landing for β₯2 GiB.- Consumer
sys/vfs/nfs/nfs_vfsops.c:666-701βif (nd->swap_nblks)(negative = true) βnd->swap_nblks *= 2(:669) βVTONFS(vp)->n_size = VTONFS(vp)->n_vattr.va_size = nd->swap_nblks * DEV_BSIZE;(:691-692) β negative size on a VREG swap vnode βswaponvp(td, vp, nd->swap_nblks)(:701) where the parameter isu_quad_t nblks(sys/vm/vm_swap.c) β sign extension to β2^64 blocks feeding the swap bitmap/accounting. sys/vfs/nfs/xdr_subs.h:83-84βfxdr_hyper(f)exists and is the intended decoder for words [5..6]; other consumers (e.g. nfs_subs.c attribute handling) use it for exactly this field.
Why untested
Executes only during nfs_mountroot boot with an NFS root + swap path (#if BOOTP||NFS_ROOT). Audit guest
(DragonFly 6.5-DEVELOPMENT #0, X86_64_GENERIC) boots hammer2 from disk; no runtime path reaches
md_lookup_swap. Trigger on a real diskless client requires either a >2 GiB server swap file (plausible
misconfig) or the DF-3205 forged-LOOKUP vector.
Impact assessment
Worst realistic: corrupt swap geometry at boot (negative vnode size, ~2^64-block swap claim) β boot failure / vm accounting corruption β boot-path DoS ceiling. No post-boot runtime reachability, no memory-safety primitive (values flow into sizes/counts, not pointers or copy lengths).
Recommended fix
Decode the full 64-bit size and range-check before storing (see fix.diff).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-3211/VERDICT.md steps 1-5; decisive lines: nfs_mountrpc.c:286 (v3[6] only, into signed int), :292 (v2 signed landing), :295-296 (swap_nblks = size/1024), nfsdiskless.h:70 (int swap_nblks), nfs_vfsops.c:691-692 (negative n_size), :701 + vm_swap.c swaponvp(u_quad_t) (sign extension), xdr_subs.h:83 (fxdr_hyper is the intended decoder)
PoC changes
none β seed not runnable (boot path); verdict is a wire-layout + signedness derivation cross-checked against nfsproto.h fattr layout and the fxdr_hyper macro
Verified recommended fix
decode fattr.size with fxdr_hyper(&fattribs.v3[5]) into u_quad_t and only accept sizes <= INT_MAX before storing into swap_nblks (fix.diff applies clean)
Verdict
md_lookup_swap reads only word [6] (low 32 bits) of the 64-bit NFSv3 fattr.size into a signed int (nfs_mountrpc.c:286; layout nfsproto.h:325: size spans words [5..6]) and divides it into the int swap_nblks (:296): a 2-4 GiB swap file yields a negative swap_nblks, >=4 GiB wraps mod 2^32 (8 GiB -> swap silently disabled); the v2 twin at :292 lands a >=2 GiB u32 size in the same signed int. nfs_mountroot then treats the negative count as truthy, doubles it, sets a negative n_size on the swap vnode, and passes it to swaponvp's u_quad_t parameter where it sign-extends to ~2^64 blocks (nfs_vfsops.c:666-701, vm_swap.c) β corrupt swap geometry/boot failure. Arithmetic is certain; not runtime-testable because the code runs only during an NFS-root boot and the audit guest boots hammer2 from disk.
No comments yet.