DragonFlyBSD Kernel Audit
DF-2975 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/kern/uipc_accf.c new/sys/kern/uipc_accf.c
--- a/sys/kern/uipc_accf.c	2026-09-04 12:29:11.301125561 +0000
+++ b/sys/kern/uipc_accf.c	2026-09-04 12:29:00.685262638 +0000
@@ -43,10 +43,18 @@
 #include <sys/socketvar.h>
 #include <sys/queue.h>
 #include <sys/thread2.h>
+#include <sys/spinlock2.h>
 
 static SLIST_HEAD(, accept_filter) accept_filtlsthd =
 	SLIST_HEAD_INITIALIZER(&accept_filtlsthd);
 
+/*
+ * Registry lock: serializes add/del against lookups issued from
+ * setsockopt(SO_ACCEPTFILTER) on any CPU (previously unsynchronized).
+ */
+static struct spinlock accept_filt_spin =
+	SPINLOCK_INITIALIZER(accept_filt_spin, "accept_filt_spin");
+
 MALLOC_DEFINE(M_ACCF, "accf", "accept filter data");
 
 static int unloadable = 0;
@@ -66,21 +74,38 @@
 accept_filt_add(struct accept_filter *filt)
 {
 	struct accept_filter *p;
+	int reused = 0;
+	int error = 0;
 
-	SLIST_FOREACH(p, &accept_filtlsthd, accf_next)
-		if (strcmp(p->accf_name, filt->accf_name) == 0)  {
-			if (p->accf_callback != NULL) {
-				return (EEXIST);
-			} else {
-				p->accf_callback = filt->accf_callback;
-				kfree(filt, M_ACCF);
-				return (0);
-			}
+	spin_lock(&accept_filt_spin);
+	SLIST_FOREACH(p, &accept_filtlsthd, accf_next) {
+		if (strcmp(p->accf_name, filt->accf_name) == 0)
+			break;
+	}
+	if (p != NULL) {
+		if (p->accf_callback != NULL || p->accf_refs > 0) {
+			error = EEXIST;
+		} else {
+			/*
+			 * Re-use the leaked entry, but refresh ALL
+			 * callbacks: previously accf_create/accf_destroy
+			 * kept pointing into the previously unloaded
+			 * module's text.
+			 */
+			p->accf_callback = filt->accf_callback;
+			p->accf_create = filt->accf_create;
+			p->accf_destroy = filt->accf_destroy;
+			reused = 1;
 		}
-				
-	if (p == NULL)
+	} else {
 		SLIST_INSERT_HEAD(&accept_filtlsthd, filt, accf_next);
-	return (0);
+	}
+	spin_unlock(&accept_filt_spin);
+
+	if (reused)
+		kfree(filt, M_ACCF);
+
+	return (error);
 }
 
 int
@@ -88,24 +113,60 @@
 {
 	struct accept_filter *p;
 
-	p = accept_filt_get(name);
-	if (p == NULL)
+	spin_lock(&accept_filt_spin);
+	SLIST_FOREACH(p, &accept_filtlsthd, accf_next) {
+		if (strcmp(p->accf_name, name) == 0)
+			break;
+	}
+	if (p == NULL) {
+		spin_unlock(&accept_filt_spin);
 		return (ENOENT);
+	}
+	if (p->accf_refs > 0) {
+		/* sockets are still attached to this filter */
+		spin_unlock(&accept_filt_spin);
+		return (EBUSY);
+	}
 
 	p->accf_callback = NULL;
+	p->accf_create = NULL;
+	p->accf_destroy = NULL;
+	spin_unlock(&accept_filt_spin);
 	return (0);
 }
 
+/*
+ * Look up a filter by name and take a reference on it.  The reference must
+ * be released with accept_filt_release() once the socket stops using the
+ * filter.  Holding a reference prevents the registering module from being
+ * unloaded underneath live sockets (previously no refcount was kept).
+ */
 struct accept_filter *
 accept_filt_get(char *name)
 {
 	struct accept_filter *p;
 
-	SLIST_FOREACH(p, &accept_filtlsthd, accf_next)
+	spin_lock(&accept_filt_spin);
+	SLIST_FOREACH(p, &accept_filtlsthd, accf_next) {
 		if (strcmp(p->accf_name, name) == 0)
-			return (p);
+			break;
+	}
+	if (p != NULL)
+		p->accf_refs++;
+	spin_unlock(&accept_filt_spin);
 
-	return (NULL);
+	return (p);
+}
+
+void
+accept_filt_release(struct accept_filter *filt)
+{
+	if (filt == NULL)
+		return;
+	spin_lock(&accept_filt_spin);
+	KKASSERT(filt->accf_refs > 0);
+	filt->accf_refs--;
+	spin_unlock(&accept_filt_spin);
 }
 
 int
@@ -117,7 +178,7 @@
 
 	switch (event) {
 	case MOD_LOAD:
-		p = kmalloc(sizeof(*p), M_ACCF, M_WAITOK);
+		p = kmalloc(sizeof(*p), M_ACCF, M_WAITOK | M_ZERO);
 		bcopy(accfp, p, sizeof(*p));
 		crit_enter();
 		error = accept_filt_add(p);
diff --git a/sys/kern/uipc_socket.c new/sys/kern/uipc_socket.c
--- a/sys/kern/uipc_socket.c	2026-09-04 12:29:11.301125561 +0000
+++ b/sys/kern/uipc_socket.c	2026-09-04 12:29:00.693262535 +0000
@@ -2008,9 +2008,11 @@
 	/* removing the filter */
 	if (sopt == NULL) {
 		if (af != NULL) {
-			if (af->so_accept_filter != NULL &&
-				af->so_accept_filter->accf_destroy != NULL) {
-				af->so_accept_filter->accf_destroy(so);
+			if (af->so_accept_filter != NULL) {
+				if (af->so_accept_filter->accf_destroy != NULL) {
+					af->so_accept_filter->accf_destroy(so);
+				}
+				accept_filt_release(af->so_accept_filter);
 			}
 			if (af->so_accept_filter_str != NULL) {
 				kfree(af->so_accept_filter_str, M_ACCF);
@@ -2035,7 +2037,9 @@
 	if (error)
 		goto out;
 	afp = accept_filt_get(afap->af_name);
-	if (afp == NULL) {
+	if (afp == NULL || afp->accf_callback == NULL) {
+		if (afp != NULL)
+			accept_filt_release(afp);
 		error = ENOENT;
 		goto out;
 	}
@@ -2050,6 +2054,7 @@
 		}
 		af->so_accept_filter_arg = afp->accf_create(so, afap->af_arg);
 		if (af->so_accept_filter_arg == NULL) {
+			accept_filt_release(afp);
 			kfree(af->so_accept_filter_str, M_ACCF);
 			kfree(af, M_ACCF);
 			so->so_accf = NULL;
diff --git a/sys/sys/socketvar.h new/sys/sys/socketvar.h
--- a/sys/sys/socketvar.h	2026-09-04 12:29:11.301125561 +0000
+++ b/sys/sys/socketvar.h	2026-09-04 12:29:00.693262535 +0000
@@ -369,6 +369,7 @@
 		(struct socket *so, char *arg);
 	void	(*accf_destroy)
 		(struct socket *so);
+	int	accf_refs;			/* sockets attached to filter */
 	SLIST_ENTRY(accept_filter) accf_next;	/* next on the list */
 };
 
@@ -494,6 +495,7 @@
 int	accept_filt_add (struct accept_filter *filt);
 int	accept_filt_del (char *name);
 struct accept_filter *	accept_filt_get (char *name);
+void	accept_filt_release (struct accept_filter *filt);
 #ifdef ACCEPT_FILTER_MOD
 int accept_filt_generic_mod_event (module_t mod, int event, void *data);
 SYSCTL_DECL(_net_inet_accf);