⬢ DragonFlyBSD Kernel Audit
DF-4510 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/drm/include/linux/dma-fence.h
+++ b/sys/dev/drm/include/linux/dma-fence.h
@@ -97,9 +97,10 @@
 static inline struct dma_fence *
 dma_fence_get_rcu(struct dma_fence *fence)
 {
-	if (fence)
-		kref_get(&fence->refcount);
-	return fence;
+	/* DF-4510: must not resurrect a fence whose release already fired. */
+	if (fence && kref_get_unless_zero(&fence->refcount))
+		return fence;
+	return NULL;
 }
 
 static inline void
@@ -192,10 +193,23 @@
 	struct dma_fence *fence;
 	if (dfp == NULL)
 		return NULL;
-	fence = *dfp;
-	if (fence)
-		kref_get(&fence->refcount);
-	return fence;
+	/*
+	 * DF-4510: DragonFly's rcu_read_lock() is only preempt_disable(),
+	 * so the load of *dfp can race the last dma_fence_put() of the
+	 * pointed fence (e.g. drm_syncobj_replace_fence dropping the old
+	 * fence outside its lock).  With the deferred kfree_rcu free in
+	 * dma_fence_release()/dma_fence_free() the object stays mapped,
+	 * and kref_get_unless_zero + retry yields upstream semantics:
+	 * either a live reference or the pointer changed / went NULL.
+	 */
+	do {
+		fence = rcu_dereference(*dfp);
+		if (!fence)
+			return NULL;
+		if (kref_get_unless_zero(&fence->refcount))
+			return fence;
+		cpu_relax();
+	} while (1);
 }
 
 void dma_fence_free(struct dma_fence *fence);
--- a/sys/dev/drm/linux_fence.c
+++ b/sys/dev/drm/linux_fence.c
@@ -47,7 +47,12 @@
 	if (fence->ops && fence->ops->release)
 		fence->ops->release(fence);
 	else
-		kfree(fence);
+		/* DF-4510: lockless readers (drm_syncobj_fence_get and the
+		 * dma_fence_get_rcu family) may still sit between their
+		 * pointer load and their kref_get; defer the free through
+		 * the port's RCU delay-line (>= 1s) instead of freeing
+		 * synchronously. */
+		kfree_rcu(fence, rcu);
 }
 
 long
@@ -339,5 +344,7 @@
 void
 dma_fence_free(struct dma_fence *fence)
 {
-	kfree(fence);
+	/* DF-4510: same deferred free for drivers using dma_fence_free as
+	 * their ops->release. */
+	kfree_rcu(fence, rcu);
 }