CVE-2022-2946: Use After Free (CWE-416)
value: Use After Free in GitHub repository vim/vim prior to 9.0.0246.
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: 7.8
Published: 2022-08-23T17:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/tag.c, src/testdir/test_tagfunc.vim, src/version.c
Commit date: 2022-08-22 16:35:45 +0100
Commit message: patch 9.0.0246: using freed memory when 'tagfunc' deletes the buffer

Problem:    Using freed memory when 'tagfunc' deletes the buffer.
Solution:   Make a copy of the tag name.

Diff: diff --git a/src/tag.c b/src/tag.c
index 8a351cc0580a..02f0818fec55 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -279,10 +279,11 @@ do_tag(
     int		use_tagstack;
     int		skip_msg = FALSE;
     char_u	*buf_ffname = curbuf->b_ffname;	    // name to use for
 						    // priority computation
     int		use_tfu = 1;
+    char_u	*tofree = NULL;
 
     // remember the matches for the last used tag
     static int		num_matches = 0;
     static int		max_num_matches = 0;  // limit used for match search
     static char_u	**matches = NULL;
@@ -628,11 +629,16 @@ do_tag(
 
 	/*
 	 * When desired match not found yet, try to find it (and others).
 	 */
 	if (use_tagstack)
-	    name = tagstack[tagstackidx].tagname;
+	{
+	    // make a copy, the tagstack may change in 'tagfunc'
+	    name = vim_strsave(tagstack[tagstackidx].tagname);
+	    vim_free(tofree);
+	    tofree = name;
+	}
 #if defined(FEAT_QUICKFIX)
 	else if (g_do_tagpreview != 0)
 	    name = ptag_entry.tagname;
 #endif
 	else
@@ -920,10 +926,11 @@ end_do_tag:
     postponed_split = 0;	// don't split next time
 # ifdef FEAT_QUICKFIX
     g_do_tagpreview = 0;	// don't do tag preview next time
 # endif
 
+    vim_free(tofree);
 #ifdef FEAT_CSCOPE
     return jumped_to_tag;
 #else
     return FALSE;
 #endif
diff --git a/src/testdir/test_tagfunc.vim b/src/testdir/test_tagfunc.vim
index 05d8473cfb79..95826121cd84 100644
--- a/src/testdir/test_tagfunc.vim
+++ b/src/testdir/test_tagfunc.vim
@@ -387,6 +387,18 @@ func Test_tagfunc_callback()
   delfunc TagFunc2
   set tagfunc&
   %bw!
 endfunc
 
+func Test_tagfunc_wipes_buffer()
+  func g:Tag0unc0(t,f,o)
+   bwipe
+  endfunc
+  set tagfunc=g:Tag0unc0
+  new
+  cal assert_fails('tag 0', 'E987:')
+
+  delfunc g:Tag0unc0
+  set tagfunc=
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index ec381fef2dc7..ec1302df0454 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,10 +729,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    246,
 /**/
     245,
 /**/
     244,
 /**/

Vulnerable code: 	    name = tagstack[tagstackidx].tagname;
Fixed code:     char_u	*tofree = NULL;
	{
	    // make a copy, the tagstack may change in 'tagfunc'
	    name = vim_strsave(tagstack[tagstackidx].tagname);
	    vim_free(tofree);
	    tofree = name;
	}
    vim_free(tofree);
func Test_tagfunc_wipes_buffer()
  func g:Tag0unc0(t,f,o)
   bwipe
  endfunc
  set tagfunc=g:Tag0unc0
  new
  cal assert_fails('tag 0', 'E987:')

  delfunc g:Tag0unc0
  set tagfunc=
endfunc

/**/
    246,