⬢ DragonFlyBSD Kernel Audit
DF-5015 / fix.diff
← back to finding ↓ download raw
diff -ruN a/sys/dev/misc/syscons/schistory.c b/sys/dev/misc/syscons/schistory.c
--- a/sys/dev/misc/syscons/schistory.c	2026-09-26 01:28:52.525406118 +0000
+++ b/sys/dev/misc/syscons/schistory.c	2026-09-26 01:28:52.541405910 +0000
@@ -136,26 +136,47 @@
 
 	if (ok) {
 		/*
-		 * Install new buffer, destroy previous buffer
+		 * Install new buffer, destroy previous buffer.
+		 *
+		 * Install and destroy under syscons_lock: the console
+		 * scroll path (sc_term_gen_scroll -> sc_hist_save_one_line)
+		 * reads scp->history and appends to the ring while holding
+		 * syscons_lock but NOT vga_token, so freeing the old ring
+		 * without syscons_lock races it into a use-after-free.
 		 */
+		syscons_lock();
 		if (prev_history != NULL) {
 			extra_history_size += delta;
 			sc_vtb_destroy(prev_history);
 			kfree(prev_history, M_SYSCONS);
 		}
 		scp->history = history;
+		syscons_unlock();
 	} else {
 		/*
-		 * Destroy partially initialized new buffer, keep
-		 * previous buffer.   Anything could have happened
-		 * in the mean time so reset the history position.
+		 * Destroy partially initialized new buffer, and do NOT
+		 * keep the previous buffer either: our caller (mode
+		 * change) may have updated scp->xsize/ysize after that
+		 * buffer was allocated, and a stale-geometry ring makes
+		 * sc_vtb_pos()/sc_vtb_seek()/sc_vtb_append() compute
+		 * negative indices -- an OOB read around the ring and an
+		 * OOB write of up to one screenful BEFORE it.  With
+		 * history == NULL, browsing and CONS_CLRHIST degrade to
+		 * no-ops instead.
 		 */
 		if (history) {
 			sc_vtb_destroy(history);
 			kfree(history, M_SYSCONS);
 		}
+		syscons_lock();
+		if (prev_history != NULL) {
+			extra_history_size += delta;	/* also fixes DF-2045 */
+			sc_vtb_destroy(prev_history);
+			kfree(prev_history, M_SYSCONS);
+		}
+		scp->history = NULL;
+		syscons_unlock();
 		scp->history_pos = 0;
-		scp->history = prev_history;
 	}
 	return 0;
 }
@@ -192,8 +213,10 @@
 	int cur_lines;				/* current buffer size */
 	int min_lines;				/* guaranteed buffer size */
 
+	syscons_lock();
 	history = scp->history;
 	scp->history = NULL;
+	syscons_unlock();
 	if (history == NULL)
 		return;
 
@@ -333,6 +356,8 @@
 
 	case CONS_CLRHIST:
 		scp = SC_STAT(tp->t_dev);
+		if (scp->history == NULL)
+			return 0;
 		sc_vtb_clear(scp->history, scp->sc->scr_map[0x20],
 		    SC_NORM_ATTR << 8);
 		return 0;
diff -ruN a/sys/dev/misc/syscons/syscons.c b/sys/dev/misc/syscons/syscons.c
--- a/sys/dev/misc/syscons/syscons.c	2026-09-26 01:28:52.525406118 +0000
+++ b/sys/dev/misc/syscons/syscons.c	2026-09-26 01:28:52.553405753 +0000
@@ -240,7 +240,7 @@
  * occurs we try for up to 1/2 a second and then blow the spinlock away.
  * The asynchronous update thread makes this happen more often.
  */
-static void
+void
 syscons_lock(void)
 {
 	if (panicstr || shutdown_in_progress) {
@@ -266,7 +266,7 @@
 	return(mtx_spinlock_try(&syscons_mtx));
 }
 
-static void
+void
 syscons_unlock(void)
 {
 	mtx_spinunlock(&syscons_mtx);
diff -ruN a/sys/dev/misc/syscons/syscons.h b/sys/dev/misc/syscons/syscons.h
--- a/sys/dev/misc/syscons/syscons.h	2026-09-26 01:28:52.525406118 +0000
+++ b/sys/dev/misc/syscons/syscons.h	2026-09-26 01:28:52.561405650 +0000
@@ -532,6 +532,10 @@
 void		sc_set_cursor_image(scr_stat *scp);
 int		sc_clean_up(scr_stat *scp, int need_unlock);
 int		sc_switch_scr(sc_softc_t *sc, u_int next_scr);
+
+/* syscons.c */
+void		syscons_lock(void);
+void		syscons_unlock(void);
 void		sc_alloc_scr_buffer(scr_stat *scp, int wait, int discard);
 int		sc_init_emulator(scr_stat *scp, char *name);
 void		sc_paste(scr_stat *scp, u_char *p, int count);