CVE-2023-0051: Heap-based Buffer Overflow (CWE-122)
value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 9.0.1144.
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: 2023-01-04T18:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/message.c, src/proto/strings.pro, src/strings.c, src/testdir/test_cmdline.vim, src/version.c
Commit date: 2023-01-04 15:56:51 +0000
Commit message: patch 9.0.1144: reading beyond text

Problem:    Reading beyond text.
Solution:   Add strlen_maxlen() and use it.

Diff: diff --git a/src/message.c b/src/message.c
index 591691f3f65..bb3517c94ed 100644
--- a/src/message.c
+++ b/src/message.c
@@ -3053,11 +3053,12 @@ msg_puts_printf(char_u *str, int maxlen)
 
     if (*p != NUL && !(silent_mode && p_verbose == 0))
     {
 	char_u *tofree = NULL;
 
-	if (maxlen > 0 && STRLEN(p) > (size_t)maxlen)
+	if (maxlen > 0 && vim_strlen_maxlen((char *)p, (size_t)maxlen)
+							     >= (size_t)maxlen)
 	{
 	    tofree = vim_strnsave(p, (size_t)maxlen);
 	    p = tofree;
 	}
 	if (p != NULL)
diff --git a/src/proto/strings.pro b/src/proto/strings.pro
index 50bbf29fe85..60220883156 100644
--- a/src/proto/strings.pro
+++ b/src/proto/strings.pro
@@ -10,10 +10,11 @@ char_u *vim_strnsave_up(char_u *string, size_t len);
 void vim_strup(char_u *p);
 char_u *strlow_save(char_u *orig);
 void del_trailing_spaces(char_u *ptr);
 void vim_strncpy(char_u *to, char_u *from, size_t len);
 void vim_strcat(char_u *to, char_u *from, size_t tosize);
+size_t vim_strlen_maxlen(char *s, size_t maxlen);
 int vim_stricmp(char *s1, char *s2);
 int vim_strnicmp(char *s1, char *s2, size_t len);
 char_u *vim_strchr(char_u *string, int c);
 char_u *vim_strbyte(char_u *string, int c);
 char_u *vim_strrchr(char_u *string, int c);
diff --git a/src/strings.c b/src/strings.c
index edcae6f8a49..9174265997e 100644
--- a/src/strings.c
+++ b/src/strings.c
@@ -523,10 +523,23 @@ vim_strcat(char_u *to, char_u *from, size_t tosize)
     }
     else
 	mch_memmove(to + tolen, from, fromlen + 1);
 }
 
+/*
+ * A version of strlen() that has a maximum length.
+ */
+    size_t
+vim_strlen_maxlen(char *s, size_t maxlen)
+{
+    size_t i;
+    for (i = 0; i < maxlen; ++i)
+	if (s[i] == NUL)
+	    break;
+    return i;
+}
+
 #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) || defined(PROTO)
 /*
  * Compare two strings, ignoring case, using current locale.
  * Doesn't work for multi-byte characters.
  * return 0 for match, < 0 for smaller, > 0 for bigger
@@ -580,11 +593,11 @@ vim_strnicmp(char *s1, char *s2, size_t len)
  * Search for first occurrence of "c" in "string".
  * Version of strchr() that handles unsigned char strings with characters from
  * 128 to 255 correctly.  It also doesn't return a pointer to the NUL at the
  * end of the string.
  */
-    char_u  *
+    char_u *
 vim_strchr(char_u *string, int c)
 {
     char_u	*p;
     int		b;
 
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 78f9ba22bfb..91ff6f1567f 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -652,10 +652,21 @@ func Test_getcompletion()
   call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:')
   call assert_fails('call getcompletion("", "burp")', 'E475:')
   call assert_fails('call getcompletion("abc", [])', 'E1174:')
 endfunc
 
+func Test_multibyte_expression()
+  " This was using uninitialized memory.
+  let lines =<< trim END
+      set verbose=6
+      norm @=ٷ
+      qall!
+  END
+  call writefile(lines, 'XmultiScript', 'D')
+  call RunVim('', '', '-u NONE -n -e -s -S XmultiScript')
+endfunc
+
 " Test for getcompletion() with "fuzzy" in 'wildoptions'
 func Test_getcompletion_wildoptions()
   let save_wildoptions = &wildoptions
   set wildoptions&
   let l = getcompletion('space', 'option')
diff --git a/src/version.c b/src/version.c
index 4ccbd537abe..6e652c697cf 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 */
+/**/
+    1144,
 /**/
     1143,
 /**/
     1142,
 /**/

Vulnerable code: 	if (maxlen > 0 && STRLEN(p) > (size_t)maxlen)
    char_u  *
Fixed code: 	if (maxlen > 0 && vim_strlen_maxlen((char *)p, (size_t)maxlen)
							     >= (size_t)maxlen)
size_t vim_strlen_maxlen(char *s, size_t maxlen);
/*
 * A version of strlen() that has a maximum length.
 */
    size_t
vim_strlen_maxlen(char *s, size_t maxlen)
{
    size_t i;
    for (i = 0; i < maxlen; ++i)
	if (s[i] == NUL)
	    break;
    return i;
}

    char_u *
func Test_multibyte_expression()
  " This was using uninitialized memory.
  let lines =<< trim END
      set verbose=6
      norm @=ٷ
      qall!
  END
  call writefile(lines, 'XmultiScript', 'D')
  call RunVim('', '', '-u NONE -n -e -s -S XmultiScript')
endfunc

/**/
    1144,