DF-5016 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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); |