DF-3157 / verdict.json
{ "finding_id": "DF-3157", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Stock INVARIANTS kernel: an unprivileged forged NFS file handle (unvalidated defid_dirclust/defid_dirofs through msdosfs_fhtovp -> deget -> readep/bptoep -> DE_INTERNALIZE/DE_EXTERNALIZE) yields (a) a deterministic fatal kernel page fault in deget from a single GETATTR (3/3, nfsd thread), (b) a silent 31-byte heap OOB read exfiltrated via stat() fields, (c) an arbitrary in-volume 32-byte read proven with a planted marker returned as st_size, (d) a persisted 32-byte structured write at a forged device offset with >=9 attacker-chosen bytes (utimes+chmod -> deupdat), and (e) an nfsd wedge from a forged SETATTR. Patched kernel (fix.diff: fh bounds validation) rejects every OOB/out-of-range probe with EINVAL, the panic and wedge are gone, and legitimate in-range handles still work; the residual in-range cluster-content disclosure via stat is inherent to untyped FAT clusters and matches upstream validation level. uid=0 escalation not pursued (buffer-cache heap grooming research project); panic + arbitrary-offset R/W + leak fully establish the memory-safety violation.", "exploit_chain": "NFS-exported msdosfs -> client learns fsid+fh format via mountd GETFH (standard client privilege) -> forged 28-byte fh with chosen (dirclust, dirofs) -> NFSPROC3_GETATTR -> deget/readep OOB or arbitrary-offset read -> stat()/fattr3 exfil (leak); NFSPROC3_SETATTR (utimes/chmod/truncate) -> msdosfs_setattr -> deupdat -> *dirp = dir 32-byte write at the same unbounded location (heap OOB write / arbitrary device-offset corruption); dirofs=511-family -> fatal page fault in DE_INTERNALIZE (panic), dirofs beyond buffer -> nfsd wedge. Local root-gated equivalents via fhstat(2)/fhopen(2).", "evidence": [ "findings/poc/DF-3157/run.log -- marker read st_size=1145258561 (0x44434241 ABCD), OOB read garbage (mtime=0x12CE0FC0), persisted 32-byte write into VICTIM.DAT cluster (raw-block hexdump, bytes 11/22-25 changed to exact FAT-encoded futimes value)", "findings/poc/DF-3157/panic.txt + panic_capture.txt -- Fatal trap 12 at deget+0x2b5 (inlined DE_INTERNALIZE, movq (%rax),%rcx), current process = nfsd server thread, 3/3 deterministic", "findings/poc/DF-3157/run.2.log -- patched-kernel matrix: (3,481)/(3,511)/(0,481)/(0,511)/(0,8192)/(4294967295,0) all EINVAL; NFS (0,32) clean ESTALE instead of panic; nfsd healthy after (0,511) SETATTR" ], "kernel_refs": [ "sys/vfs/msdosfs/msdosfs_vfsops.c:856", "sys/vfs/msdosfs/msdosfs_denode.c:363-377", "sys/vfs/msdosfs/msdosfs_denode.c:453-463", "sys/vfs/msdosfs/msdosfs_lookup.c:888-899", "sys/vfs/nfs/nfs_subs.c:1243" ], "poc_changes": "Seed sketch did not exist; written from scratch. Fixes vs initial draft: RPC reply parser (reply_stat/verf offsets), GETPORT null-cred verifier words, fh must be exactly NFSX_V3FH=28 with defid at offset 8 (nfsm_srvmtofh), mountd rejects unprivileged ports so fh is forged from the mount fsid (env-seeded locally; mountd-supplied remotely), status-first GETATTR reply parsing, bounds-checked XDR reads after a client segfault.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline) / #3: Mon Sep 7 09:32:17 UTC 2026 (patched)", "runtime_sec": 6900, "guest_dirty": 0, "build_cmd": "cc -O2 -o /tmp/df3157 df3157.c; cc -O2 -o /tmp/fhtest fhtest.c; (fix) cd /usr/src && make -j6 nativekernel && make installkernel", "run_cmd": "sh setup.sh; DF3157_SRCPORT=800 DF3157_FSID0=0x1e110807 DF3157_FSID1=3 /tmp/df3157 getattr 0 32 (panic); /tmp/fhtest 3 0; /tmp/fhtest 3 0 write", "code_hash": "b3cd52e94ba7154cbad2b001b5dab136cd7c411a94a8dbb76cd886ddcf99a238", "notes": "fsid seeding root-assisted locally because DF masks f_fsid for non-root statfs; network clients obtain it from mountd GETFH (the real remote path). nfsd-context ESTALE for cluster-mode keys observed pre- and post-fix, not root-caused (documented in VERDICT.md); the local syscall path proves all primitives and the nfsd path demonstrably reaches deget (panicked there). DF-3091 covers a different arbitrary-offset path (pcbmap with crafted on-disk chains); this finding needs no crafted image.", "recommended_fix": "Validate fh in msdosfs_fhtovp (fid_len, cluster range CLUST_FIRST..pm_maxcluster, FAT12/16 root-area bounds, and uniform (dirofs & crbomask) <= bpcluster-32) -- validated in fix.diff" } |