⬢ DragonFlyBSD Kernel Audit
DF-3157 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/msdosfs/msdosfs_denode.c b/sys/vfs/msdosfs/msdosfs_denode.c
--- a/sys/vfs/msdosfs/msdosfs_denode.c	2026-09-07 09:04:26.755369941 +0000
+++ b/sys/vfs/msdosfs/msdosfs_denode.c	2026-09-07 09:04:26.763369843 +0000
@@ -305,6 +305,15 @@
 	 * Insert the denode into the hash queue.  If a collision occurs
 	 * throw away the vnode and try again.
 	 */
+	/*
+	 * The denode must become visible in the hash with its reference
+	 * already held. msdosfs_hashget() treats de_refcnt == 0 entries as
+	 * absent, so if this store happened after the insertion a concurrent
+	 * deget() on another CPU could insert a duplicate denode for the
+	 * same directory entry (verified: duplicate denodes / divergent file
+	 * state under parallel open()).
+	 */
+	ldep->de_refcnt = 1;
 	error = msdosfs_hashins(ldep);
 	if (error == EBUSY) {
 		nvp->v_type = VBAD;
@@ -320,7 +329,6 @@
 	}
 	nvp->v_data = ldep;
 	ldep->de_pmp = pmp;
-	ldep->de_refcnt = 1;
 	/*
 	 * Copy the directory entry into the denode area of the vnode.
 	 */
diff --git a/sys/vfs/msdosfs/msdosfs_vfsops.c b/sys/vfs/msdosfs/msdosfs_vfsops.c
--- a/sys/vfs/msdosfs/msdosfs_vfsops.c	2026-09-07 09:04:26.755369941 +0000
+++ b/sys/vfs/msdosfs/msdosfs_vfsops.c	2026-09-07 09:23:45.340807394 +0000
@@ -853,6 +853,45 @@
 	struct denode *dep;
 	int error;
 
+	/*
+	 * The file handle arrives from an untrusted RPC payload (or any
+	 * fhopen(2)/fhstat(2) caller). deget()->readep() locates the on-disk
+	 * direntry from these fields with no further validation:
+	 * bptoep() computes the entry pointer as bp->b_data +
+	 * (dirofs & pm_crbomask) while the block number calculation ignores
+	 * dirofs entirely, so out-of-range values produce out-of-bounds
+	 * accesses past kernel buffers (verified: fatal page fault in
+	 * DE_INTERNALIZE from a forged NFS GETATTR) and arbitrary
+	 * device-offset reads/writes via stat()/SETATTR (verified).
+	 * Reject anything that cannot name a directory entry that lies
+	 * entirely inside one buffer.
+	 */
+	if (fhp->fid_len != sizeof(struct defid))
+		return (EINVAL);
+	if (defhp->defid_dirclust == MSDOSFSROOT ||
+	    (FAT32(pmp) && defhp->defid_dirclust == pmp->pm_rootdirblk)) {
+		/* root directory */
+		if (defhp->defid_dirofs == MSDOSFSROOT_OFS)
+			goto valid;
+		if (FAT32(pmp))
+			return (EINVAL);	/* root is a single chain */
+		if (defhp->defid_dirofs >
+		    pmp->pm_rootdirsize * DEV_BSIZE - sizeof(struct direntry))
+			return (EINVAL);
+	} else {
+		/* ordinary cluster directory */
+		if (defhp->defid_dirclust < CLUST_FIRST ||
+		    defhp->defid_dirclust > pmp->pm_maxcluster)
+			return (EINVAL);
+		if (defhp->defid_dirofs >
+		    pmp->pm_bpcluster - sizeof(struct direntry))
+			return (EINVAL);
+	}
+	/* entry must not cross the end of the buffer it is read from */
+	if ((defhp->defid_dirofs & pmp->pm_crbomask) >
+	    pmp->pm_bpcluster - sizeof(struct direntry))
+		return (EINVAL);
+valid:
 	error = deget(pmp, defhp->defid_dirclust, defhp->defid_dirofs, &dep);
 	if (error) {
 		*vpp = NULLVP;