DragonFlyBSD Kernel Audit
DF-0847 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/ufs/ufs_quota.c b/sys/vfs/ufs/ufs_quota.c
index 0000000..1111111 100644
--- a/sys/vfs/ufs/ufs_quota.c
+++ b/sys/vfs/ufs/ufs_quota.c
@@ -44,6 +44,7 @@
 #include <sys/vnode.h>
 #include <sys/mount.h>
 #include <vm/vm_zone.h>
+#include <sys/thread.h>
 
 #include "quota.h"
 #include "inode.h"
@@ -732,6 +733,15 @@
 static long ufs_numdquot, ufs_desireddquot = DQUOTINC;
 
 /*
+ * Serializes all mutation of the global dquot hash table and free list.
+ * The token is held only across list/hash manipulation (lookup, recycle,
+ * insert, remove) and is NOT held across sleeping operations (kmalloc
+ * M_WAITOK, vn_lock, VOP_READ/VOP_WRITE). See ufs_dqget/ufs_dqrele/
+ * ufs_dqflush.
+ */
+static struct lwkt_token ufs_dq_token = LWKT_TOKEN_INITIALIZER(ufs_dq_token);
+
+/*
  * Initialize the quota system.
  */
 void
@@ -741,6 +751,7 @@
 
 	ufs_dqhashtbl = hashinit(hsize, M_DQUOT, &ufs_dqhash);
 	TAILQ_INIT(&ufs_dqfreelist);
+	lwkt_token_init(&ufs_dq_token, "ufsdqlist");
 }
 
 /*
@@ -765,8 +776,14 @@
 	}
 	/*
 	 * Check the cache first.
+	 *
+	 * The dquot token serializes all mutation of the global hash table and
+	 * free list.  It is held across the cache lookup and the miss-path
+	 * alloc/recycle + hash insert, but released around sleeping operations
+	 * (kmalloc M_WAITOK below, vn_lock + VOP_READ further down).
 	 */
 	dqh = DQHASH(dqvp, id);
+	lwkt_gettoken(&ufs_dq_token);
 	LIST_FOREACH(dq, dqh, dq_hash) {
 		if (dq->dq_id != id ||
 		    dq->dq_ump->um_quotas[dq->dq_type] != dqvp)
@@ -779,6 +796,7 @@
 			TAILQ_REMOVE(&ufs_dqfreelist, dq, dq_freelist);
 		DQREF(dq);
 		*dqp = dq;
+		lwkt_reltoken(&ufs_dq_token);
 		return (0);
 	}
 
@@ -790,13 +808,20 @@
 		ufs_desireddquot += DQUOTINC;
 	}
 	if (ufs_numdquot < ufs_desireddquot) {
+		/*
+		 * kmalloc(M_WAITOK) may sleep -> drop the token around it.
+		 * ufs_numdquot is bumped under the token after the allocation.
+		 */
+		lwkt_reltoken(&ufs_dq_token);
 		dq = (struct ufs_dquot *)
 			kmalloc(sizeof *dq, M_DQUOT, M_WAITOK | M_ZERO);
+		lwkt_gettoken(&ufs_dq_token);
 		ufs_numdquot++;
 	} else {
 		if ((dq = TAILQ_FIRST(&ufs_dqfreelist)) == NULL) {
 			tablefull("dquot");
 			*dqp = NODQUOT;
+			lwkt_reltoken(&ufs_dq_token);
 			return (EUSERS);
 		}
 		if (dq->dq_cnt || (dq->dq_flags & DQ_MOD))
@@ -806,16 +831,22 @@
 			LIST_REMOVE(dq, dq_hash);
 	}
 	/*
-	 * Initialize the contents of the dquot structure.
+	 * Initialize the contents of the dquot structure and publish it on
+	 * the hash chain while still holding the token.
 	 */
-	if (vp != dqvp)
-		vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY);
 	LIST_INSERT_HEAD(dqh, dq, dq_hash);
 	DQREF(dq);
 	dq->dq_flags = DQ_LOCK;
 	dq->dq_id = id;
 	dq->dq_ump = ump;
 	dq->dq_type = type;
+	lwkt_reltoken(&ufs_dq_token);
+
+	/*
+	 * Read the quota record.  Performed outside the token (may sleep).
+	 */
+	if (vp != dqvp)
+		vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY);
 	auio.uio_iov = &aiov;
 	auio.uio_iovcnt = 1;
 	aiov.iov_base = (caddr_t)&dq->dq_dqb;
@@ -838,7 +869,9 @@
 	 * quota structure and reflect problem to caller.
 	 */
 	if (error) {
+		lwkt_gettoken(&ufs_dq_token);
 		LIST_REMOVE(dq, dq_hash);
+		lwkt_reltoken(&ufs_dq_token);
 		ufs_dqrele(vp, dq);
 		*dqp = NODQUOT;
 		return (error);
@@ -887,7 +920,9 @@
 		(void)ufs_dqsync(vp, dq);
 	if (--dq->dq_cnt > 0)
 		return;
+	lwkt_gettoken(&ufs_dq_token);
 	TAILQ_INSERT_TAIL(&ufs_dqfreelist, dq, dq_freelist);
+	lwkt_reltoken(&ufs_dq_token);
 }
 
 /*
@@ -953,6 +988,7 @@
 	 * file off their hash chains (they will eventually
 	 * fall off the head of the free list and be re-used).
 	 */
+	lwkt_gettoken(&ufs_dq_token);
 	for (dqh = &ufs_dqhashtbl[ufs_dqhash]; dqh >= ufs_dqhashtbl; dqh--) {
 		for (dq = dqh->lh_first; dq; dq = nextdq) {
 			nextdq = dq->dq_hash.le_next;
@@ -964,4 +1000,5 @@
 			dq->dq_ump = NULL;
 		}
 	}
+	lwkt_reltoken(&ufs_dq_token);
 }