mirror of
https://github.com/neovim/neovim
synced 2025-07-17 09:41:46 +00:00
vim-patch:9.1.1361: [security]: possible use-after-free when closing a buffer (#33820)
Problem: [security]: Possible to open more windows into a closing buffer without splitting, bypassing existing "b_locked_split" checks and triggering use-after-free Solution: Disallow switching to a closing buffer. Editing a closing buffer (via ":edit", etc.) was fixed in v9.1.0764, but add an error message and check just "b_locked_split", as "b_locked" is necessary only when the buffer shouldn't be wiped, and may be set for buffers that are in-use but not actually closing. (Sean Dewar) closes: vim/vim#172466cb1c82840
(cherry picked from commit627c648252
)
This commit is contained in:
committed by
github-actions[bot]
parent
bdd8498ed7
commit
9965cfb84c
@ -480,11 +480,6 @@ static bool can_unload_buffer(buf_T *buf)
|
|||||||
return can_unload;
|
return can_unload;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool buf_locked(buf_T *buf)
|
|
||||||
{
|
|
||||||
return buf->b_locked || buf->b_locked_split;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Close the link to a buffer.
|
/// Close the link to a buffer.
|
||||||
///
|
///
|
||||||
/// @param win If not NULL, set b_last_cursor.
|
/// @param win If not NULL, set b_last_cursor.
|
||||||
@ -1300,12 +1295,18 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags)
|
|||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == DOBUF_GOTO
|
if (action == DOBUF_GOTO && buf != curbuf) {
|
||||||
&& buf != curbuf
|
if (!check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) != 0)) {
|
||||||
&& !check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) ? true : false)) {
|
|
||||||
// disallow navigating to another buffer when 'winfixbuf' is applied
|
// disallow navigating to another buffer when 'winfixbuf' is applied
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
if (buf->b_locked_split) {
|
||||||
|
// disallow navigating to a closing buffer, which like splitting,
|
||||||
|
// can result in more windows displaying it
|
||||||
|
emsg(_(e_cannot_switch_to_a_closing_buffer));
|
||||||
|
return FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) {
|
if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) {
|
||||||
// disallow navigating to the dummy buffer
|
// disallow navigating to the dummy buffer
|
||||||
|
@ -366,7 +366,7 @@ struct file_buffer {
|
|||||||
int b_locked; // Buffer is being closed or referenced, don't
|
int b_locked; // Buffer is being closed or referenced, don't
|
||||||
// let autocommands wipe it out.
|
// let autocommands wipe it out.
|
||||||
int b_locked_split; // Buffer is being closed, don't allow opening
|
int b_locked_split; // Buffer is being closed, don't allow opening
|
||||||
// a new window with it.
|
// it in more windows.
|
||||||
int b_ro_locked; // Non-zero when the buffer can't be changed.
|
int b_ro_locked; // Non-zero when the buffer can't be changed.
|
||||||
// Used for FileChangedRO
|
// Used for FileChangedRO
|
||||||
|
|
||||||
|
@ -188,6 +188,7 @@ INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")
|
|||||||
EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
|
EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
|
||||||
INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
|
INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
|
||||||
EXTERN const char e_invalid_return_type_from_findfunc[] INIT( = N_("E1514: 'findfunc' did not return a List type"));
|
EXTERN const char e_invalid_return_type_from_findfunc[] INIT( = N_("E1514: 'findfunc' did not return a List type"));
|
||||||
|
EXTERN const char e_cannot_switch_to_a_closing_buffer[] INIT( = N_("E1546: Cannot switch to a closing buffer"));
|
||||||
|
|
||||||
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
|
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
|
||||||
|
|
||||||
|
@ -2270,14 +2270,16 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
|
|||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
// autocommands try to edit a file that is going to be removed, abort
|
// autocommands try to edit a closing buffer, which like splitting, can
|
||||||
if (buf_locked(buf)) {
|
// result in more windows displaying it; abort
|
||||||
|
if (buf->b_locked_split) {
|
||||||
// window was split, but not editing the new buffer, reset b_nwindows again
|
// window was split, but not editing the new buffer, reset b_nwindows again
|
||||||
if (oldwin == NULL
|
if (oldwin == NULL
|
||||||
&& curwin->w_buffer != NULL
|
&& curwin->w_buffer != NULL
|
||||||
&& curwin->w_buffer->b_nwindows > 1) {
|
&& curwin->w_buffer->b_nwindows > 1) {
|
||||||
curwin->w_buffer->b_nwindows--;
|
curwin->w_buffer->b_nwindows--;
|
||||||
}
|
}
|
||||||
|
emsg(_(e_cannot_switch_to_a_closing_buffer));
|
||||||
goto theend;
|
goto theend;
|
||||||
}
|
}
|
||||||
if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) {
|
if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) {
|
||||||
|
@ -1769,10 +1769,10 @@ describe('API/win', function()
|
|||||||
pcall_err(api.nvim_win_close, w, true)
|
pcall_err(api.nvim_win_close, w, true)
|
||||||
)
|
)
|
||||||
|
|
||||||
-- OK when using window to different buffer than `win`s.
|
-- OK when using a buffer that isn't closing.
|
||||||
w = api.nvim_get_current_win()
|
w = api.nvim_get_current_win()
|
||||||
command(
|
command(
|
||||||
'only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: '
|
'only | autocmd BufHidden * ++once call nvim_open_win(bufnr("#"), 0, #{split: "left", win: '
|
||||||
.. w
|
.. w
|
||||||
.. '})'
|
.. '})'
|
||||||
)
|
)
|
||||||
|
@ -4174,7 +4174,8 @@ func Test_autocmd_BufWinLeave_with_vsp()
|
|||||||
exe "e " fname
|
exe "e " fname
|
||||||
vsp
|
vsp
|
||||||
augroup testing
|
augroup testing
|
||||||
exe "au BufWinLeave " .. fname .. " :e " dummy .. "| vsp " .. fname
|
exe 'au BufWinLeave' fname 'e' dummy
|
||||||
|
\ '| call assert_fails(''vsp' fname ''', ''E1546:'')'
|
||||||
augroup END
|
augroup END
|
||||||
bw
|
bw
|
||||||
call CleanUpTestAuGroup()
|
call CleanUpTestAuGroup()
|
||||||
|
@ -563,4 +563,39 @@ func Test_buflist_alloc_failure()
|
|||||||
call assert_fails('cexpr "XallocFail6:10:Line10"', 'E342:')
|
call assert_fails('cexpr "XallocFail6:10:Line10"', 'E342:')
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_closed_buffer_still_in_window()
|
||||||
|
%bw!
|
||||||
|
|
||||||
|
let s:w = win_getid()
|
||||||
|
new
|
||||||
|
let s:b = bufnr()
|
||||||
|
setl bufhidden=wipe
|
||||||
|
|
||||||
|
augroup ViewClosedBuffer
|
||||||
|
autocmd!
|
||||||
|
autocmd BufUnload * ++once call assert_fails(
|
||||||
|
\ 'call win_execute(s:w, "' .. s:b .. 'b")', 'E1546:')
|
||||||
|
augroup END
|
||||||
|
quit!
|
||||||
|
" Previously resulted in s:b being curbuf while unloaded (no memfile).
|
||||||
|
call assert_equal(1, bufloaded(bufnr()))
|
||||||
|
call assert_equal(0, bufexists(s:b))
|
||||||
|
|
||||||
|
let s:w = win_getid()
|
||||||
|
split
|
||||||
|
new
|
||||||
|
let s:b = bufnr()
|
||||||
|
|
||||||
|
augroup ViewClosedBuffer
|
||||||
|
autocmd!
|
||||||
|
autocmd BufWipeout * ++once call win_gotoid(s:w)
|
||||||
|
\| call assert_fails(s:b .. 'b', 'E1546:') | wincmd p
|
||||||
|
augroup END
|
||||||
|
bw! " Close only this buffer first; used to be a heap UAF.
|
||||||
|
|
||||||
|
unlet! s:w s:b
|
||||||
|
autocmd! ViewClosedBuffer
|
||||||
|
%bw!
|
||||||
|
endfunc
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user