DragonFlyBSD Kernel Audit
DF-2941 / fix.diff
← back to finding ↓ download raw
# DF-2941 fix: put-side floor guard in _sysref_put
#
# A double-release of a fully-released sysref object currently falls into
# the `count > -0x40000000` branch (kern_sysref.c:329) and is silently
# accepted as a 0 -> -1 underflow.  The only existing barrier is the
# debug-only KKASSERT at kern_sysref.c:303, whose flags read races the
# refcnt-cmpset (:346) -> SRF_PUTAWAY-RMW (:348) pair in the final-release
# branch, so even INVARIANTS kernels accept a put that lands in that window
# (demonstrated: findings/poc/DF-2941/run.log, "B2: layer ACCEPTED
# double-release: refcnt=-1").
#
# This diff makes count==0 an explicit, loud terminal case: on INVARIANTS
# kernels the misuse panics at the point of the bug instead of corrupting
# the count; on production kernels the put is a no-op instead of an
# underflow.
#
# Authored after verification; NOT applied to the read-only sys/ tree.

--- a/sys/kern/kern_sysref.c
+++ b/sys/kern/kern_sysref.c
@@ -326,6 +326,16 @@ _sysref_put(struct sysref *sr)
 			sr->srclass->ops.unlock(data);
 		} else if (count > -0x40000000) {
 			/*
+			 * DF-2941: count == 0 must never reach the generic
+			 * decrement below.  A put on a fully-released object
+			 * is a double-release (caller bug): fail loudly on
+			 * INVARIANTS kernels, no-op on production builds.
+			 */
+			if (count == 0) {
+				KKASSERT(count != 0);
+				break;
+			}
+			/*
 			 * release 1 count, nominal case, resource undergoing
 			 * termination.  The Resource can be ref'd and
 			 * deref'd while undergoing termination.