From 0a6e57b09bc8c76691b367a5babfb79b31b770e8 Mon Sep 17 00:00:00 2001
From: Christian Brabandt <cb@256bit.org>
Date: Thu, 15 Aug 2024 22:15:28 +0200
Subject: [PATCH] patch 9.1.0678: [security]: use-after-free in alist_add()
https://github.com/vim/vim/commit/0a6e57b09bc8c76691b367a5babfb79b31b770e8

Problem:  [security]: use-after-free in alist_add()
          (SuyueGuo)
Solution: Lock the current window, so that the reference to
          the argument list remains valid.

This fixes CVE-2024-43374

Signed-off-by: Christian Brabandt <cb@256bit.org>
---
 src/arglist.c                |  6 ++++++
 src/buffer.c                 |  4 ++--
 src/ex_cmds.c                |  4 ++--
 src/proto/window.pro         |  1 +
 src/structs.h                |  2 +-
 src/terminal.c               |  4 ++--
 src/testdir/test_arglist.vim | 23 +++++++++++++++++++++++
 src/version.c                |  2 ++
 src/window.c                 | 29 +++++++++++++++++++----------
 9 files changed, 58 insertions(+), 17 deletions(-)

diff --git a/src/arglist.c b/src/arglist.c
index a63f6c7..050f96f 100644
--- a/src/arglist.c
+++ b/src/arglist.c
@@ -184,6 +184,8 @@ alist_set(
 /*
  * Add file "fname" to argument list "al".
  * "fname" must have been allocated and "al" must have been checked for room.
+ *
+ * May trigger Buf* autocommands
  */
     void
 alist_add(
@@ -196,6 +198,7 @@ alist_add(
     if (check_arglist_locked() == FAIL)
 	return;
     arglist_locked = TRUE;
+    curwin->w_locked = TRUE;
 
 #ifdef BACKSLASH_IN_FILENAME
     slash_adjust(fname);
@@ -207,6 +210,7 @@ alist_add(
     ++al->al_ga.ga_len;
 
     arglist_locked = FALSE;
+    curwin->w_locked = FALSE;
 }
 
 #if defined(BACKSLASH_IN_FILENAME) || defined(PROTO)
@@ -365,6 +369,7 @@ alist_add_list(
 	    mch_memmove(&(ARGLIST[after + count]), &(ARGLIST[after]),
 				       (ARGCOUNT - after) * sizeof(aentry_T));
 	arglist_locked = TRUE;
+	curwin->w_locked = TRUE;
 	for (i = 0; i < count; ++i)
 	{
 	    int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
@@ -373,6 +378,7 @@ alist_add_list(
 	    ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
 	}
 	arglist_locked = FALSE;
+	curwin->w_locked = FALSE;
 	ALIST(curwin)->al_ga.ga_len += count;
 	if (old_argcount > 0 && curwin->w_arg_idx >= after)
 	    curwin->w_arg_idx += count;
diff --git a/src/buffer.c b/src/buffer.c
index ccd095b..260d22e 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -1456,7 +1456,7 @@ do_buffer_ext(
 	// (unless it's the only window).  Repeat this so long as we end up in
 	// a window with this buffer.
 	while (buf == curbuf
-		   && !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
+		   && !(win_locked(curwin) || curwin->w_buffer->b_locked > 0)
 		   && (!ONE_WINDOW || first_tabpage->tp_next != NULL))
 	{
 	    if (win_close(curwin, FALSE) == FAIL)
@@ -5443,7 +5443,7 @@ ex_buffer_all(exarg_T *eap)
 			    : wp->w_width != Columns)
 			|| (had_tab > 0 && wp != firstwin))
 		    && !ONE_WINDOW
-		    && !(wp->w_closing || wp->w_buffer->b_locked > 0)
+		    && !(win_locked(wp) || wp->w_buffer->b_locked > 0)
 		    && !win_unlisted(wp))
 	    {
 		if (win_close(wp, FALSE) == FAIL)
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index a08682b..46c4503 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -2827,7 +2827,7 @@ do_ecmd(
 
 		// Set the w_closing flag to avoid that autocommands close the
 		// window.  And set b_locked for the same reason.
-		the_curwin->w_closing = TRUE;
+		the_curwin->w_locked = TRUE;
 		++buf->b_locked;
 
 		if (curbuf == old_curbuf.br_buf)
@@ -2841,7 +2841,7 @@ do_ecmd(
 
 		// Autocommands may have closed the window.
 		if (win_valid(the_curwin))
-		    the_curwin->w_closing = FALSE;
+		    the_curwin->w_locked = FALSE;
 		--buf->b_locked;
 
 #ifdef FEAT_EVAL
diff --git a/src/proto/window.pro b/src/proto/window.pro
index cfb771d..12edf0b 100644
--- a/src/proto/window.pro
+++ b/src/proto/window.pro
@@ -93,4 +93,5 @@ int win_hasvertsplit(void);
 int get_win_number(win_T *wp, win_T *first_win);
 int get_tab_number(tabpage_T *tp);
 char *check_colorcolumn(win_T *wp);
+int win_locked(win_T *wp);
 /* vim: set ft=c : */
diff --git a/src/structs.h b/src/structs.h
index 6d9dcbb..1f4742b 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3740,7 +3740,7 @@ struct window_S
     synblock_T	*w_s;		    // for :ownsyntax
 #endif
 
-    int		w_closing;	    // window is being closed, don't let
+    int		w_locked;	    // window is being closed, don't let
 				    // autocommands close it too.
 
     frame_T	*w_frame;	    // frame containing this window
diff --git a/src/terminal.c b/src/terminal.c
index f79d102..37cd0f2 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -3669,10 +3669,10 @@ term_after_channel_closed(term_T *term)
 		if (is_aucmd_win(curwin))
 		    do_set_w_closing = TRUE;
 		if (do_set_w_closing)
-		    curwin->w_closing = TRUE;
+		    curwin->w_locked = TRUE;
 		do_bufdel(DOBUF_WIPE, (char_u *)"", 1, fnum, fnum, FALSE);
 		if (do_set_w_closing)
-		    curwin->w_closing = FALSE;
+		    curwin->w_locked = FALSE;
 		aucmd_restbuf(&aco);
 	    }
 #ifdef FEAT_PROP_POPUP
diff --git a/src/testdir/test_arglist.vim b/src/testdir/test_arglist.vim
index edc8b77..8d81a82 100644
--- a/src/testdir/test_arglist.vim
+++ b/src/testdir/test_arglist.vim
@@ -359,6 +359,7 @@ func Test_argv()
   call assert_equal('', argv(1, 100))
   call assert_equal([], argv(-1, 100))
   call assert_equal('', argv(10, -1))
+  %argdelete
 endfunc
 
 " Test for the :argedit command
@@ -744,4 +745,26 @@ func Test_all_command()
   %bw!
 endfunc
 
+" Test for deleting buffer when creating an arglist. This was accessing freed
+" memory
+func Test_crash_arglist_uaf()
+  "%argdelete
+  new one
+  au BufAdd XUAFlocal :bw
+  "call assert_fails(':arglocal XUAFlocal', 'E163:')
+  arglocal XUAFlocal
+  au! BufAdd
+  bw! XUAFlocal
+
+  au BufAdd XUAFlocal2 :bw
+  new two
+  new three
+  arglocal
+  argadd XUAFlocal2 Xfoobar
+  bw! XUAFlocal2
+  bw! two
+
+  au! BufAdd
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 555ef9f..10916ed 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    678,
 /**/
     2092,
 /**/
diff --git a/src/window.c b/src/window.c
index 2afa28d..e2a7393 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2441,7 +2441,7 @@ close_windows(
     for (wp = firstwin; wp != NULL && !ONE_WINDOW; )
     {
 	if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
-		&& !(wp->w_closing || wp->w_buffer->b_locked > 0))
+		&& !(win_locked(wp) || wp->w_buffer->b_locked > 0))
 	{
 	    if (win_close(wp, FALSE) == FAIL)
 		// If closing the window fails give up, to avoid looping
@@ -2462,7 +2462,7 @@ close_windows(
 	if (tp != curtab)
 	    FOR_ALL_WINDOWS_IN_TAB(tp, wp)
 		if (wp->w_buffer == buf
-		    && !(wp->w_closing || wp->w_buffer->b_locked > 0))
+		    && !(win_locked(wp) || wp->w_buffer->b_locked > 0))
 		{
 		    win_close_othertab(wp, FALSE, tp);
 
@@ -2584,10 +2584,10 @@ win_close_buffer(win_T *win, int action, int abort_if_last)
 	bufref_T    bufref;
 
 	set_bufref(&bufref, curbuf);
-	win->w_closing = TRUE;
+	win->w_locked = TRUE;
 	close_buffer(win, win->w_buffer, action, abort_if_last, TRUE);
 	if (win_valid_any_tab(win))
-	    win->w_closing = FALSE;
+	    win->w_locked = FALSE;
 	// Make sure curbuf is valid. It can become invalid if 'bufhidden' is
 	// "wipe".
 	if (!bufref_valid(&bufref))
@@ -2635,7 +2635,7 @@ win_close(win_T *win, int free_buf)
     if (window_layout_locked(CMD_close))
 	return FAIL;
 
-    if (win->w_closing || (win->w_buffer != NULL
+    if (win_locked(win) || (win->w_buffer != NULL
 					       && win->w_buffer->b_locked > 0))
 	return FAIL; // window is already being closed
     if (win_unlisted(win))
@@ -2684,19 +2684,19 @@ win_close(win_T *win, int free_buf)
 	    other_buffer = TRUE;
 	    if (!win_valid(win))
 		return FAIL;
-	    win->w_closing = TRUE;
+	    win->w_locked = TRUE;
 	    apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf);
 	    if (!win_valid(win))
 		return FAIL;
-	    win->w_closing = FALSE;
+	    win->w_locked = FALSE;
 	    if (last_window())
 		return FAIL;
 	}
-	win->w_closing = TRUE;
+	win->w_locked = TRUE;
 	apply_autocmds(EVENT_WINLEAVE, NULL, NULL, FALSE, curbuf);
 	if (!win_valid(win))
 	    return FAIL;
-	win->w_closing = FALSE;
+	win->w_locked = FALSE;
 	if (last_window())
 	    return FAIL;
 #ifdef FEAT_EVAL
@@ -3269,7 +3269,7 @@ win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
 
     // Get here with win->w_buffer == NULL when win_close() detects the tab
     // page changed.
-    if (win->w_closing || (win->w_buffer != NULL
+    if (win_locked(win) || (win->w_buffer != NULL
 					       && win->w_buffer->b_locked > 0))
 	return; // window is already being closed
 
@@ -7792,3 +7792,12 @@ skip:
     return NULL;  // no error
 }
 #endif
+
+/*
+ * Don't let autocommands close the given window
+ */
+   int
+win_locked(win_T *wp)
+{
+    return wp->w_locked;
+}
-- 
2.43.0

