DragonFlyBSD Kernel Audit
DF-2750 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/kern/kern_lock.c b/sys/kern/kern_lock.c
index dc8adfe..8fafff8 100644
--- a/sys/kern/kern_lock.c
+++ b/sys/kern/kern_lock.c
@@ -874,15 +874,27 @@ lockmgr_release(struct lock *lkp, u_int flags)
 			} else {
 				/*
 				 * Multiple exclusive counts, drop by 1.
-				 * Since we are the holder and there is more
-				 * than one count, we can just decrement it.
+				 *
+				 * Use a fcmpset loop instead of a blind
+				 * fetchadd.  A blind decrement can race a
+				 * concurrent release (lk_lockholder ==
+				 * LK_KERNTHREAD allows any cpu to release,
+				 * e.g. the buffer-cache biodone handoff)
+				 * and drive the exclusive count to zero
+				 * without handling the grant/wakeup/
+				 * EXREQ2/CANCEL cases that the single-count
+				 * releases above deal with, leaving blocked
+				 * requesters sleeping on a free lock
+				 * (lost wakeup).
 				 */
-				count =
-				    atomic_fetchadd_long(&lkp->lk_count, -1);
-				/* count = count - 1  NOT NEEDED */
-				if (lkp->lk_lockholder != LK_KERNTHREAD)
-					COUNT(td, -1);
-				break;
+				ncount = count - 1;
+				if (atomic_fcmpset_64(&lkp->lk_count,
+							      &count, ncount)) {
+					if (lkp->lk_lockholder != LK_KERNTHREAD)
+						COUNT(td, -1);
+					break;
+				}
+				/* fcmpset failed, count reloaded: retry */
 			}
 			/* retry */
 		} else {