CVE-2022-0351: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: Access of Memory Location Before Start of Buffer in GitHub repository vim/vim prior to 8.2.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
CVSS v3 base score: 7.8
Published: 2022-01-25T18:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/errors.h, src/eval.c, src/testdir/test_eval_stuff.vim, src/version.c
Commit date: 2022-01-24 18:16:12 +0000
Commit message: patch 8.2.4206: condition with many "(" causes a crash

Problem:    Condition with many "(" causes a crash.
Solution:   Limit recursion to 1000.

Diff: diff --git a/src/errors.h b/src/errors.h
index e632d5b240f..d89c27c22bd 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -2716,10 +2716,12 @@ EXTERN char e_export_can_only_be_used_in_vim9script[]
 	INIT(= N_("E1042: Export can only be used in vim9script"));
 EXTERN char e_invalid_command_after_export[]
 	INIT(= N_("E1043: Invalid command after :export"));
 EXTERN char e_export_with_invalid_argument[]
 	INIT(= N_("E1044: Export with invalid argument"));
+// E1045 not used
+// E1046 not used
 EXTERN char e_syntax_error_in_import_str[]
 	INIT(= N_("E1047: Syntax error in import: %s"));
 EXTERN char e_item_not_found_in_script_str[]
 	INIT(= N_("E1048: Item not found in script: %s"));
 EXTERN char e_item_not_exported_in_script_str[]
@@ -2784,10 +2786,11 @@ EXTERN char e_missing_argument_type_for_str[]
 // E1078 unused
 // E1079 unused
 // E1080 unused
 EXTERN char e_cannot_unlet_str[]
 	INIT(= N_("E1081: Cannot unlet %s"));
+// E1082 unused
 EXTERN char e_missing_backtick[]
 	INIT(= N_("E1083: Missing backtick"));
 EXTERN char e_cannot_delete_vim9_script_function_str[]
 	INIT(= N_("E1084: Cannot delete Vim9 script function %s"));
 EXTERN char e_not_callable_type_str[]
@@ -2904,10 +2907,11 @@ EXTERN char e_missing_matching_bracket_after_dict_key[]
 	INIT(= N_("E1139: Missing matching bracket after dict key"));
 EXTERN char e_for_argument_must_be_sequence_of_lists[]
 	INIT(= N_("E1140: :for argument must be a sequence of lists"));
 EXTERN char e_indexable_type_required[]
 	INIT(= N_("E1141: Indexable type required"));
+// E1142 unused
 EXTERN char e_empty_expression_str[]
 	INIT(= N_("E1143: Empty expression: \"%s\""));
 EXTERN char e_command_str_not_followed_by_white_space_str[]
 	INIT(= N_("E1144: Command \"%s\" is not followed by white space: %s"));
 EXTERN char e_missing_heredoc_end_marker_str[]
@@ -2964,11 +2968,12 @@ EXTERN char e_cannot_use_range_with_dictionary[]
 	INIT(= N_("E1166: Cannot use a range with a dictionary"));
 EXTERN char e_argument_name_shadows_existing_variable_str[]
 	INIT(= N_("E1167: Argument name shadows existing variable: %s"));
 EXTERN char e_argument_already_declared_in_script_str[]
 	INIT(= N_("E1168: Argument already declared in the script: %s"));
-// E1169 unused
+EXTERN char e_expression_too_recursive_str[]
+	INIT(= N_("E1169: Expression too recursive: %s"));
 EXTERN char e_cannot_use_hash_curly_to_start_comment[]
 	INIT(= N_("E1170: Cannot use #{ to start a comment"));
 EXTERN char e_missing_end_block[]
 	INIT(= N_("E1171: Missing } after inline function"));
 EXTERN char e_cannot_use_default_values_in_lambda[]
diff --git a/src/eval.c b/src/eval.c
index 2ca3377492e..d42e1f8266a 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3524,10 +3524,11 @@ eval7(
     char_u	*s;
     char_u	*name_start = NULL;
     char_u	*start_leader, *end_leader;
     int		ret = OK;
     char_u	*alias;
+    static	int recurse = 0;
 
     /*
      * Initialise variable so that clear_tv() can't mistake this for a
      * string and free a string that isn't there.
      */
@@ -3550,10 +3551,19 @@ eval7(
 	semsg(_(e_invalid_expression_str), *arg);
 	++*arg;
 	return FAIL;
     }
 
+    // Limit recursion to 1000 levels.  At least at 10000 we run out of stack
+    // and crash.
+    if (recurse == 1000)
+    {
+	semsg(_(e_expression_too_recursive_str), *arg);
+	return FAIL;
+    }
+    ++recurse;
+
     switch (**arg)
     {
     /*
      * Number constant.
      */
@@ -3779,10 +3789,12 @@ eval7(
     /*
      * Apply logical NOT and unary '-', from right to left, ignore '+'.
      */
     if (ret == OK && evaluate && end_leader > start_leader)
 	ret = eval7_leader(rettv, FALSE, start_leader, &end_leader);
+
+    --recurse;
     return ret;
 }
 
 /*
  * Apply the leading "!" and "-" before an eval7 expression to "rettv".
diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim
index ff6d695f622..6043ed9a135 100644
--- a/src/testdir/test_eval_stuff.vim
+++ b/src/testdir/test_eval_stuff.vim
@@ -588,6 +588,11 @@ func Test_curly_assignment()
   unlet s:svar
   unlet s:gvar
   unlet g:gvar
 endfunc
 
+func Test_deep_recursion()
+  " this was running out of stack
+  call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((')
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 4cb980ea4ab..16a99ef92e4 100644
--- a/src/version.c
+++ b/src/version.c
@@ -748,10 +748,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4206,
 /**/
     4205,
 /**/
     4204,
 /**/

Vulnerable code: // E1169 unused
Fixed code: // E1045 not used
// E1046 not used
// E1082 unused
// E1142 unused
EXTERN char e_expression_too_recursive_str[]
	INIT(= N_("E1169: Expression too recursive: %s"));
    static	int recurse = 0;
    // Limit recursion to 1000 levels.  At least at 10000 we run out of stack
    // and crash.
    if (recurse == 1000)
    {
	semsg(_(e_expression_too_recursive_str), *arg);
	return FAIL;
    }
    ++recurse;


    --recurse;
func Test_deep_recursion()
  " this was running out of stack
  call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((')
endfunc

/**/
    4206,