CVE-2022-2345: Use After Free (CWE-416)
value: Use After Free in GitHub repository vim/vim prior to 9.0.0046.
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-07-08T22:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/ex_cmds.c, src/regexp.c, src/testdir/test_regexp_latin.vim, src/version.c
Commit date: 2022-07-07 22:20:31 +0100
Commit message: patch 9.0.0047: using freed memory with recursive substitute

Problem:    Using freed memory with recursive substitute.
Solution:   Always make a copy for reg_prev_sub.

Diff: diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index eb3016fe571e..5253863c871f 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3992,11 +3992,20 @@ ex_substitute(exarg_T *eap)
 	if (sub == NULL)
 	    return;
 	sub_copy = sub;
     }
     else
-	sub = regtilde(sub, magic_isset());
+    {
+	char_u *newsub = regtilde(sub, magic_isset());
+
+	if (newsub != sub)
+	{
+	    // newsub was allocated, free it later.
+	    sub_copy = newsub;
+	    sub = newsub;
+	}
+    }
 
     /*
      * Check for a match on each line.
      */
     line2 = eap->line2;
diff --git a/src/regexp.c b/src/regexp.c
index 2cbe64eb89ca..f35a5e8002b1 100644
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -1764,15 +1764,15 @@ regtilde(char_u *source, int magic)
 	    if (has_mbyte)
 		p += (*mb_ptr2len)(p) - 1;
 	}
     }
 
+    // Store a copy of newsub  in reg_prev_sub.  It is always allocated,
+    // because recursive calls may make the returned string invalid.
     vim_free(reg_prev_sub);
-    if (newsub != source)	// newsub was allocated, just keep it
-	reg_prev_sub = newsub;
-    else			// no ~ found, need to save newsub
-	reg_prev_sub = vim_strsave(newsub);
+    reg_prev_sub = vim_strsave(newsub);
+
     return newsub;
 }
 
 #ifdef FEAT_EVAL
 static int can_f_submatch = FALSE;	// TRUE when submatch() can be used
diff --git a/src/testdir/test_regexp_latin.vim b/src/testdir/test_regexp_latin.vim
index 1fe4699d1064..dce6709ff779 100644
--- a/src/testdir/test_regexp_latin.vim
+++ b/src/testdir/test_regexp_latin.vim
@@ -1112,6 +1112,17 @@ func Test_using_two_engines_pattern()
   endfor
   call assert_equal(['xx', 'xx', 'xx'], getline(1, '$'))
   bwipe!
 endfunc
 
+func Test_recursive_substitute_expr()
+  new
+  func Repl()
+    s
+  endfunc
+  silent! s/\%')/~\=Repl()
+
+  bwipe!
+  delfunc Repl
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index de8e968f42f8..2d917a83175f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -733,10 +733,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    47,
 /**/
     46,
 /**/
     45,
 /**/

Vulnerable code: 	sub = regtilde(sub, magic_isset());
    if (newsub != source)	// newsub was allocated, just keep it
	reg_prev_sub = newsub;
    else			// no ~ found, need to save newsub
	reg_prev_sub = vim_strsave(newsub);
Fixed code:     {
	char_u *newsub = regtilde(sub, magic_isset());

	if (newsub != sub)
	{
	    // newsub was allocated, free it later.
	    sub_copy = newsub;
	    sub = newsub;
	}
    }
    // Store a copy of newsub  in reg_prev_sub.  It is always allocated,
    // because recursive calls may make the returned string invalid.
    reg_prev_sub = vim_strsave(newsub);

func Test_recursive_substitute_expr()
  new
  func Repl()
    s
  endfunc
  silent! s/\%')/~\=Repl()

  bwipe!
  delfunc Repl
endfunc

/**/
    47,