⬢ DragonFlyBSD Kernel Audit
DF-4542 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/misc/syscons/syscons.c
+++ b/sys/dev/misc/syscons/syscons.c
@@ -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);
@@ -3563,8 +3563,23 @@
     sc_vtb_t old;
 
     lwkt_gettoken(&vga_token);
-    old = scp->vtb;
     sc_vtb_init(&new, VTB_MEMORY, scp->xsize, scp->ysize, NULL, wait);
+    /*
+     * Do not replace the current buffer with an invalid one.
+     */
+    if (!(new.vtb_flags & VTB_VALID)) {
+	lwkt_reltoken(&vga_token);
+	return;
+    }
+    /*
+     * Serialize the swap (and content copy) against the tty output path
+     * (scstart()/sc_puts()) and the renderer, which run under
+     * syscons_lock(); vga_token alone does not exclude them and is
+     * released whenever this thread blocks.  Destroy the old buffer
+     * outside the spinlock.
+     */
+    syscons_lock();
+    old = scp->vtb;
     if (!discard && (old.vtb_flags & VTB_VALID)) {
 	/* retain the current cursor position and buffer contants */
 	scp->cursor_oldpos = scp->cursor_pos;
@@ -3574,8 +3589,10 @@
 	 */
 	sc_vtb_copy(&old, 0, &new, 0, scp->xsize*scp->ysize);
 	scp->vtb = new;
+	syscons_unlock();
     } else {
 	scp->vtb = new;
+	syscons_unlock();
 	sc_vtb_destroy(&old);
     }
 
--- a/sys/dev/misc/syscons/syscons.h
+++ b/sys/dev/misc/syscons/syscons.h
@@ -532,6 +532,8 @@
 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);
+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);
--- a/sys/dev/misc/syscons/schistory.c
+++ b/sys/dev/misc/syscons/schistory.c
@@ -90,9 +90,15 @@
 	/* make it at least as large as the screen size */
 	lines = imax(lines, scp->ysize);
 
-	/* remove the history buffer while we update it */
+	/*
+	 * Remove the history buffer while we update it.  Serialize against
+	 * the tty output path (scstart()/sc_puts() -> sc_hist_* writers),
+	 * which holds syscons_lock().
+	 */
+	syscons_lock();
 	history = prev_history = scp->history;
 	scp->history = NULL;
+	syscons_unlock();
 
 	/* calculate the amount of lines to put back to extra_history_size */
 	delta = 0;
@@ -136,14 +142,19 @@
 
 	if (ok) {
 		/*
-		 * Install new buffer, destroy previous buffer
+		 * Install the new buffer under syscons_lock() so a
+		 * concurrent sc_puts()/sc_hist_* writer cannot observe a
+		 * half-installed ring; free the old one outside the
+		 * spinlock.
 		 */
+		syscons_lock();
+		scp->history = history;
+		syscons_unlock();
 		if (prev_history != NULL) {
 			extra_history_size += delta;
 			sc_vtb_destroy(prev_history);
 			kfree(prev_history, M_SYSCONS);
 		}
-		scp->history = history;
 	} else {
 		/*
 		 * Destroy partially initialized new buffer, keep
@@ -154,8 +165,10 @@
 			sc_vtb_destroy(history);
 			kfree(history, M_SYSCONS);
 		}
+		syscons_lock();
 		scp->history_pos = 0;
 		scp->history = prev_history;
+		syscons_unlock();
 	}
 	return 0;
 }
--- a/sys/dev/misc/syscons/scvidctl.c
+++ b/sys/dev/misc/syscons/scvidctl.c
@@ -60,6 +60,8 @@
     int prev_ysize;
     int new_ysize;
     int error;
+    sc_vtb_t new_vtb;
+    sc_vtb_t old_vtb;
 
     lwkt_gettoken(&vga_token);
     if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, mode, &info)) {
@@ -126,19 +128,38 @@
 	return ENODEV;
     }
 
+    /*
+     * Allocate the new shadow screen buffer FIRST and WITHOUT SLEEPING:
+     * this function holds only vga_token, which LWKT drops whenever the
+     * thread blocks; a sleeping allocation here would let a second mode
+     * change (or the tty output path) run against a half-updated scp.
+     */
+    sc_vtb_init(&new_vtb, VTB_MEMORY, xsize, ysize, NULL, FALSE);
+    if (!(new_vtb.vtb_flags & VTB_VALID)) {
+	lwkt_reltoken(&vga_token);
+	return ENOMEM;
+    }
+
     /* set up scp */
     new_ysize = 0;
 #ifndef SC_NO_HISTORY
     if (scp->history != NULL) {
+	syscons_lock();
 	sc_hist_save(scp);
+	syscons_unlock();
 	new_ysize = sc_vtb_rows(scp->history); 
     }
 #endif
     prev_ysize = scp->ysize;
     /*
-     * This is a kludge to fend off scrn_update() while we
-     * muck around with scp. XXX
+     * Commit the mode change under syscons_lock() so the tty output
+     * path (scstart()/sc_puts()) and the renderer, which hold that
+     * lock, cannot observe scp mid-update; vga_token serializes
+     * concurrent mode changes (there is no sleeping past this point).
+     * The UNKNOWN_MODE kludge to fend off scrn_update() while we muck
+     * around with scp stays. XXX
      */
+    syscons_lock();
     scp->status |= UNKNOWN_MODE | MOUSE_HIDDEN;
     scp->status &= ~(GRAPHICS_MODE | PIXEL_MODE | MOUSE_VISIBLE);
     scp->mode = mode;
@@ -152,13 +173,14 @@
     scp->font = font;
     scp->font_height = fontsize;
     scp->font_width = 8;
-
-    /* allocate buffers */
-    sc_alloc_scr_buffer(scp, TRUE, TRUE);
+    old_vtb = scp->vtb;
+    scp->vtb = new_vtb;
     sc_init_emulator(scp, NULL);
 #ifndef SC_NO_CUTPASTE
     sc_alloc_cut_buffer(scp, FALSE);
 #endif
+    syscons_unlock();
+    sc_vtb_destroy(&old_vtb);	/* outside the spinlock */
 #ifndef SC_NO_HISTORY
     sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE);
 #endif
@@ -271,6 +293,8 @@
     int prev_ysize;
     int new_ysize;
     int error;
+    sc_vtb_t new_vtb;
+    sc_vtb_t old_vtb;
 
     lwkt_gettoken(&vga_token);
     if ((*vidsw[scp->sc->adapter]->get_info)(scp->sc->adp, scp->mode, &info)) {
@@ -367,15 +391,31 @@
     scp->ts = NULL;
 #endif
 
+    /*
+     * Allocate the new shadow screen buffer FIRST and WITHOUT SLEEPING;
+     * see sc_set_text_mode().  (sc_set_pixel_mode() is entered without
+     * vga_token held, so we take it for the commit.)
+     */
+    lwkt_gettoken(&vga_token);
+    sc_vtb_init(&new_vtb, VTB_MEMORY, xsize, ysize, NULL, FALSE);
+    if (!(new_vtb.vtb_flags & VTB_VALID)) {
+	lwkt_reltoken(&vga_token);
+	return ENOMEM;
+    }
+
     /* set up scp */
     new_ysize = 0;
 #ifndef SC_NO_HISTORY
     if (scp->history != NULL) {
+	syscons_lock();
 	sc_hist_save(scp);
+	syscons_unlock();
 	new_ysize = sc_vtb_rows(scp->history);
     }
 #endif
     prev_ysize = scp->ysize;
+    /* commit under syscons_lock(); no sleeping past this point */
+    syscons_lock();
     scp->status |= (UNKNOWN_MODE | PIXEL_MODE | MOUSE_HIDDEN);
     scp->status &= ~(GRAPHICS_MODE | MOUSE_VISIBLE);
     scp->model = info.vi_mem_model;
@@ -386,13 +426,15 @@
     scp->font = font;
     scp->font_height = fontsize;
     scp->font_width = 8;
-
-    /* allocate buffers */
-    sc_alloc_scr_buffer(scp, TRUE, TRUE);
+    old_vtb = scp->vtb;
+    scp->vtb = new_vtb;
     sc_init_emulator(scp, NULL);
 #ifndef SC_NO_CUTPASTE
     sc_alloc_cut_buffer(scp, FALSE);
 #endif
+    syscons_unlock();
+    sc_vtb_destroy(&old_vtb);	/* outside the spinlock */
+    lwkt_reltoken(&vga_token);
 #ifndef SC_NO_HISTORY
     sc_alloc_history_buffer(scp, new_ysize, prev_ysize, FALSE);
 #endif