vim-patch:9.1.0143: [security]: autocmd causes use-after-free in set_curbuf() (#27664)

Problem:  [security]: autocmd cause use-after-free in set_curbuf()
          (kawarimidoll)
Solution: check side-effect of BufLeave autocommand, when the number
          of windows changed, close windows containing buffers that will
          be wiped, if curbuf changed unexpectedly make sure b_nwindows
          is decremented otherwise it cannot be wiped

set_curbuf() already makes some efforts to ensure the BufLeave
autocommands do not cause issues.  However there are still 2 issues
that are not taken care of:

1) If a BufLeave autocommand opens a new window containing the same
buffer as that is going got be closed in close_buffer() a bit later,
we suddenly have another window open, containing a free'd buffer.  So we
must check if the number of windows changed and if it does (and the
current buffer is going to be wiped (according to the 'bufhidden'
setting), let's immediately close all windows containing the current
buffer using close_windows()

2) If a BufLeave autocommand changes our current buffer (displays it in
the current window), buf->b_nwindow will be incremented. As part of
set_curbuf() we will however enter another buffer soon, which means, the
newly created curbuf will have b_nwindows still have set, even so the
buffer is no longer displayed in a window. This causes later problems,
because it will no longer be possible to wipe such a buffer. So just
before entering the final buffer, check if the curbuf changed when
calling the BufLeave autocommand and if it does (and curbuf is still
valid), decrement curbuf->b_nwindows.

Both issues can be verified using the provided test (however the second
issue only because such an impacted buffer won't be wiped, causing
futher issues in later tests).

fixes: vim/vim#13839
closes: vim/vim#14104

55f8bba73b

Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
zeertzjq 2024-02-29 08:33:02 +08:00 committed by GitHub
parent f9e7c4c9c4
commit 0eaae1bc05
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 30 additions and 3 deletions

View File

@ -1584,6 +1584,7 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
|| action == DOBUF_WIPE);
OptInt old_tw = curbuf->b_p_tw;
const int last_winid = get_last_winid();
if (update_jumplist) {
setpcmark();
@ -1612,7 +1613,11 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
if (prevbuf == curwin->w_buffer) {
reset_synblock(curwin);
}
if (unload) {
// autocommands may have opened a new window
// with prevbuf, grr
if (unload
|| (last_winid != get_last_winid()
&& strchr("wdu", prevbuf->b_p_bh[0]) != NULL)) {
close_windows(prevbuf, false);
}
if (bufref_valid(&prevbufref) && !aborting()) {
@ -1642,6 +1647,11 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
// If curwin->w_buffer is null, enter_buffer() will make it valid again
bool valid = buf_valid(buf);
if ((valid && buf != curbuf && !aborting()) || curwin->w_buffer == NULL) {
// autocommands changed curbuf and we will move to another
// buffer soon, so decrement curbuf->b_nwindows
if (curbuf != NULL && prevbuf != curbuf) {
curbuf->b_nwindows--;
}
// If the buffer is not valid but curwin->w_buffer is NULL we must
// enter some buffer. Using the last one is hopefully OK.
if (!valid) {

View File

@ -4946,12 +4946,12 @@ win_T *buf_jump_open_tab(buf_T *buf)
return NULL;
}
static int last_win_id = LOWEST_WIN_ID - 1;
/// @param hidden allocate a window structure and link it in the window if
// false.
win_T *win_alloc(win_T *after, bool hidden)
{
static int last_win_id = LOWEST_WIN_ID - 1;
// allocate window structure and linesizes arrays
win_T *new_wp = xcalloc(1, sizeof(win_T));
@ -7451,6 +7451,11 @@ skip:
return NULL; // no error
}
int get_last_winid(void)
{
return last_win_id;
}
void win_get_tabwin(handle_T id, int *tabnr, int *winnr)
{
*tabnr = 0;

View File

@ -3868,4 +3868,16 @@ func Test_autocmd_invalidates_undo_on_textchanged()
call StopVimInTerminal(buf)
endfunc
func Test_autocmd_creates_new_buffer_on_bufleave()
e a.txt
e b.txt
setlocal bufhidden=wipe
autocmd BufLeave <buffer> diffsplit c.txt
bn
call assert_equal(1, winnr('$'))
call assert_equal('a.txt', bufname('%'))
bw a.txt
bw c.txt
endfunc
" vim: shiftwidth=2 sts=2 expandtab