CVE-2022-2817: Use After Free (CWE-416)
value: Use After Free in GitHub repository vim/vim prior to 9.0.0213.
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-15T23:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/testdir/test_assert.vim, src/testing.c, src/version.c
Commit date: 2022-08-14 22:23:02 +0100
Commit message: patch 9.0.0213: using freed memory with error in assert argument

Problem:    Using freed memory with error in assert argument.
Solution:   Make a copy of the error.

Diff: diff --git a/src/testdir/test_assert.vim b/src/testdir/test_assert.vim
index 27b2d73fbfc8..7c9d090b39df 100644
--- a/src/testdir/test_assert.vim
+++ b/src/testdir/test_assert.vim
@@ -289,10 +289,14 @@ func Test_assert_fail_fails()
     call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123))
   catch
     let exp = v:exception
   endtry
   call assert_match("E1174: String required for argument 5", exp)
+
+  call assert_equal(1, assert_fails('c0', ['', '\1']))
+  call assert_match("Expected '\\\\\\\\1' but got 'E939: Positive count required: c0': c0", v:errors[0])
+  call remove(v:errors, 0)
 endfunc
 
 func Test_assert_fails_in_try_block()
   try
     call assert_equal(0, assert_fails('throw "error"'))
diff --git a/src/testing.c b/src/testing.c
index f2355f5dac13..21eb9c18e6e2 100644
--- a/src/testing.c
+++ b/src/testing.c
@@ -595,10 +595,11 @@ f_assert_fails(typval_T *argvars, typval_T *rettv)
     char_u	*cmd;
     garray_T	ga;
     int		save_trylevel = trylevel;
     int		called_emsg_before = called_emsg;
     char	*wrong_arg_msg = NULL;
+    char_u	*tofree = NULL;
 
     if (check_for_string_or_number_arg(argvars, 0) == FAIL
 	    || check_for_opt_string_or_list_arg(argvars, 1) == FAIL
 	    || (argvars[1].v_type != VAR_UNKNOWN
 		&& (argvars[2].v_type != VAR_UNKNOWN
@@ -658,17 +659,21 @@ f_assert_fails(typval_T *argvars, typval_T *rettv)
 		error_found = TRUE;
 		expected_str = expected;
 	    }
 	    else if (list->lv_len == 2)
 	    {
-		tv = &list->lv_u.mat.lv_last->li_tv;
-		actual = get_vim_var_str(VV_ERRMSG);
-		expected = tv_get_string_buf_chk(tv, buf);
-		if (!pattern_match(expected, actual, FALSE))
+		// make a copy, an error in pattern_match() may free it
+		tofree = actual = vim_strsave(get_vim_var_str(VV_ERRMSG));
+		if (actual != NULL)
 		{
-		    error_found = TRUE;
-		    expected_str = expected;
+		    tv = &list->lv_u.mat.lv_last->li_tv;
+		    expected = tv_get_string_buf_chk(tv, buf);
+		    if (!pattern_match(expected, actual, FALSE))
+		    {
+			error_found = TRUE;
+			expected_str = expected;
+		    }
 		}
 	    }
 	}
 	else
 	{
@@ -747,10 +752,11 @@ theend:
     need_wait_return = FALSE;
     emsg_on_display = FALSE;
     msg_scrolled = 0;
     lines_left = Rows;
     VIM_CLEAR(emsg_assert_fails_msg);
+    vim_free(tofree);
     set_vim_var_string(VV_ERRMSG, NULL, 0);
     if (wrong_arg_msg != NULL)
 	emsg(_(wrong_arg_msg));
 }
 
diff --git a/src/version.c b/src/version.c
index 7de71581568e..ec63c24b06f8 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 */
+/**/
+    213,
 /**/
     212,
 /**/
     211,
 /**/

Vulnerable code: 		tv = &list->lv_u.mat.lv_last->li_tv;
		actual = get_vim_var_str(VV_ERRMSG);
		expected = tv_get_string_buf_chk(tv, buf);
		if (!pattern_match(expected, actual, FALSE))
		    error_found = TRUE;
		    expected_str = expected;
Fixed code: 
  call assert_equal(1, assert_fails('c0', ['', '\1']))
  call assert_match("Expected '\\\\\\\\1' but got 'E939: Positive count required: c0': c0", v:errors[0])
  call remove(v:errors, 0)
    char_u	*tofree = NULL;
		// make a copy, an error in pattern_match() may free it
		tofree = actual = vim_strsave(get_vim_var_str(VV_ERRMSG));
		if (actual != NULL)
		    tv = &list->lv_u.mat.lv_last->li_tv;
		    expected = tv_get_string_buf_chk(tv, buf);
		    if (!pattern_match(expected, actual, FALSE))
		    {
			error_found = TRUE;
			expected_str = expected;
		    }
    vim_free(tofree);
/**/
    213,