CVE-2023-4738: Out-of-bounds Write (CWE-787)
value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 9.0.1848.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2023-09-02T20:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/ex_cmds.c, src/regexp.c, src/testdir/crash/vim_regsub_both, src/testdir/test_crash.vim, src/version.c
Commit date: 2023-09-02 21:37:04 +0200
Commit message: patch 9.0.1848: [security] buffer-overflow in vim_regsub_both()

Problem:  buffer-overflow in vim_regsub_both()
Solution: Check remaining space

Signed-off-by: Christian Brabandt <cb@256bit.org>

Diff: diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index c30b6fddf24c..53c7bb5a3762 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -4649,10 +4649,13 @@ ex_substitute(exarg_T *eap)
 		 * copy the text up to the part that matched
 		 */
 		mch_memmove(new_end, sub_firstline + copycol, (size_t)copy_len);
 		new_end += copy_len;
 
+		if (new_start_len - copy_len < sublen)
+		    sublen = new_start_len - copy_len - 1;
+
 #ifdef FEAT_EVAL
 		++textlock;
 #endif
 		(void)vim_regsub_multi(&regmatch,
 				    sub_firstlnum - regmatch.startpos[0].lnum,
diff --git a/src/regexp.c b/src/regexp.c
index 9c576c6893cb..edd1293a530d 100644
--- a/src/regexp.c
+++ b/src/regexp.c
@@ -2049,11 +2049,12 @@ vim_regsub_both(
 	// resulting string is saved from the call with
 	// "flags & REGSUB_COPY" == 0 to the call with
 	// "flags & REGSUB_COPY" != 0.
 	if (copy)
 	{
-	    if (eval_result[nested] != NULL)
+	    if (eval_result[nested] != NULL &&
+		    STRLEN(eval_result[nested]) < destlen)
 	    {
 		STRCPY(dest, eval_result[nested]);
 		dst += STRLEN(eval_result[nested]);
 		VIM_CLEAR(eval_result[nested]);
 	    }
diff --git a/src/testdir/crash/vim_regsub_both b/src/testdir/crash/vim_regsub_both
new file mode 100644
index 000000000000..a82b205c8f3e
--- /dev/null
+++ b/src/testdir/crash/vim_regsub_both
@@ -0,0 +1,10 @@
+fu R()
+sil!norm0z=
+endf
+cal R()
+s/\%')/\=R()
+d
+no0 normyynore sm:vs0@vvvvvvvvvvse()dir(¼Xtest=csd{so88
+vs
+0scr
+so
diff --git a/src/testdir/test_crash.vim b/src/testdir/test_crash.vim
index 0dea3c2cb1cb..445fe8d5a725 100644
--- a/src/testdir/test_crash.vim
+++ b/src/testdir/test_crash.vim
@@ -4,11 +4,11 @@ source screendump.vim
 
 CheckScreendump
 
 func Test_crash1()
   " The following used to crash Vim
-  let opts = #{wait_for_ruler: 0}
+  let opts = #{wait_for_ruler: 0, rows: 20}
   let args = ' -u NONE -i NONE -n -e -s -S '
   let buf = RunVimInTerminal(args .. ' crash/poc_huaf1', opts)
   call VerifyScreenDump(buf, 'Test_crash_01', {})
   exe buf .. "bw!"
 
@@ -20,6 +20,15 @@ func Test_crash1()
   call VerifyScreenDump(buf, 'Test_crash_01', {})
   exe buf .. "bw!"
 
 endfunc
 
+func Test_crash2()
+  " The following used to crash Vim
+  let opts = #{wait_for_ruler: 0, rows: 20}
+  let args = ' -u NONE -i NONE -n -e -s -S '
+  let buf = RunVimInTerminal(args .. ' crash/vim_regsub_both', opts)
+  call VerifyScreenDump(buf, 'Test_crash_01', {})
+  exe buf .. "bw!"
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index c638a107e30f..b69c41068720 100644
--- a/src/version.c
+++ b/src/version.c
@@ -697,10 +697,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1848,
 /**/
     1847,
 /**/
     1846,
 /**/

Vulnerable code: 	    if (eval_result[nested] != NULL)
  let opts = #{wait_for_ruler: 0}
Fixed code: 		if (new_start_len - copy_len < sublen)
		    sublen = new_start_len - copy_len - 1;

	    if (eval_result[nested] != NULL &&
		    STRLEN(eval_result[nested]) < destlen)
fu R()
sil!norm0z=
endf
cal R()
s/\%')/\=R()
d
no0 normyynore sm:vs0@vvvvvvvvvvse()dir(¼Xtest=csd{so88
vs
0scr
so
  let opts = #{wait_for_ruler: 0, rows: 20}
func Test_crash2()
  " The following used to crash Vim
  let opts = #{wait_for_ruler: 0, rows: 20}
  let args = ' -u NONE -i NONE -n -e -s -S '
  let buf = RunVimInTerminal(args .. ' crash/vim_regsub_both', opts)
  call VerifyScreenDump(buf, 'Test_crash_01', {})
  exe buf .. "bw!"
endfunc

/**/
    1848,