CVE-2023-2426: Use of Out-of-range Pointer Offset (CWE-823)
value: Use of Out-of-range Pointer Offset in GitHub repository vim/vim prior to 9.0.1499.
CWE description: The product performs pointer arithmetic on a valid pointer, but it uses an offset that can point outside of the intended range of valid memory locations for the resulting pointer.
CVSS v3 base score: 5.5
Published: 2023-04-29T22:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/quickfix.c, src/search.c, src/testdir/test_matchfuzzy.vim, src/version.c
Commit date: 2023-04-29 21:38:04 +0100
Commit message: patch 9.0.1499: using uninitialized memory with fuzzy matching

Problem:    Using uninitialized memory with fuzzy matching.
Solution:   Initialize the arrays used to store match positions.

Diff: diff --git a/src/quickfix.c b/src/quickfix.c
index 13292e2f7515..553ad457880a 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -6056,19 +6056,21 @@ vgr_match_buflines(
 {
     int		found_match = FALSE;
     long	lnum;
     colnr_T	col;
     int		pat_len = (int)STRLEN(spat);
+    if (pat_len > MAX_FUZZY_MATCHES)
+	pat_len = MAX_FUZZY_MATCHES;
 
     for (lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; ++lnum)
     {
 	col = 0;
 	if (!(flags & VGR_FUZZY))
 	{
 	    // Regular expression match
 	    while (vim_regexec_multi(regmatch, curwin, buf, lnum,
-			col, NULL) > 0)
+								col, NULL) > 0)
 	    {
 		// Pass the buffer number so that it gets used even for a
 		// dummy buffer, unless duplicate_name is set, then the
 		// buffer will be wiped out below.
 		if (qf_add_entry(qfl,
@@ -6110,10 +6112,11 @@ vgr_match_buflines(
 	    int	    score;
 	    int_u   matches[MAX_FUZZY_MATCHES];
 	    int_u   sz = ARRAY_LENGTH(matches);
 
 	    // Fuzzy string match
+	    CLEAR_FIELD(matches);
 	    while (fuzzy_match(str + col, spat, FALSE, &score, matches, sz) > 0)
 	    {
 		// Pass the buffer number so that it gets used even for a
 		// dummy buffer, unless duplicate_name is set, then the
 		// buffer will be wiped out below.
diff --git a/src/search.c b/src/search.c
index 74ca8fefb2c5..5e3857078031 100644
--- a/src/search.c
+++ b/src/search.c
@@ -4420,30 +4420,27 @@ fuzzy_match_recursive(
 	c2 = PTR2CHAR(str);
 
 	// Found match
 	if (vim_tolower(c1) == vim_tolower(c2))
 	{
-	    int_u	recursiveMatches[MAX_FUZZY_MATCHES];
-	    int		recursiveScore = 0;
-	    char_u	*next_char;
-
 	    // Supplied matches buffer was too short
 	    if (nextMatch >= maxMatches)
 		return 0;
 
+	    int		recursiveScore = 0;
+	    int_u	recursiveMatches[MAX_FUZZY_MATCHES];
+	    CLEAR_FIELD(recursiveMatches);
+
 	    // "Copy-on-Write" srcMatches into matches
 	    if (first_match && srcMatches)
 	    {
 		memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0]));
 		first_match = FALSE;
 	    }
 
 	    // Recursive call that "skips" this match
-	    if (has_mbyte)
-		next_char = str + (*mb_ptr2len)(str);
-	    else
-		next_char = str + 1;
+	    char_u *next_char = str + (has_mbyte ? (*mb_ptr2len)(str) : 1);
 	    if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1,
 			&recursiveScore, strBegin, strLen, matches,
 			recursiveMatches,
 			ARRAY_LENGTH(recursiveMatches),
 			nextMatch, recursionCount))
@@ -4504,12 +4501,12 @@ fuzzy_match_recursive(
  * Recursion is limited internally (default=10) to prevent degenerate cases
  * (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").
  * Uses char_u for match indices. Therefore patterns are limited to
  * MAX_FUZZY_MATCHES characters.
  *
- * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in
- * 'outScore' and the matching character positions in 'matches'.
+ * Returns TRUE if "pat_arg" matches "str". Also returns the match score in
+ * "outScore" and the matching character positions in "matches".
  */
     int
 fuzzy_match(
 	char_u		*str,
 	char_u		*pat_arg,
diff --git a/src/testdir/test_matchfuzzy.vim b/src/testdir/test_matchfuzzy.vim
index 502d136ccf2a..43eca8ff08af 100644
--- a/src/testdir/test_matchfuzzy.vim
+++ b/src/testdir/test_matchfuzzy.vim
@@ -1,9 +1,10 @@
 " Tests for fuzzy matching
 
 source shared.vim
 source check.vim
+source term_util.vim
 
 " Test for matchfuzzy()
 func Test_matchfuzzy()
   call assert_fails('call matchfuzzy(10, "abc")', 'E686:')
   call assert_fails('call matchfuzzy(["abc"], [])', 'E730:')
@@ -251,6 +252,32 @@ func Test_matchfuzzy_limit()
   call assert_equal([{'id': 5, 'val': 'crayon'}, {'id': 6, 'val': 'camera'}], l->matchfuzzy('c', #{key: 'val', limit: 0}))
   call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{text_cb: {v -> v.val}, limit: 1}))
   call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{key: 'val', limit: 1}))
 endfunc
 
+" This was using uninitialized memory
+func Test_matchfuzzy_initialized()
+  CheckRunVimInTerminal
+
+  " This can take a very long time (esp. when using valgrind).  Run in a
+  " separate Vim instance and kill it after two seconds.  We only check for
+  " memory errors.
+  let lines =<< trim END
+      lvimgrep [ss [fg*
+  END
+  call writefile(lines, 'XTest_matchfuzzy', 'D')
+
+  let buf = RunVimInTerminal('-u NONE -X -Z', {})
+  call term_sendkeys(buf, ":source XTest_matchfuzzy\n")
+  call TermWait(buf, 2000)
+
+  let job = term_getjob(buf)
+  if job_status(job) == "run"
+    call job_stop(job, "int")
+    call TermWait(buf, 50)
+  endif
+
+  " clean up
+  call StopVimInTerminal(buf)
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index ce94bc80c0fa..460b49b9e67b 100644
--- a/src/version.c
+++ b/src/version.c
@@ -693,10 +693,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1499,
 /**/
     1498,
 /**/
     1497,
 /**/

Vulnerable code: 			col, NULL) > 0)
	    int_u	recursiveMatches[MAX_FUZZY_MATCHES];
	    int		recursiveScore = 0;
	    char_u	*next_char;

	    if (has_mbyte)
		next_char = str + (*mb_ptr2len)(str);
	    else
		next_char = str + 1;
 * Returns TRUE if 'pat_arg' matches 'str'. Also returns the match score in
 * 'outScore' and the matching character positions in 'matches'.
Fixed code:     if (pat_len > MAX_FUZZY_MATCHES)
	pat_len = MAX_FUZZY_MATCHES;
								col, NULL) > 0)
	    CLEAR_FIELD(matches);
	    int		recursiveScore = 0;
	    int_u	recursiveMatches[MAX_FUZZY_MATCHES];
	    CLEAR_FIELD(recursiveMatches);

	    char_u *next_char = str + (has_mbyte ? (*mb_ptr2len)(str) : 1);
 * Returns TRUE if "pat_arg" matches "str". Also returns the match score in
 * "outScore" and the matching character positions in "matches".
source term_util.vim
" This was using uninitialized memory
func Test_matchfuzzy_initialized()
  CheckRunVimInTerminal

  " This can take a very long time (esp. when using valgrind).  Run in a
  " separate Vim instance and kill it after two seconds.  We only check for
  " memory errors.
  let lines =<< trim END
      lvimgrep [ss [fg*
  END
  call writefile(lines, 'XTest_matchfuzzy', 'D')

  let buf = RunVimInTerminal('-u NONE -X -Z', {})
  call term_sendkeys(buf, ":source XTest_matchfuzzy\n")
  call TermWait(buf, 2000)

  let job = term_getjob(buf)
  if job_status(job) == "run"
    call job_stop(job, "int")
    call TermWait(buf, 50)
  endif

  " clean up
  call StopVimInTerminal(buf)
endfunc

/**/
    1499,