DragonFlyBSD Kernel Audit
DF-0930 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/ntfs/ntfs_ihash.c b/sys/vfs/ntfs/ntfs_ihash.c
--- a/sys/vfs/ntfs/ntfs_ihash.c
+++ b/sys/vfs/ntfs/ntfs_ihash.c
@@ -44,6 +44,7 @@
 #include "ntfs.h"
 #include "ntfs_inode.h"
 #include "ntfs_ihash.h"
+#include "ntfs_subr.h"
 
 MALLOC_DEFINE(M_NTFSNTHASH, "NTFS nthash", "NTFS ntnode hash tables");
 
@@ -86,6 +87,12 @@
 /*
  * Use the device/inum pair to find the incore inode, and return a pointer
  * to it. If it is in core, return it, even if it is locked.
+ *
+ * WARNING: the returned ntnode has NO reference and NO lock held.  The
+ * hash token is released before the pointer is returned, so the ntnode
+ * may be concurrently freed by ntfs_ntput() on another CPU.  Callers
+ * that intend to dereference the pointer MUST use ntfs_nthashget()
+ * instead, which takes a reference under the token.
  */
 struct ntnode *
 ntfs_nthashlookup(cdev_t dev, ino_t inum)
@@ -103,6 +110,34 @@
 }
 
 /*
+ * Look up an ntnode and return it with i_usecount incremented and i_lock
+ * held exclusively.  The usecount reference and lock acquisition happen
+ * UNDER the hash token so the ntnode cannot be torn down between the
+ * lookup and the caller taking the reference.  Mirrors ext2_ihashget().
+ *
+ * If ntfs_ntget() blocks in LOCKMGR(&ip->i_lock), the lwkt token is
+ * yielded to other CPUs, but the already-incremented i_usecount keeps
+ * the ntnode alive until we resume and return.  Returns NULL if not
+ * present in the hash.
+ */
+struct ntnode *
+ntfs_nthashget(cdev_t dev, ino_t inum)
+{
+	struct ntnode *ip;
+
+	lwkt_gettoken(&ntfs_nthash_slock);
+	for (ip = NTNOHASH(dev, inum)->lh_first; ip; ip = ip->i_hash.le_next) {
+		if (inum == ip->i_number && dev == ip->i_dev) {
+			ntfs_ntget(ip);
+			break;
+		}
+	}
+	lwkt_reltoken(&ntfs_nthash_slock);
+
+	return (ip);
+}
+
+/*
  * Insert the ntnode into the hash table.
  */
 void
@@ -110,7 +145,20 @@
 {
 	struct nthashhead *ipp;
 
+	KKASSERT((ip->i_flag & IN_HASHED) == 0);
 	lwkt_gettoken(&ntfs_nthash_slock);
+#ifdef DIAGNOSTIC
+	{
+		struct ntnode *iq;
+
+		LIST_FOREACH(iq, NTNOHASH(ip->i_dev, ip->i_number), i_hash) {
+			if (iq->i_dev == ip->i_dev &&
+			    iq->i_number == ip->i_number)
+				panic("ntfs_nthashins: duplicate inode %ju",
+				      (uintmax_t)ip->i_number);
+		}
+	}
+#endif
 	ipp = NTNOHASH(ip->i_dev, ip->i_number);
 	LIST_INSERT_HEAD(ipp, ip, i_hash);
 	ip->i_flag |= IN_HASHED;
diff --git a/sys/vfs/ntfs/ntfs_subr.c b/sys/vfs/ntfs/ntfs_subr.c
--- a/sys/vfs/ntfs/ntfs_subr.c
+++ b/sys/vfs/ntfs/ntfs_subr.c
@@ -366,8 +366,13 @@
 	dprintf(("ntfs_ntlookup: looking for ntnode %ju\n", (uintmax_t)ino));
 
 	do {
-		if ((ip = ntfs_nthashlookup(ntmp->ntm_dev, ino)) != NULL) {
-			ntfs_ntget(ip);
+		/*
+		 * Use ntfs_nthashget() which takes a usecount reference and
+		 * acquires i_lock UNDER the hash token, so the ntnode cannot
+		 * be freed between the lookup and our taking the reference.
+		 * The returned ntnode is already locked (i_lock held excl).
+		 */
+		if ((ip = ntfs_nthashget(ntmp->ntm_dev, ino)) != NULL) {
 			dprintf(("ntfs_ntlookup: ntnode %ju: %p, usecount: %d\n",
 				(uintmax_t)ino, ip, ip->i_usecount));
 			*ipp = ip;