From: zeertzjq Date: Sun, 18 Jan 2026 20:51:56 +0000 (+0000) Subject: patch 9.1.2093: heap-use-after-free when wiping buffer in TabClosedPre X-Git-Tag: v9.1.2093^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8fc7042b3da3939213bb3f4216dc581703a954dd;p=thirdparty%2Fvim.git 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: #19196 Signed-off-by: zeertzjq Signed-off-by: Christian Brabandt --- diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 645168e9a8..0a6e940d6b 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -6593,6 +6593,9 @@ tabpage_close_other(tabpage_T *tp, int forceit) int done = 0; win_T *wp; + if (window_layout_locked(CMD_SIZE)) + return; + trigger_tabclosedpre(tp, TRUE); // Limit to 1000 windows, autocommands may add a window while we close diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index a798355cbc..ad10af7c75 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -5177,6 +5177,7 @@ func Test_OptionSet_cmdheight() call test_setmouse(&lines - 2, 1) call feedkeys("\", 'xt') call assert_equal(2, &l:ch) + call feedkeys("\", 'xt') tabnew | resize +1 call assert_equal(1, &l:ch) @@ -5248,7 +5249,7 @@ func Test_WinScrolled_Resized_eiw() endfunc " Test that TabClosedPre and TabClosed are triggered when closing a tab. -func Test_autocmd_tabclosedpre() +func Test_autocmd_TabClosedPre() augroup testing au TabClosedPre * call add(g:tabpagenr_pre, t:testvar) au TabClosed * call add(g:tabpagenr_post, t:testvar) @@ -5314,7 +5315,7 @@ func Test_autocmd_tabclosedpre() call assert_equal([1, 2], g:tabpagenr_pre) call assert_equal([2, 3], g:tabpagenr_post) - func ClearAutomcdAndCreateTabs() + func ClearAutocmdAndCreateTabs() au! TabClosedPre bw! e Z @@ -5337,41 +5338,41 @@ func Test_autocmd_tabclosedpre() call CleanUpTestAuGroup() " Close tab in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabclose call assert_fails('tabclose', 'E1312:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabclose call assert_fails('tabclose 2', 'E1312:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabclose 1 call assert_fails('tabclose', 'E1312:') " Close other (all) tabs in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabonly call assert_fails('tabclose', 'E1312:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabonly call assert_fails('tabclose 2', 'E1312:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabclose 4 call assert_fails('tabclose 2', 'E1312:') " Open new tabs in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabnew D call assert_fails('tabclose', 'E1312:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabnew D call assert_fails('tabclose 1', 'E1312:') " Moving the tab page in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabmove 0 tabclose call assert_equal('1>Z2A3B', GetTabs()) - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabmove 0 tabclose 1 call assert_equal('1A2B3>C', GetTabs()) @@ -5379,11 +5380,11 @@ func Test_autocmd_tabclosedpre() call assert_equal('1>C', GetTabs()) " Switching tab page in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabnext | e Y tabclose call assert_equal('1Y2A3>B', GetTabs()) - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * tabnext | e Y tabclose 1 call assert_equal('1Y2B3>C', GetTabs()) @@ -5391,10 +5392,10 @@ func Test_autocmd_tabclosedpre() call assert_equal('1>Y', GetTabs()) " Create new windows in TabClosedPre autocmd - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * split | e X| vsplit | e Y | split | e Z call assert_fails('tabclose', 'E242:') - call ClearAutomcdAndCreateTabs() + call ClearAutocmdAndCreateTabs() au TabClosedPre * new X | new Y | new Z call assert_fails('tabclose 1', 'E242:') @@ -5429,6 +5430,94 @@ func Test_autocmd_tabclosedpre() only tabonly 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 test_setmouse(1, 2) + call feedkeys("\\", '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("\\", 'tx') + call test_setmouse(1, 2) + " Closing tab page inside TabClosedPre is not allowed. + call assert_fails('call feedkeys("\", "tx")', 'E1312:') + call feedkeys("\", 'tx') + + autocmd! TabClosedPre + call OpenTwoTabPages() + let g:autocmd_bufnrs = [] + autocmd TabClosedPre * let g:autocmd_bufnrs += [tabpagebuflist()] + call test_setmouse(1, 10) + call feedkeys("\\", '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("\\", 'tx') + call test_setmouse(1, 10) + " Closing tab page inside TabClosedPre is not allowed. + call assert_fails('call feedkeys("\", "tx")', 'E1312:') + call feedkeys("\", '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 func Test_eventignorewin_non_current() diff --git a/src/version.c b/src/version.c index fefbe783e2..a5a3146d9e 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 2093, /**/ 2092, /**/ diff --git a/src/window.c b/src/window.c index 500ec88caf..e34574bb08 100644 --- a/src/window.c +++ b/src/window.c @@ -124,7 +124,8 @@ frames_locked(void) /* * 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 - * error message. + * error message. When closing window(s) and the command isn't easy to know, + * passing CMD_SIZE will also work. */ int window_layout_locked(enum CMD_index cmd) @@ -2530,6 +2531,8 @@ close_windows( if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(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) == FAIL) // If closing the window fails give up, to avoid looping // forever. @@ -2551,6 +2554,8 @@ close_windows( if (wp->w_buffer == buf && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) { + if (window_layout_locked(CMD_SIZE)) + goto theend; // Only give one error message. win_close_othertab(wp, FALSE, tp); // Start all over, the tab page may be closed and @@ -2560,6 +2565,7 @@ close_windows( } } +theend: if (RedrawingDisabled > 0) --RedrawingDisabled; @@ -3420,6 +3426,10 @@ win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) tabpage_T *ptp = NULL; int free_tp = 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; // Get here with win->w_buffer == NULL when win_close() detects the tab // page changed. if (win_locked(win) || (win->w_buffer != NULL