patch 9.1.2093: heap-use-after-free when wiping buffer in TabClosedPre

Commit: 
https://github.com/vim/vim/commit/8fc7042b3da3939213bb3f4216dc581703a954dd
Author: zeertzjq <[email protected]>
Date:   Sun Jan 18 20:51:56 2026 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 645168e9a..0a6e940d6 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 a798355cb..ad10af7c7 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("\<LeftDrag>", 'xt')
   call assert_equal(2, &l:ch)
+  call feedkeys("\<LeftRelease>", '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("\<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 test_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 test_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 test_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
 
 func Test_eventignorewin_non_current()
diff --git a/src/version.c b/src/version.c
index fefbe783e..a5a3146d9 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 500ec88ca..e34574bb0 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

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1vhZsT-0021Sh-VH%40256bit.org.

Raspunde prin e-mail lui