⬢ DragonFlyBSD Kernel Audit
DF-4297 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/misc/kbd/kbd.c
+++ b/sys/dev/misc/kbd/kbd.c
@@ -582,6 +582,7 @@
 kbd_detach(keyboard_t *kbd)
 {
 	cdev_t dev;
+	genkbd_softc_t dsc;
 
 	lwkt_gettoken(&kbd_token);
 
@@ -595,8 +596,23 @@
 	}
 
 	if ((dev = kbd->kb_dev) != NULL) {
-		if (dev->si_drv1) {
-			kfree(dev->si_drv1, M_DEVBUF);
+		if ((dsc = dev->si_drv1) != NULL) {
+			/*
+			 * DF-4297: an open /dev/kbdN may still hold this
+			 * genkbd_softc: genkbdread sleeps on it (see the
+			 * tsleep below) and kbd_allocate registered it as
+			 * kb_callback.kc_arg.  Release the callback first
+			 * so a later kbd_unregister cannot feed the stale
+			 * sc to genkbd_event (KNOTE on freed memory), then
+			 * wake blocked readers (they re-fetch dev->si_drv1
+			 * after wakeup and exit ENXIO once it is NULL), and
+			 * only then free the sc.
+			 */
+			if (KBD_IS_BUSY(kbd))
+				kbd_release(kbd, kbd->kb_token);
+			KNOTE(&dsc->gkb_rkq.ki_note, 0);
+			wakeup(dsc);
+			kfree(dsc, M_DEVBUF);
 			dev->si_drv1 = NULL;
 		}
 		kbd->kb_dev = NULL;
@@ -748,6 +764,11 @@
 		}
 		sc->gkb_flags |= KB_ASLEEP;
 		error = tsleep((caddr_t)sc, PCATCH, "kbdrea", 0);
+		sc = dev->si_drv1;	/* DF-4297: kbd_detach may free sc */
+		if (sc == NULL) {
+			lwkt_reltoken(&kbd_token);
+			return (ENXIO);
+		}
 		kbd = kbd_get_keyboard(KBD_INDEX(dev));
 		if ((kbd == NULL) || !KBD_IS_VALID(kbd)) {
 			lwkt_reltoken(&kbd_token);
@@ -759,6 +780,11 @@
 			return error;
 		}
 	}
+	sc = dev->si_drv1;	/* DF-4297: re-fetch under token */
+	if (sc == NULL) {
+		lwkt_reltoken(&kbd_token);
+		return (ENXIO);
+	}
 	lwkt_reltoken(&kbd_token);
 
 	/* copy as much input as possible */