CVE-2022-0408: Stack-based Buffer Overflow (CWE-121)
value: Stack-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.
CWE description: A stack-based buffer overflow condition is a condition where the buffer being overwritten is allocated on the stack (i.e., is a local variable or, rarely, a parameter to a function).
CVSS v3 base score: 7.8
Published: 2022-01-30T15:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/spellsuggest.c, src/testdir/test_spell.vim, src/version.c
Commit date: 2022-01-29 10:51:59 +0000
Commit message: patch 8.2.4247: stack corruption when looking for spell suggestions

Problem:    Stack corruption when looking for spell suggestions.
Solution:   Prevent the depth increased too much.  Add a five second time
            limit to finding suggestions.

Diff: diff --git a/src/spellsuggest.c b/src/spellsuggest.c
index b2b402919fb..e54b6fdc879 100644
--- a/src/spellsuggest.c
+++ b/src/spellsuggest.c
@@ -1203,11 +1203,11 @@ suggest_try_change(suginfo_T *su)
     }
 }
 
 // Check the maximum score, if we go over it we won't try this change.
 #define TRY_DEEPER(su, stack, depth, add) \
-		(stack[depth].ts_score + (add) < su->su_maxscore)
+	   (depth < MAXWLEN && stack[depth].ts_score + (add) < su->su_maxscore)
 
 /*
  * Try finding suggestions by adding/removing/swapping letters.
  *
  * This uses a state machine.  At each node in the tree we try various
@@ -1275,10 +1275,13 @@ suggest_trie_walk(
 #ifdef DEBUG_TRIEWALK
     // Stores the name of the change made at each level.
     char_u	changename[MAXWLEN][80];
 #endif
     int		breakcheckcount = 1000;
+#ifdef FEAT_RELTIME
+    proftime_T	time_limit;
+#endif
     int		compound_ok;
 
     // Go through the whole case-fold tree, try changes at each node.
     // "tword[]" contains the word collected from nodes in the tree.
     // "fword[]" the word we are trying to match with (initially the bad
@@ -1319,10 +1322,15 @@ suggest_trie_walk(
 	    idxs = fidxs;
 	    sp->ts_prefixdepth = PFD_NOPREFIX;
 	    sp->ts_state = STATE_START;
 	}
     }
+#ifdef FEAT_RELTIME
+    // The loop may take an indefinite amount of time. Break out after five
+    // sectonds. TODO: add an option for the time limit.
+    profile_setlimit(5000, &time_limit);
+#endif
 
     // Loop to find all suggestions.  At each round we either:
     // - For the current state try one operation, advance "ts_curi",
     //   increase "depth".
     // - When a state is done go to the next, set "ts_state".
@@ -1353,11 +1361,12 @@ suggest_trie_walk(
 		sp->ts_state = STATE_ENDNUL;
 		sp->ts_save_badflags = su->su_badflags;
 
 		// At end of a prefix or at start of prefixtree: check for
 		// following word.
-		if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX)
+		if (depth < MAXWLEN
+			    && (byts[arridx] == 0 || n == (int)STATE_NOPREFIX))
 		{
 		    // Set su->su_badflags to the caps type at this position.
 		    // Use the caps type until here for the prefix itself.
 		    if (has_mbyte)
 			n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
@@ -2647,10 +2656,14 @@ suggest_trie_walk(
 	    // Don't check for CTRL-C too often, it takes time.
 	    if (--breakcheckcount == 0)
 	    {
 		ui_breakcheck();
 		breakcheckcount = 1000;
+#ifdef FEAT_RELTIME
+		if (profile_passed_limit(&time_limit))
+		    got_int = TRUE;
+#endif
 	    }
 	}
     }
 }
 
diff --git a/src/testdir/test_spell.vim b/src/testdir/test_spell.vim
index 3c32450c241..a5c990ab728 100644
--- a/src/testdir/test_spell.vim
+++ b/src/testdir/test_spell.vim
@@ -771,10 +771,18 @@ func Test_spell_long_word()
   redraw!
   bwipe!
   set nospell
 endfunc
 
+func Test_spellsuggest_too_deep()
+  " This was incrementing "depth" over MAXWLEN.
+  new
+  norm s000G00ý000000000000
+  sil norm ..vzG................vvzG0     v z=
+  bwipe!
+endfunc
+
 func LoadAffAndDic(aff_contents, dic_contents)
   set enc=latin1
   set spellfile=
   call writefile(a:aff_contents, "Xtest.aff")
   call writefile(a:dic_contents, "Xtest.dic")
diff --git a/src/version.c b/src/version.c
index b0d48d50a61..005a394ab4d 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 */
+/**/
+    4247,
 /**/
     4246,
 /**/
     4245,
 /**/

Vulnerable code: 		(stack[depth].ts_score + (add) < su->su_maxscore)
		if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX)
Fixed code: 	   (depth < MAXWLEN && stack[depth].ts_score + (add) < su->su_maxscore)
#ifdef FEAT_RELTIME
    proftime_T	time_limit;
#endif
#ifdef FEAT_RELTIME
    // The loop may take an indefinite amount of time. Break out after five
    // sectonds. TODO: add an option for the time limit.
    profile_setlimit(5000, &time_limit);
#endif
		if (depth < MAXWLEN
			    && (byts[arridx] == 0 || n == (int)STATE_NOPREFIX))
#ifdef FEAT_RELTIME
		if (profile_passed_limit(&time_limit))
		    got_int = TRUE;
#endif
func Test_spellsuggest_too_deep()
  " This was incrementing "depth" over MAXWLEN.
  new
  norm s000G00ý000000000000
  sil norm ..vzG................vvzG0     v z=
  bwipe!
endfunc

/**/
    4247,