mirror of
https://github.com/neovim/neovim.git
synced 2026-04-22 07:15:34 +00:00
vim-patch:9.1.2093: heap-use-after-free when wiping buffer in TabClosedPre
Problem: heap-use-after-free when wiping buffer in TabClosedPre.
Solution: Check window_layout_locked() when closing window(s) in another
tabpage (zeertzjq).
closes: vim/vim#19196
8fc7042b3d
This commit is contained in:
@@ -5174,6 +5174,10 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
|
|||||||
int done = 0;
|
int done = 0;
|
||||||
char prev_idx[NUMBUFLEN];
|
char prev_idx[NUMBUFLEN];
|
||||||
|
|
||||||
|
if (window_layout_locked(CMD_SIZE)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
trigger_tabclosedpre(tp, true);
|
trigger_tabclosedpre(tp, true);
|
||||||
|
|
||||||
// Limit to 1000 windows, autocommands may add a window while we close
|
// Limit to 1000 windows, autocommands may add a window while we close
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ bool frames_locked(void)
|
|||||||
|
|
||||||
/// When the window layout cannot be changed give an error and return true.
|
/// When the window layout cannot be changed give an error and return true.
|
||||||
/// "cmd" indicates the action being performed and is used to pick the relevant
|
/// "cmd" indicates the action being performed and is used to pick the relevant
|
||||||
/// error message.
|
/// error message. When closing window(s) and the command isn't easy to know,
|
||||||
|
/// passing CMD_SIZE will also work.
|
||||||
bool window_layout_locked(cmdidx_T cmd)
|
bool window_layout_locked(cmdidx_T cmd)
|
||||||
{
|
{
|
||||||
if (split_disallowed > 0 || close_disallowed > 0) {
|
if (split_disallowed > 0 || close_disallowed > 0) {
|
||||||
@@ -2569,6 +2570,9 @@ void close_windows(buf_T *buf, bool keep_curwin)
|
|||||||
for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp, NULL));) {
|
for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp, NULL));) {
|
||||||
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
|
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
|
||||||
&& !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
|
&& !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
|
||||||
|
if (window_layout_locked(CMD_SIZE)) {
|
||||||
|
goto theend; // Only give one error message.
|
||||||
|
}
|
||||||
if (win_close(wp, false, false) == FAIL) {
|
if (win_close(wp, false, false) == FAIL) {
|
||||||
// If closing the window fails give up, to avoid looping forever.
|
// If closing the window fails give up, to avoid looping forever.
|
||||||
break;
|
break;
|
||||||
@@ -2591,6 +2595,9 @@ void close_windows(buf_T *buf, bool keep_curwin)
|
|||||||
for (win_T *wp = tp->tp_lastwin; wp != NULL; wp = wp->w_prev) {
|
for (win_T *wp = tp->tp_lastwin; wp != NULL; wp = wp->w_prev) {
|
||||||
if (wp->w_buffer == buf
|
if (wp->w_buffer == buf
|
||||||
&& !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
|
&& !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
|
||||||
|
if (window_layout_locked(CMD_SIZE)) {
|
||||||
|
goto theend; // Only give one error message.
|
||||||
|
}
|
||||||
if (!win_close_othertab(wp, false, tp, false)) {
|
if (!win_close_othertab(wp, false, tp, false)) {
|
||||||
// If closing the window fails give up, to avoid looping forever.
|
// If closing the window fails give up, to avoid looping forever.
|
||||||
break;
|
break;
|
||||||
@@ -2605,6 +2612,7 @@ void close_windows(buf_T *buf, bool keep_curwin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
theend:
|
||||||
RedrawingDisabled--;
|
RedrawingDisabled--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3156,6 +3164,11 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
|
|||||||
assert(tp != curtab);
|
assert(tp != curtab);
|
||||||
bool did_decrement = false;
|
bool did_decrement = false;
|
||||||
|
|
||||||
|
// Commands that may call win_close_othertab() already check this, but
|
||||||
|
// check here again just in case.
|
||||||
|
if (window_layout_locked(CMD_SIZE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// Get here with win->w_buffer == NULL when win_close() detects the tab page
|
// Get here with win->w_buffer == NULL when win_close() detects the tab page
|
||||||
// changed.
|
// changed.
|
||||||
if (win_locked(win)
|
if (win_locked(win)
|
||||||
|
|||||||
@@ -4566,6 +4566,7 @@ func Test_OptionSet_cmdheight()
|
|||||||
call Ntest_setmouse(&lines - 2, 1)
|
call Ntest_setmouse(&lines - 2, 1)
|
||||||
call feedkeys("\<LeftDrag>", 'xt')
|
call feedkeys("\<LeftDrag>", 'xt')
|
||||||
call assert_equal(2, &l:ch)
|
call assert_equal(2, &l:ch)
|
||||||
|
call feedkeys("\<LeftRelease>", 'xt')
|
||||||
|
|
||||||
tabnew | resize +1
|
tabnew | resize +1
|
||||||
call assert_equal(1, &l:ch)
|
call assert_equal(1, &l:ch)
|
||||||
@@ -4637,7 +4638,7 @@ func Test_WinScrolled_Resized_eiw()
|
|||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
" Test that TabClosedPre and TabClosed are triggered when closing a tab.
|
" Test that TabClosedPre and TabClosed are triggered when closing a tab.
|
||||||
func Test_autocmd_tabclosedpre()
|
func Test_autocmd_TabClosedPre()
|
||||||
augroup testing
|
augroup testing
|
||||||
au TabClosedPre * call add(g:tabpagenr_pre, t:testvar)
|
au TabClosedPre * call add(g:tabpagenr_pre, t:testvar)
|
||||||
au TabClosed * call add(g:tabpagenr_post, t:testvar)
|
au TabClosed * call add(g:tabpagenr_post, t:testvar)
|
||||||
@@ -4703,7 +4704,7 @@ func Test_autocmd_tabclosedpre()
|
|||||||
call assert_equal([1, 2], g:tabpagenr_pre)
|
call assert_equal([1, 2], g:tabpagenr_pre)
|
||||||
call assert_equal([2, 3], g:tabpagenr_post)
|
call assert_equal([2, 3], g:tabpagenr_post)
|
||||||
|
|
||||||
func ClearAutomcdAndCreateTabs()
|
func ClearAutocmdAndCreateTabs()
|
||||||
au! TabClosedPre
|
au! TabClosedPre
|
||||||
bw!
|
bw!
|
||||||
e Z
|
e Z
|
||||||
@@ -4727,41 +4728,41 @@ func Test_autocmd_tabclosedpre()
|
|||||||
call CleanUpTestAuGroup()
|
call CleanUpTestAuGroup()
|
||||||
|
|
||||||
" Close tab in TabClosedPre autocmd
|
" Close tab in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabclose
|
au TabClosedPre * tabclose
|
||||||
call assert_fails('tabclose', 'E1312:')
|
call assert_fails('tabclose', 'E1312:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabclose
|
au TabClosedPre * tabclose
|
||||||
call assert_fails('tabclose 2', 'E1312:')
|
call assert_fails('tabclose 2', 'E1312:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabclose 1
|
au TabClosedPre * tabclose 1
|
||||||
call assert_fails('tabclose', 'E1312:')
|
call assert_fails('tabclose', 'E1312:')
|
||||||
|
|
||||||
" Close other (all) tabs in TabClosedPre autocmd
|
" Close other (all) tabs in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabonly
|
au TabClosedPre * tabonly
|
||||||
call assert_fails('tabclose', 'E1312:')
|
call assert_fails('tabclose', 'E1312:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabonly
|
au TabClosedPre * tabonly
|
||||||
call assert_fails('tabclose 2', 'E1312:')
|
call assert_fails('tabclose 2', 'E1312:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabclose 4
|
au TabClosedPre * tabclose 4
|
||||||
call assert_fails('tabclose 2', 'E1312:')
|
call assert_fails('tabclose 2', 'E1312:')
|
||||||
|
|
||||||
" Open new tabs in TabClosedPre autocmd
|
" Open new tabs in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabnew D
|
au TabClosedPre * tabnew D
|
||||||
call assert_fails('tabclose', 'E1312:')
|
call assert_fails('tabclose', 'E1312:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabnew D
|
au TabClosedPre * tabnew D
|
||||||
call assert_fails('tabclose 1', 'E1312:')
|
call assert_fails('tabclose 1', 'E1312:')
|
||||||
|
|
||||||
" Moving the tab page in TabClosedPre autocmd
|
" Moving the tab page in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabmove 0
|
au TabClosedPre * tabmove 0
|
||||||
tabclose
|
tabclose
|
||||||
call assert_equal('1>Z2A3B', GetTabs())
|
call assert_equal('1>Z2A3B', GetTabs())
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabmove 0
|
au TabClosedPre * tabmove 0
|
||||||
tabclose 1
|
tabclose 1
|
||||||
call assert_equal('1A2B3>C', GetTabs())
|
call assert_equal('1A2B3>C', GetTabs())
|
||||||
@@ -4769,11 +4770,11 @@ func Test_autocmd_tabclosedpre()
|
|||||||
call assert_equal('1>C', GetTabs())
|
call assert_equal('1>C', GetTabs())
|
||||||
|
|
||||||
" Switching tab page in TabClosedPre autocmd
|
" Switching tab page in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabnext | e Y
|
au TabClosedPre * tabnext | e Y
|
||||||
tabclose
|
tabclose
|
||||||
call assert_equal('1Y2A3>B', GetTabs())
|
call assert_equal('1Y2A3>B', GetTabs())
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * tabnext | e Y
|
au TabClosedPre * tabnext | e Y
|
||||||
tabclose 1
|
tabclose 1
|
||||||
call assert_equal('1Y2B3>C', GetTabs())
|
call assert_equal('1Y2B3>C', GetTabs())
|
||||||
@@ -4781,10 +4782,10 @@ func Test_autocmd_tabclosedpre()
|
|||||||
call assert_equal('1>Y', GetTabs())
|
call assert_equal('1>Y', GetTabs())
|
||||||
|
|
||||||
" Create new windows in TabClosedPre autocmd
|
" Create new windows in TabClosedPre autocmd
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * split | e X| vsplit | e Y | split | e Z
|
au TabClosedPre * split | e X| vsplit | e Y | split | e Z
|
||||||
call assert_fails('tabclose', 'E242:')
|
call assert_fails('tabclose', 'E242:')
|
||||||
call ClearAutomcdAndCreateTabs()
|
call ClearAutocmdAndCreateTabs()
|
||||||
au TabClosedPre * new X | new Y | new Z
|
au TabClosedPre * new X | new Y | new Z
|
||||||
call assert_fails('tabclose 1', 'E242:')
|
call assert_fails('tabclose 1', 'E242:')
|
||||||
|
|
||||||
@@ -4819,6 +4820,94 @@ func Test_autocmd_tabclosedpre()
|
|||||||
only
|
only
|
||||||
tabonly
|
tabonly
|
||||||
bw!
|
bw!
|
||||||
|
delfunc ClearAutocmdAndCreateTabs
|
||||||
|
delfunc GetTabs
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
" This used to cause heap-use-after-free.
|
||||||
|
func Run_test_TabClosedPre_wipe_buffer(split_cmds)
|
||||||
|
file Xa
|
||||||
|
exe a:split_cmds
|
||||||
|
autocmd TabClosedPre * ++once tabnext | bwipe! Xa
|
||||||
|
" Closing window inside TabClosedPre is not allowed.
|
||||||
|
call assert_fails('tabonly', 'E1312:')
|
||||||
|
|
||||||
|
%bwipe!
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_TabClosedPre_wipe_buffer()
|
||||||
|
" Test with Xa only in other tab pages.
|
||||||
|
call Run_test_TabClosedPre_wipe_buffer('split | tab split | tabnew Xb')
|
||||||
|
" Test with Xa in both current and other tab pages.
|
||||||
|
call Run_test_TabClosedPre_wipe_buffer('split | tab split | new Xb')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
func Test_TabClosedPre_mouse()
|
||||||
|
func MyTabline()
|
||||||
|
let cnt = tabpagenr('$')
|
||||||
|
return range(1, cnt)->mapnew({_, n -> $'%{n}X|Close{n}|%X'})->join('')
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
let save_mouse = &mouse
|
||||||
|
if has('gui')
|
||||||
|
set guioptions-=e
|
||||||
|
endif
|
||||||
|
set mouse=a tabline=%!MyTabline()
|
||||||
|
|
||||||
|
func OpenTwoTabPages()
|
||||||
|
%bwipe!
|
||||||
|
file Xa | split | split
|
||||||
|
let g:Xa_bufnr = bufnr()
|
||||||
|
tabnew Xb | split
|
||||||
|
let g:Xb_bufnr = bufnr()
|
||||||
|
redraw!
|
||||||
|
call assert_match('^|Close1||Close2| *$', Screenline(1))
|
||||||
|
call assert_equal(2, tabpagenr('$'))
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
autocmd! TabClosedPre
|
||||||
|
call OpenTwoTabPages()
|
||||||
|
let g:autocmd_bufnrs = []
|
||||||
|
autocmd TabClosedPre * let g:autocmd_bufnrs += [tabpagebuflist()]
|
||||||
|
call Ntest_setmouse(1, 2)
|
||||||
|
call feedkeys("\<LeftMouse>\<LeftRelease>", 'tx')
|
||||||
|
call assert_equal(1, tabpagenr('$'))
|
||||||
|
call assert_equal([[g:Xa_bufnr]->repeat(3)], g:autocmd_bufnrs)
|
||||||
|
call assert_equal([g:Xb_bufnr]->repeat(2), tabpagebuflist())
|
||||||
|
|
||||||
|
call OpenTwoTabPages()
|
||||||
|
let g:autocmd_bufnrs = []
|
||||||
|
autocmd TabClosedPre * call feedkeys("\<LeftRelease>\<LeftMouse>", 'tx')
|
||||||
|
call Ntest_setmouse(1, 2)
|
||||||
|
" Closing tab page inside TabClosedPre is not allowed.
|
||||||
|
call assert_fails('call feedkeys("\<LeftMouse>", "tx")', 'E1312:')
|
||||||
|
call feedkeys("\<LeftRelease>", 'tx')
|
||||||
|
|
||||||
|
autocmd! TabClosedPre
|
||||||
|
call OpenTwoTabPages()
|
||||||
|
let g:autocmd_bufnrs = []
|
||||||
|
autocmd TabClosedPre * let g:autocmd_bufnrs += [tabpagebuflist()]
|
||||||
|
call Ntest_setmouse(1, 10)
|
||||||
|
call feedkeys("\<LeftMouse>\<LeftRelease>", 'tx')
|
||||||
|
call assert_equal(1, tabpagenr('$'))
|
||||||
|
call assert_equal([[g:Xb_bufnr]->repeat(2)], g:autocmd_bufnrs)
|
||||||
|
call assert_equal([g:Xa_bufnr]->repeat(3), tabpagebuflist())
|
||||||
|
|
||||||
|
call OpenTwoTabPages()
|
||||||
|
let g:autocmd_bufnrs = []
|
||||||
|
autocmd TabClosedPre * call feedkeys("\<LeftRelease>\<LeftMouse>", 'tx')
|
||||||
|
call Ntest_setmouse(1, 10)
|
||||||
|
" Closing tab page inside TabClosedPre is not allowed.
|
||||||
|
call assert_fails('call feedkeys("\<LeftMouse>", "tx")', 'E1312:')
|
||||||
|
call feedkeys("\<LeftRelease>", 'tx')
|
||||||
|
|
||||||
|
autocmd! TabClosedPre
|
||||||
|
%bwipe!
|
||||||
|
unlet g:Xa_bufnr g:Xb_bufnr g:autocmd_bufnrs
|
||||||
|
let &mouse = save_mouse
|
||||||
|
set tabline& guioptions&
|
||||||
|
delfunc MyTabline
|
||||||
|
delfunc OpenTwoTabPages
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_eventignorewin_non_current()
|
func Test_eventignorewin_non_current()
|
||||||
|
|||||||
Reference in New Issue
Block a user