CVE-2023-2610: Integer Overflow or Wraparound (CWE-190)
value: Integer Overflow or Wraparound in GitHub repository vim/vim prior to 9.0.1532.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.8
Published: 2023-05-09T22:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/regexp.c, src/testdir/test_substitute.vim, src/version.c
Commit date: 2023-05-09 21:15:30 +0100
Commit message: patch 9.0.1532: crash when expanding "~" in substitute causes very long text

Problem:    Crash when expanding "~" in substitute causes very long text.
Solution:   Limit the text length to MAXCOL.

Diff: diff --git a/src/regexp.c b/src/regexp.c
index 33b36d11a8be..0e6c746df819 100644
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -1765,40 +1765,48 @@ do_Lower(int *d, int c)
  */
     char_u *
 regtilde(char_u *source, int magic)
 {
     char_u	*newsub = source;
-    char_u	*tmpsub;
     char_u	*p;
-    int		len;
-    int		prevlen;
 
     for (p = newsub; *p; ++p)
     {
 	if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic))
 	{
 	    if (reg_prev_sub != NULL)
 	    {
 		// length = len(newsub) - 1 + len(prev_sub) + 1
-		prevlen = (int)STRLEN(reg_prev_sub);
-		tmpsub = alloc(STRLEN(newsub) + prevlen);
+		// Avoid making the text longer than MAXCOL, it will cause
+		// trouble at some point.
+		size_t	prevsublen = STRLEN(reg_prev_sub);
+		size_t  newsublen = STRLEN(newsub);
+		if (prevsublen > MAXCOL || newsublen > MAXCOL
+					    || newsublen + prevsublen > MAXCOL)
+		{
+		    emsg(_(e_resulting_text_too_long));
+		    break;
+		}
+
+		char_u *tmpsub = alloc(newsublen + prevsublen);
 		if (tmpsub != NULL)
 		{
 		    // copy prefix
-		    len = (int)(p - newsub);	// not including ~
-		    mch_memmove(tmpsub, newsub, (size_t)len);
+		    size_t prefixlen = p - newsub;	// not including ~
+		    mch_memmove(tmpsub, newsub, prefixlen);
 		    // interpret tilde
-		    mch_memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen);
+		    mch_memmove(tmpsub + prefixlen, reg_prev_sub,
+							       prevsublen);
 		    // copy postfix
 		    if (!magic)
 			++p;			// back off backslash
-		    STRCPY(tmpsub + len + prevlen, p + 1);
+		    STRCPY(tmpsub + prefixlen + prevsublen, p + 1);
 
-		    if (newsub != source)	// already allocated newsub
+		    if (newsub != source)	// allocated newsub before
 			vim_free(newsub);
 		    newsub = tmpsub;
-		    p = newsub + len + prevlen;
+		    p = newsub + prefixlen + prevsublen;
 		}
 	    }
 	    else if (magic)
 		STRMOVE(p, p + 1);	// remove '~'
 	    else
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index 7491b6163dc8..32e2f2785479 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -1412,10 +1412,24 @@ func Test_substitute_short_cmd()
   sIe
 
   bw!
 endfunc
 
+" Check handling expanding "~" resulting in extremely long text.
+func Test_substitute_tilde_too_long()
+  enew!
+
+  s/.*/ixxx
+  s//~~~~~~~~~AAAAAAA@(
+
+  " Either fails with "out of memory" or "text too long".
+  " This can take a long time.
+  call assert_fails('sil! norm &&&&&&&&&', ['E1240:\|E342:'])
+
+  bwipe!
+endfunc
+
 " This should be done last to reveal a memory leak when vim_regsub_both() is
 " called to evaluate an expression but it is not used in a second call.
 func Test_z_substitute_expr_leak()
   func SubExpr()
     ~n
diff --git a/src/version.c b/src/version.c
index 7ee9f575f9bd..3fb73b25ea33 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 */
+/**/
+    1532,
 /**/
     1531,
 /**/
     1530,
 /**/

Vulnerable code:     char_u	*tmpsub;
    int		len;
    int		prevlen;
		prevlen = (int)STRLEN(reg_prev_sub);
		tmpsub = alloc(STRLEN(newsub) + prevlen);
		    len = (int)(p - newsub);	// not including ~
		    mch_memmove(tmpsub, newsub, (size_t)len);
		    mch_memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen);
		    STRCPY(tmpsub + len + prevlen, p + 1);
		    if (newsub != source)	// already allocated newsub
		    p = newsub + len + prevlen;
Fixed code: 		// Avoid making the text longer than MAXCOL, it will cause
		// trouble at some point.
		size_t	prevsublen = STRLEN(reg_prev_sub);
		size_t  newsublen = STRLEN(newsub);
		if (prevsublen > MAXCOL || newsublen > MAXCOL
					    || newsublen + prevsublen > MAXCOL)
		{
		    emsg(_(e_resulting_text_too_long));
		    break;
		}

		char_u *tmpsub = alloc(newsublen + prevsublen);
		    size_t prefixlen = p - newsub;	// not including ~
		    mch_memmove(tmpsub, newsub, prefixlen);
		    mch_memmove(tmpsub + prefixlen, reg_prev_sub,
							       prevsublen);
		    STRCPY(tmpsub + prefixlen + prevsublen, p + 1);
		    if (newsub != source)	// allocated newsub before
		    p = newsub + prefixlen + prevsublen;
" Check handling expanding "~" resulting in extremely long text.
func Test_substitute_tilde_too_long()
  enew!

  s/.*/ixxx
  s//~~~~~~~~~AAAAAAA@(

  " Either fails with "out of memory" or "text too long".
  " This can take a long time.
  call assert_fails('sil! norm &&&&&&&&&', ['E1240:\|E342:'])

  bwipe!
endfunc

/**/
    1532,