CVE-2023-48231: Use After Free (CWE-416)
value: Vim is an open source command line text editor. When closing a window, vim may try to access already freed window structure. Exploitation beyond crashing the application has not been shown to be viable. This issue has been addressed in commit `25aabc2b` which has been included in release version 9.0.2106. Users are advised to upgrade. There are no known workarounds for this vulnerability.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 4.3
Published: 2023-11-16T23:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/testdir/test_crash.vim, src/version.c, src/window.c
Commit date: 2023-11-16 22:04:00 +0100
Commit message: patch 9.0.2106: [security]: Use-after-free in win_close()

Problem:  [security]: Use-after-free in win_close()
Solution: Check window is valid, before accessing it

If the current window structure is no longer valid (because a previous
autocommand has already freed this window), fail and return before
attempting to set win->w_closing variable.

Add a test to trigger ASAN in CI

Signed-off-by: Christian Brabandt <cb@256bit.org>

Diff: diff --git a/src/testdir/crash/poc1 b/src/testdir/crash/poc1
new file mode 100644
index 000000000000..ec223f16b880
Binary files /dev/null and b/src/testdir/crash/poc1 differ
diff --git a/src/testdir/test_crash.vim b/src/testdir/test_crash.vim
index 5cd07e2a3f0b..b093b053c59d 100644
--- a/src/testdir/test_crash.vim
+++ b/src/testdir/test_crash.vim
@@ -108,10 +108,43 @@ func Test_crash1()
   bw!
 
   call delete('X_crash1_result.txt')
 endfunc
 
+func Test_crash1_2()
+  CheckNotBSD
+  CheckExecutable dash
+
+  " The following used to crash Vim
+  let opts = #{cmd: 'sh'}
+  let vim  = GetVimProg()
+  let result = 'X_crash1_1_result.txt'
+
+  let buf = RunVimInTerminal('sh', opts)
+
+  let file = 'crash/poc1'
+  let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
+  let args = printf(cmn_args, vim, file)
+  call term_sendkeys(buf, args ..
+    \ '  && echo "crash 1: [OK]" > '.. result .. "\<cr>")
+  call TermWait(buf, 150)
+
+  " clean up
+  exe buf .. "bw!"
+
+  exe "sp " .. result
+
+  let expected = [
+      \ 'crash 1: [OK]',
+      \ ]
+
+  call assert_equal(expected, getline(1, '$'))
+  bw!
+
+  call delete(result)
+endfunc
+
 func Test_crash2()
   " The following used to crash Vim
   let opts = #{wait_for_ruler: 0, rows: 20}
   let args = ' -u NONE -i NONE -n -e -s -S '
   let buf = RunVimInTerminal(args .. ' crash/vim_regsub_both', opts)
diff --git a/src/version.c b/src/version.c
index f9d1593c0dd5..ec021985f258 100644
--- a/src/version.c
+++ b/src/version.c
@@ -702,10 +702,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2106,
 /**/
     2105,
 /**/
     2104,
 /**/
diff --git a/src/window.c b/src/window.c
index f77ede330d30..55ce31c88643 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2680,10 +2680,12 @@ win_close(win_T *win, int free_buf)
 	if (wp->w_buffer != curbuf)
 	{
 	    reset_VIsual_and_resel();	// stop Visual mode
 
 	    other_buffer = TRUE;
+	    if (!win_valid(win))
+		return FAIL;
 	    win->w_closing = TRUE;
 	    apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf);
 	    if (!win_valid(win))
 		return FAIL;
 	    win->w_closing = FALSE;

Fixed code: func Test_crash1_2()
  CheckNotBSD
  CheckExecutable dash

  " The following used to crash Vim
  let opts = #{cmd: 'sh'}
  let vim  = GetVimProg()
  let result = 'X_crash1_1_result.txt'

  let buf = RunVimInTerminal('sh', opts)

  let file = 'crash/poc1'
  let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
  let args = printf(cmn_args, vim, file)
  call term_sendkeys(buf, args ..
    \ '  && echo "crash 1: [OK]" > '.. result .. "\<cr>")
  call TermWait(buf, 150)

  " clean up
  exe buf .. "bw!"

  exe "sp " .. result

  let expected = [
      \ 'crash 1: [OK]',
      \ ]

  call assert_equal(expected, getline(1, '$'))
  bw!

  call delete(result)
endfunc

/**/
    2106,
	    if (!win_valid(win))
		return FAIL;