⬢ DragonFlyBSD Kernel Audit
DF-5631 / fix.diff
← back to finding ↓ download raw
--- a/sys/platform/vkernel64/platform/kqueue.c
+++ b/sys/platform/vkernel64/platform/kqueue.c
@@ -55,10 +55,13 @@
 struct kqueue_info {
 	void (*func)(void *, struct intrframe *);
 	void *data;
 	int fd;
+	int inuse;			/* in-flight dispatch fence */
 };
 
 #include <errno.h>
+#include <string.h>
+
 static int KQueueFd = -1;
 
 /*
@@ -114,6 +117,7 @@ kqueue_add(int fd, void (*func)(void *, s
 	info = kmalloc(sizeof(*info), M_DEVBUF, M_ZERO|M_INTWAIT);
 	info->func = func;
 	info->data = data;
+	info->inuse = 0;
 	info->fd = fd;
 	EV_SET(&kev, fd, EVFILT_READ, EV_ADD|EV_ENABLE|EV_CLEAR, 0, 0, info);
 	if (kevent(KQueueFd, &kev, 1, NULL, 0, &ts) < 0)
@@ -157,17 +161,38 @@ kqueue_add_timer(void (*func)(void *, str
 void
 kqueue_del(struct kqueue_info *info)
 {
 	struct timespec ts = { 0, 0 };
 	struct kevent kev;
+	int save_errno;
 
 	KKASSERT(info->fd >= 0);
 	EV_SET(&kev, info->fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
-	if (kevent(KQueueFd, &kev, 1, NULL, 0, &ts) < 0)
-		panic("kevent: failed to delete descriptor %d", info->fd);
-	info->fd = -1;
-	kfree(info, M_DEVBUF);
+	if (kevent(KQueueFd, &kev, 1, NULL, 0, &ts) < 0) {
+		/*
+		 * ENOENT is expected and must not be fatal: the host
+		 * auto-detaches an EVFILT_READ knote when the watched fd's
+		 * file description is closed, so close(fd)-before-kqueue_del()
+		 * legitimately finds no knote.  Anything else is still fatal.
+		 */
+		save_errno = errno;
+		if (save_errno != ENOENT)
+			panic("kqueue_del: cannot delete descriptor %d: %s",
+			      info->fd, strerror(save_errno));
+	}
+	/*
+	 * Fence against a concurrent kqueue_intr() on another vCPU host
+	 * thread which already copied this event out (udata == info) and
+	 * has not dispatched yet.  info->fd = -1 makes the dispatcher skip
+	 * the call; the wait drains in-flight dispatches before the free.
+	 *
+	 * NOTE: a fully airtight cross-thread serialization needs a lock
+	 * shared with kqueue_intr(); keep this simple fence unless
+	 * kqueue_del gains multi-CPU callers.
+	 */
+	info->fd = -1;
+	while (info->inuse)
+		usleep(1000);
+	kfree(info, M_DEVBUF);
 }
 
 /*
@@ -187,6 +212,7 @@ kqueue_intr(struct intrframe *frame)
 		n = kevent(KQueueFd, NULL, 0, kevary, 8, &ts);
 		for (i = 0; i < n; ++i) {
 			struct kevent *kev = &kevary[i];
 			struct kqueue_info *info = (void *)kev->udata;
+			atomic_add_int(&info->inuse, 1);
 
-			info->func(info->data, frame);
+			if (info->fd != -1)
+				info->func(info->data, frame);
+			atomic_add_int(&info->inuse, -1);
 		}
 	} while (n == 8);
 }