⬢ DragonFlyBSD Kernel Audit
DF-5836 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/hammer2/hammer2_inode.c
+++ b/sys/vfs/hammer2/hammer2_inode.c
@@ -1154,9 +1154,31 @@
 	 * inode.
 	 */
 	nip = hammer2_inode_get(dip->pmp, NULL, inum, -1);
+	/*
+	 * hammer2_inode_get() returns the EXISTING inode when `inum` is
+	 * already present in the pmp inum hash.  That must never happen
+	 * here: the inum was just allocated from pmp->inode_tid, whose
+	 * seed (iroot meta.pfs_inum) comes from untrusted on-media data
+	 * and can collide with an existing live inode on a crafted (or
+	 * crash-inconsistent) image.  The old KKASSERT compiled out on
+	 * production kernels and create_normal then stomped the live
+	 * inode's meta-data (type/uid/mode/nlinks/DIRECTDATA), flagged a
+	 * fully-linked inode CREATING, and re-inserted a duplicate chain
+	 * into the media topology at the next filesystem sync.
+	 *
+	 * Dispose of the found inode with hammer2_inode_unlock() only:
+	 * its single reference is the one taken by the inum-hash lookup
+	 * inside hammer2_inode_get(), and unlock() drops it.
+	 */
+	if ((nip->flags & HAMMER2_INODE_CREATING) != 0 ||
+	    nip->cluster.nchains != 0) {
+		kprintf("hammer2: duplicate inode number %ld detected, "
+			"create refused\n", (long)inum);
+		hammer2_inode_unlock(nip);
+		*errorp = HAMMER2_ERROR_EEXIST;
+		return NULL;
+	}
 	nip->comp_heuristic = 0;
-	KKASSERT((nip->flags & HAMMER2_INODE_CREATING) == 0 &&
-		 nip->cluster.nchains == 0);
 	atomic_set_int(&nip->flags, HAMMER2_INODE_CREATING);
 
 	/*