DragonFlyBSD Kernel Audit
DF-0893 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/vfs/hammer/hammer_undo.c b/sys/vfs/hammer/hammer_undo.c
index 0000000..1111111 100644
--- a/sys/vfs/hammer/hammer_undo.c
+++ b/sys/vfs/hammer/hammer_undo.c
@@ -121,16 +121,27 @@
 	/*
 	 * Enter the offset into our undo history.  If there is an existing
 	 * undo we do not have to generate a new one.
+	 *
+	 * The undo history (rb_undo_root / undo_lru_list / undo_alloc) is
+	 * shared across all frontends and must be mutated under undo_lock.
+	 * Acquire the lock BEFORE hammer_enter_undo_history() and hold it
+	 * across the FIFO lay-down below (it is released at function exit).
+	 * Previously this lock was taken AFTER the history call, leaving the
+	 * RB tree, TAILQ LRU list and undo_alloc counter unprotected against
+	 * concurrent frontends -> KKASSERT(onode==NULL) panic on INVARIANTS
+	 * kernels and TAILQ corruption (CWE-787) otherwise. (DF-0893)
 	 */
-	if (hammer_enter_undo_history(hmp, zone_off, len) == EALREADY)
+	hammer_lock_ex(&hmp->undo_lock);
+	if (hammer_enter_undo_history(hmp, zone_off, len) == EALREADY) {
+		hammer_unlock(&hmp->undo_lock);
 		return(0);
+	}
 
 	root_volume = trans->rootvol;
 	undomap = &hmp->blockmap[HAMMER_ZONE_UNDO_INDEX];
 
 	/* no undo recursion */
 	hammer_modify_volume_noundo(NULL, root_volume);
-	hammer_lock_ex(&hmp->undo_lock);
 
 	/* undo had better not roll over (loose test) */
 	if (hammer_undo_space(trans) < len + HAMMER_BUFSIZE*3)