DragonFlyBSD Kernel Audit
DF-0764 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/ufs/ffs_softdep.c b/sys/vfs/ufs/ffs_softdep.c
--- a/sys/vfs/ufs/ffs_softdep.c
+++ b/sys/vfs/ufs/ffs_softdep.c
@@ -348,6 +348,12 @@
 static	void worklist_remove(struct worklist *);
 static	void workitem_free(struct worklist *, int);
 
+/*
+ * Cached tail of softdep_workitem_pending (declared below).  Kept here so the
+ * removal helper can invalidate it; it must be declared before worklist_remove.
+ */
+static	struct worklist *softdep_worklist_tail;
+
 #define WORKLIST_INSERT_BP(bp, item) do {	\
 	(bp)->b_ops = &softdep_bioops;		\
 	worklist_insert(&(bp)->b_dep, item);	\
@@ -379,6 +385,15 @@
 	
 	item->wk_state &= ~ONWORKLIST;
 	LIST_REMOVE(item, wk_list);
+	/*
+	 * If we are removing the cached tail of the pending worklist, drop
+	 * the cached pointer so that add_to_worklist() does not insert onto
+	 * a now-detached (and soon-to-be-freed) phantom chain.  The list is
+	 * a singly-linked LIST, so add_to_worklist() will rediscover the new
+	 * tail by walking from the head the next time it appends.
+	 */
+	if (item == softdep_worklist_tail)
+		softdep_worklist_tail = NULL;
 }
 
 static void
@@ -461,17 +476,39 @@
 static void
 add_to_worklist(struct worklist *wk)
 {
-	static struct worklist *worklist_tail;
 
 	if (wk->wk_state & ONWORKLIST) {
 		panic("add_to_worklist: already on list");
 	}
 	wk->wk_state |= ONWORKLIST;
-	if (LIST_FIRST(&softdep_workitem_pending) == NULL)
+	if (LIST_FIRST(&softdep_workitem_pending) == NULL) {
 		LIST_INSERT_HEAD(&softdep_workitem_pending, wk, wk_list);
-	else
-		LIST_INSERT_AFTER(worklist_tail, wk, wk_list);
-	worklist_tail = wk;
+		softdep_worklist_tail = wk;
+	} else {
+		/*
+		 * softdep_worklist_tail is cached to keep appends O(1),
+		 * but it may have gone stale: process_worklist_item() can
+		 * remove the tail item out-of-order (via the LK_NOWAIT scan
+		 * in request_cleanup), after which worklist_remove() clears
+		 * the cache.  When that happens we must rediscover the real
+		 * tail by walking from the head before appending, otherwise
+		 * we would insert onto a phantom chain unreachable from the
+		 * head -- leaking the item and inflating num_on_worklist,
+		 * which eventually makes softdep_flushfiles() panic with
+		 * "looping".
+		 */
+		if (softdep_worklist_tail == NULL ||
+		    (softdep_worklist_tail->wk_state & ONWORKLIST) == 0) {
+			softdep_worklist_tail =
+			    LIST_FIRST(&softdep_workitem_pending);
+			while (LIST_NEXT(softdep_worklist_tail, wk_list) != NULL)
+				softdep_worklist_tail =
+				    LIST_NEXT(softdep_worklist_tail, wk_list);
+		}
+		KKASSERT(softdep_worklist_tail != NULL);
+		LIST_INSERT_AFTER(softdep_worklist_tail, wk, wk_list);
+		softdep_worklist_tail = wk;
+	}
 	num_on_worklist += 1;
 }