CVE-2022-2287: Out-of-bounds Read (CWE-125)
value: Out-of-bounds Read in GitHub repository vim/vim prior to 9.0.
CWE description: The product reads data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.1
Published: 2022-07-02T22:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/spellfile.c, src/testdir/test_spell.vim, src/version.c
Commit date: 2022-07-01 22:26:20 +0100
Commit message: patch 9.0.0021: invalid memory access when adding word to spell word list

Problem:    Invalid memory access when adding word with a control character to
            the internal spell word list.
Solution:   Disallow adding a word with control characters or a trailing
            slash.

Diff: diff --git a/src/spellfile.c b/src/spellfile.c
index f0d6d96a47f0..4a0de5237ffe 100644
--- a/src/spellfile.c
+++ b/src/spellfile.c
@@ -4364,10 +4364,27 @@ free_blocks(sblock_T *bl)
 wordtree_alloc(spellinfo_T *spin)
 {
     return (wordnode_T *)getroom(spin, sizeof(wordnode_T), TRUE);
 }
 
+/*
+ * Return TRUE if "word" contains valid word characters.
+ * Control characters and trailing '/' are invalid.  Space is OK.
+ */
+    static int
+valid_spell_word(char_u *word)
+{
+    char_u *p;
+
+    if (enc_utf8 && !utf_valid_string(word, NULL))
+	return FALSE;
+    for (p = word; *p != NUL; p += mb_ptr2len(p))
+	if (*p < ' ' || (p[0] == '/' && p[1] == NUL))
+	    return FALSE;
+    return TRUE;
+}
+
 /*
  * Store a word in the tree(s).
  * Always store it in the case-folded tree.  For a keep-case word this is
  * useful when the word can also be used with all caps (no WF_FIXCAP flag) and
  * used to find suggestions.
@@ -4389,11 +4406,11 @@ store_word(
     char_u	foldword[MAXWLEN];
     int		res = OK;
     char_u	*p;
 
     // Avoid adding illegal bytes to the word tree.
-    if (enc_utf8 && !utf_valid_string(word, NULL))
+    if (!valid_spell_word(word))
 	return FAIL;
 
     (void)spell_casefold(curwin, word, len, foldword, MAXWLEN);
     for (p = pfxlist; res == OK; ++p)
     {
@@ -6192,11 +6209,11 @@ spell_add_word(
     char_u	line[MAXWLEN * 2];
     long	fpos, fpos_next = 0;
     int		i;
     char_u	*spf;
 
-    if (enc_utf8 && !utf_valid_string(word, NULL))
+    if (!valid_spell_word(word))
     {
 	emsg(_(e_illegal_character_in_word));
 	return;
     }
 
diff --git a/src/testdir/test_spell.vim b/src/testdir/test_spell.vim
index 0fd5ed91780f..0187a175a826 100644
--- a/src/testdir/test_spell.vim
+++ b/src/testdir/test_spell.vim
@@ -852,10 +852,25 @@ func Test_spellsuggest_too_deep()
   norm s000G00ý000000000000
   sil norm ..vzG................vvzG0     v z=
   bwipe!
 endfunc
 
+func Test_spell_good_word_invalid()
+  " This was adding a word with a 0x02 byte, which causes havoc.
+  enew
+  norm o0
+  sil! norm rzzWs00/
+  2
+  sil! norm VzGprzzW
+  sil! norm z=
+
+  bwipe!
+  " clear the internal word list
+  set enc=latin1
+  set enc=utf-8
+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 26cb768a8254..23798ee560bd 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 */
+/**/
+    21,
 /**/
     20,
 /**/
     19,
 /**/

Vulnerable code:     if (enc_utf8 && !utf_valid_string(word, NULL))
    if (enc_utf8 && !utf_valid_string(word, NULL))
Fixed code: /*
 * Return TRUE if "word" contains valid word characters.
 * Control characters and trailing '/' are invalid.  Space is OK.
 */
    static int
valid_spell_word(char_u *word)
{
    char_u *p;

    if (enc_utf8 && !utf_valid_string(word, NULL))
	return FALSE;
    for (p = word; *p != NUL; p += mb_ptr2len(p))
	if (*p < ' ' || (p[0] == '/' && p[1] == NUL))
	    return FALSE;
    return TRUE;
}

    if (!valid_spell_word(word))
    if (!valid_spell_word(word))
func Test_spell_good_word_invalid()
  " This was adding a word with a 0x02 byte, which causes havoc.
  enew
  norm o0
  sil! norm rzzWs00/
  2
  sil! norm VzGprzzW
  sil! norm z=

  bwipe!
  " clear the internal word list
  set enc=latin1
  set enc=utf-8
endfunc

/**/
    21,