CVE-2022-2849: Heap-based Buffer Overflow (CWE-122)
value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 9.0.0220.
CWE description: A heap overflow condition is a buffer overflow, where the buffer that can be overwritten is allocated in the heap portion of memory, generally meaning that the buffer was allocated using a routine such as malloc().
CVSS v3 base score: 7.8
Published: 2022-08-17T18:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/globals.h, src/mbyte.c, src/testdir/test_eval_stuff.vim, src/version.c
Commit date: 2022-08-16 17:50:38 +0100
Commit message: patch 9.0.0220: invalid memory access with for loop over NULL string

Problem:    Invalid memory access with for loop over NULL string.
Solution:   Make sure mb_ptr2len() consistently returns zero for NUL.

Diff: diff --git a/src/globals.h b/src/globals.h
index 1fadc744f5fb..21fabdbb517c 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -1033,11 +1033,12 @@ EXTERN vimconv_T output_conv;			// type of output conversion
  * Function pointers, used to quickly get to the right function.  Each has
  * three possible values: latin_ (8-bit), utfc_ or utf_ (utf-8) and dbcs_
  * (DBCS).
  * The value is set in mb_init();
  */
-// length of char in bytes, including following composing chars
+// Length of char in bytes, including any following composing chars.
+// NUL has length zero.
 EXTERN int (*mb_ptr2len)(char_u *p) INIT(= latin_ptr2len);
 
 // idem, with limit on string length
 EXTERN int (*mb_ptr2len_len)(char_u *p, int size) INIT(= latin_ptr2len_len);
 
diff --git a/src/mbyte.c b/src/mbyte.c
index 941411b4a940..73065c739459 100644
--- a/src/mbyte.c
+++ b/src/mbyte.c
@@ -1075,28 +1075,32 @@ dbcs_char2bytes(int c, char_u *buf)
     buf[0] = c;
     return 1;
 }
 
 /*
- * mb_ptr2len() function pointer.
- * Get byte length of character at "*p" but stop at a NUL.
- * For UTF-8 this includes following composing characters.
- * Returns 0 when *p is NUL.
+ * Get byte length of character at "*p".  Returns zero when "*p" is NUL.
+ * Used for mb_ptr2len() when 'encoding' latin.
  */
     int
 latin_ptr2len(char_u *p)
 {
- return MB_BYTE2LEN(*p);
+    return *p == NUL ? 0 : 1;
 }
 
+/*
+ * Get byte length of character at "*p".  Returns zero when "*p" is NUL.
+ * Used for mb_ptr2len() when 'encoding' DBCS.
+ */
     static int
-dbcs_ptr2len(
-    char_u	*p)
+dbcs_ptr2len(char_u *p)
 {
     int		len;
 
-    // Check if second byte is not missing.
+    if (*p == NUL)
+	return 0;
+
+    // if the second byte is missing the length is 1
     len = MB_BYTE2LEN(*p);
     if (len == 2 && p[1] == NUL)
 	len = 1;
     return len;
 }
@@ -2103,10 +2107,11 @@ utf_ptr2len_len(char_u *p, int size)
 }
 
 /*
  * Return the number of bytes the UTF-8 encoding of the character at "p" takes.
  * This includes following composing characters.
+ * Returns zero for NUL.
  */
     int
 utfc_ptr2len(char_u *p)
 {
     int		len;
diff --git a/src/testdir/test_eval_stuff.vim b/src/testdir/test_eval_stuff.vim
index c63082e8e8b6..313d791850c0 100644
--- a/src/testdir/test_eval_stuff.vim
+++ b/src/testdir/test_eval_stuff.vim
@@ -73,10 +73,22 @@ func Test_for_invalid()
     /1/5/2/s/\n
   endif
   redraw
 endfunc
 
+func Test_for_over_null_string()
+  let save_enc = &enc
+  set enc=iso8859
+  let cnt = 0
+  for c in test_null_string()
+    let cnt += 1
+  endfor
+  call assert_equal(0, cnt)
+
+  let &enc = save_enc
+endfunc
+
 func Test_readfile_binary()
   new
   call setline(1, ['one', 'two', 'three'])
   setlocal ff=dos
   silent write XReadfile_bin
diff --git a/src/version.c b/src/version.c
index 3aff052a75be..a6293e0cfa16 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 */
+/**/
+    220,
 /**/
     219,
 /**/
     218,
 /**/

Vulnerable code: // length of char in bytes, including following composing chars
 * mb_ptr2len() function pointer.
 * Get byte length of character at "*p" but stop at a NUL.
 * For UTF-8 this includes following composing characters.
 * Returns 0 when *p is NUL.
 return MB_BYTE2LEN(*p);
dbcs_ptr2len(
    char_u	*p)
    // Check if second byte is not missing.
Fixed code: // Length of char in bytes, including any following composing chars.
// NUL has length zero.
 * Get byte length of character at "*p".  Returns zero when "*p" is NUL.
 * Used for mb_ptr2len() when 'encoding' latin.
    return *p == NUL ? 0 : 1;
/*
 * Get byte length of character at "*p".  Returns zero when "*p" is NUL.
 * Used for mb_ptr2len() when 'encoding' DBCS.
 */
dbcs_ptr2len(char_u *p)
    if (*p == NUL)
	return 0;

    // if the second byte is missing the length is 1
 * Returns zero for NUL.
func Test_for_over_null_string()
  let save_enc = &enc
  set enc=iso8859
  let cnt = 0
  for c in test_null_string()
    let cnt += 1
  endfor
  call assert_equal(0, cnt)

  let &enc = save_enc
endfunc

/**/
    220,