CVE-2022-2889 (published 2022-08-19T13:15Z)

Use After Free in GitHub repository vim/vim prior to 9.0.0225.

CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-416
cwe_name: Use After Free
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.
extended_description: 
             
             
             
			 
             
             
         
         

Fix information:
repo_url: https://github.com/vim/vim
commit_details: repo_url: https://github.com/vim/vim
author: Bram Moolenaar
author_date: 2022-08-18 13:28:31+01:00
msg: patch 9.0.0225: using freed memory with multiple line breaks in expression

Problem:    Using freed memory with multiple line breaks in expression.
Solution:   Free eval_tofree later.
repository: repo_url: https://github.com/vim/vim
repo_name: vim/vim
description: The official Vim repository
repo_language: Vim Script
owner: vim
file_changes: filename: eval.c
change_type: ModificationType.MODIFY
diff: @@ -353,6 +353,63 @@ eval_to_string_skip(
     return retval;
 }
 
+/*
+ * Initialize "evalarg" for use.
+ */
+    void
+init_evalarg(evalarg_T *evalarg)
+{
+    CLEAR_POINTER(evalarg);
+    ga_init2(&evalarg->eval_tofree_ga, sizeof(char_u *), 20);
+}
+
+/*
+ * If "evalarg->eval_tofree" is not NULL free it later.
+ * Caller is expected to overwrite "evalarg->eval_tofree" next.
+ */
+    static void
+free_eval_tofree_later(evalarg_T *evalarg)
+{
+    if (evalarg->eval_tofree != NULL)
+    {
+	if (ga_grow(&evalarg->eval_tofree_ga, 1) == OK)
+	    ((char_u **)evalarg->eval_tofree_ga.ga_data)
+		[evalarg->eval_tofree_ga.ga_len++]
+		= evalarg->eval_tofree;
+	else
+	    vim_free(evalarg->eval_tofree);
+    }
+}
+
+/*
+ * After using "evalarg" filled from "eap": free the memory.
+ */
+    void
+clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
+{
+    if (evalarg != NULL)
+    {
+	if (evalarg->eval_tofree != NULL)
+	{
+	    if (eap != NULL)
+	    {
+		// We may need to keep the original command line, e.g. for
+		// ":let" it has the variable names.  But we may also need the
+		// new one, "nextcmd" points into it.  Keep both.
+		vim_free(eap->cmdline_tofree);
+		eap->cmdline_tofree = *eap->cmdlinep;
+		*eap->cmdlinep = evalarg->eval_tofree;
+	    }
+	    else
+		vim_free(evalarg->eval_tofree);
+	    evalarg->eval_tofree = NULL;
+	}
+
+	ga_clear_strings(&evalarg->eval_tofree_ga);
+	VIM_CLEAR(evalarg->eval_tofree_lambda);
+    }
+}
+
 /*
  * Skip over an expression at "*pp".
  * Return FAIL for an error, OK otherwise.
@@ -435,8 +492,8 @@ skip_expr_concatenate(
 		// Do not free the first line, the caller can still use it.
 		*((char_u **)gap->ga_data) = NULL;
 		// Do not free the last line, "arg" points into it, free it
-		// later.
-		vim_free(evalarg->eval_tofree);
+		// later.  Also free "eval_tofree" later if needed.
+		free_eval_tofree_later(evalarg);
 		evalarg->eval_tofree =
 				    ((char_u **)gap->ga_data)[gap->ga_len - 1];
 		((char_u **)gap->ga_data)[gap->ga_len - 1] = NULL;
@@ -2274,7 +2331,7 @@ eval_next_line(char_u *arg, evalarg_T *evalarg)
     }
     else if (evalarg->eval_cookie != NULL)
     {
-	vim_free(evalarg->eval_tofree);
+	free_eval_tofree_later(evalarg);
 	evalarg->eval_tofree = line;
     }
 
@@ -2301,45 +2358,6 @@ skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg)
     return p;
 }
 
-/*
- * Initialize "evalarg" for use.
- */
-    void
-init_evalarg(evalarg_T *evalarg)
-{
-    CLEAR_POINTER(evalarg);
-    ga_init2(&evalarg->eval_tofree_ga, sizeof(char_u *), 20);
-}
-
-/*
- * After using "evalarg" filled from "eap": free the memory.
- */
-    void
-clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
-{
-    if (evalarg != NULL)
-    {
-	if (evalarg->eval_tofree != NULL)
-	{
-	    if (eap != NULL)
-	    {
-		// We may need to keep the original command line, e.g. for
-		// ":let" it has the variable names.  But we may also need the
-		// new one, "nextcmd" points into it.  Keep both.
-		vim_free(eap->cmdline_tofree);
-		eap->cmdline_tofree = *eap->cmdlinep;
-		*eap->cmdlinep = evalarg->eval_tofree;
-	    }
-	    else
-		vim_free(evalarg->eval_tofree);
-	    evalarg->eval_tofree = NULL;
-	}
-
-	ga_clear_strings(&evalarg->eval_tofree_ga);
-	VIM_CLEAR(evalarg->eval_tofree_lambda);
-    }
-}
-
 /*
  * The "evaluate" argument: When FALSE, the argument is only parsed but not
  * executed.  The function may return OK, but the rettv will be of type

programming_language: C
filename: eval.pro
change_type: ModificationType.MODIFY
diff: @@ -9,6 +9,8 @@ int eval_expr_valid_arg(typval_T *tv);
 int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv);
 int eval_expr_to_bool(typval_T *expr, int *error);
 char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip);
+void init_evalarg(evalarg_T *evalarg);
+void clear_evalarg(evalarg_T *evalarg, exarg_T *eap);
 int skip_expr(char_u **pp, evalarg_T *evalarg);
 int skip_expr_concatenate(char_u **arg, char_u **start, char_u **end, evalarg_T *evalarg);
 char_u *typval2string(typval_T *tv, int convert);
@@ -34,8 +36,6 @@ int pattern_match(char_u *pat, char_u *text, int ic);
 char_u *eval_next_non_blank(char_u *arg, evalarg_T *evalarg, int *getnext);
 char_u *eval_next_line(char_u *arg, evalarg_T *evalarg);
 char_u *skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg);
-void init_evalarg(evalarg_T *evalarg);
-void clear_evalarg(evalarg_T *evalarg, exarg_T *eap);
 int eval0(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg);
 int eval0_retarg(char_u *arg, typval_T *rettv, exarg_T *eap, evalarg_T *evalarg, char_u **retarg);
 int eval1(char_u **arg, typval_T *rettv, evalarg_T *evalarg);

programming_language: C
filename: test_vim9_script.vim
change_type: ModificationType.MODIFY
diff: @@ -1560,6 +1560,19 @@ def Test_func_redefine_fails()
   v9.CheckScriptFailure(lines, 'E1073:')
 enddef
 
+def Test_lambda_split()
+  # this was using freed memory, because of the split expression
+  var lines =<< trim END
+      vim9script
+      try
+      0
+      0->(0
+        ->a.0(
+        ->u
+  END
+  v9.CheckScriptFailure(lines, 'E1050:')
+enddef
+
 def Test_fixed_size_list()
   # will be allocated as one piece of memory, check that changes work
   var l = [1, 2, 3, 4]

programming_language: Python
filename: userfunc.c
change_type: ModificationType.MODIFY
diff: @@ -1372,7 +1372,6 @@ get_lambda_tv(
     char_u	*start, *end;
     int		*old_eval_lavars = eval_lavars_used;
     int		eval_lavars = FALSE;
-    char_u	*tofree1 = NULL;
     char_u	*tofree2 = NULL;
     int		equal_arrow = **arg == '(';
     int		white_error = FALSE;
@@ -1457,12 +1456,6 @@ get_lambda_tv(
     ret = skip_expr_concatenate(arg, &start, &end, evalarg);
     if (ret == FAIL)
 	goto errret;
-    if (evalarg != NULL)
-    {
-	// avoid that the expression gets freed when another line break follows
-	tofree1 = evalarg->eval_tofree;
-	evalarg->eval_tofree = NULL;
-    }
 
     if (!equal_arrow)
     {
@@ -1585,10 +1578,6 @@ get_lambda_tv(
 
 theend:
     eval_lavars_used = old_eval_lavars;
-    if (evalarg != NULL && evalarg->eval_tofree == NULL)
-	evalarg->eval_tofree = tofree1;
-    else
-	vim_free(tofree1);
     vim_free(tofree2);
     if (types_optional)
 	ga_clear_strings(&argtypes);
@@ -1607,10 +1596,6 @@ errret:
     }
     vim_free(fp);
     vim_free(pt);
-    if (evalarg != NULL && evalarg->eval_tofree == NULL)
-	evalarg->eval_tofree = tofree1;
-    else
-	vim_free(tofree1);
     vim_free(tofree2);
     eval_lavars_used = old_eval_lavars;
     return FAIL;

programming_language: C
filename: version.c
change_type: ModificationType.MODIFY
diff: @@ -731,6 +731,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    225,
 /**/
     224,
 /**/

programming_language: Ruby