From ab9a2d884b3a4abe319606ea95a5a6d6b01cd73a Mon Sep 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Tue, 9 May 2023 21:15:30 +0100
Subject: [PATCH] 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.
---
 src/regexp.c                    | 30 +++++++++++++++++++-----------
 src/testdir/test_substitute.vim | 14 ++++++++++++++
 2 files changed, 33 insertions(+), 11 deletions(-)

diff --git a/src/regexp.c b/src/regexp.c
index 33b36d11a8be..0e6c746df819 100644
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -1723,10 +1723,7 @@ do_Lower(int *d, int c)
 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)
     {
@@ -1735,24 +1732,35 @@ regtilde(char_u *source, int 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)
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
@@ -1394,6 +1394,20 @@ func Test_substitute_short_cmd()
   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()
