CVE-2022-1897: Out-of-bounds Write (CWE-787)
value: Out-of-bounds Write in GitHub repository vim/vim prior to 8.2.
CWE description: The product writes data past the end, or before the beginning, of the intended buffer.
CVSS v3 base score: 7.8
Published: 2022-05-27T15:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/normal.c, src/testdir/test_substitute.vim, src/undo.c, src/version.c
Commit date: 2022-05-26 15:56:23 +0100
Commit message: patch 8.2.5023: substitute overwrites allocated buffer

Problem:    Substitute overwrites allocated buffer.
Solution:   Disallow undo when in a substitute command.

Diff: diff --git a/src/normal.c b/src/normal.c
index bc3e29e1abaa..53c50dc8b368 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -181,10 +181,26 @@ find_command(int cmdchar)
 	    top = i - 1;
     }
     return idx;
 }
 
+/*
+ * If currently editing a cmdline or text is locked: beep and give an error
+ * message, return TRUE.
+ */
+    static int
+check_text_locked(oparg_T *oap)
+{
+    if (text_locked())
+    {
+	clearopbeep(oap);
+	text_locked_msg();
+	return TRUE;
+    }
+    return FALSE;
+}
+
 /*
  * Handle the count before a normal command and set cap->count0.
  */
     static int
 normal_cmd_get_count(
@@ -800,18 +816,13 @@ normal_cmd(
 	// Not a known command: beep.
 	clearopbeep(oap);
 	goto normal_end;
     }
 
-    if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW))
-    {
-	// This command is not allowed while editing a cmdline: beep.
-	clearopbeep(oap);
-	text_locked_msg();
-	goto normal_end;
-    }
-    if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
+    if ((nv_cmds[idx].cmd_flags & NV_NCW)
+				&& (check_text_locked(oap) || curbuf_locked()))
+	// this command is not allowed now
 	goto normal_end;
 
     // In Visual/Select mode, a few keys are handled in a special way.
     if (VIsual_active)
     {
@@ -4047,16 +4058,12 @@ nv_down(cmdarg_T *cap)
 nv_gotofile(cmdarg_T *cap)
 {
     char_u	*ptr;
     linenr_T	lnum = -1;
 
-    if (text_locked())
-    {
-	clearopbeep(cap->oap);
-	text_locked_msg();
+    if (check_text_locked(cap->oap))
 	return;
-    }
     if (curbuf_locked())
     {
 	clearop(cap->oap);
 	return;
     }
@@ -6180,18 +6187,11 @@ nv_g_cmd(cmdarg_T *cap)
 	break;
 #endif
 
     // "gQ": improved Ex mode
     case 'Q':
-	if (text_locked())
-	{
-	    clearopbeep(cap->oap);
-	    text_locked_msg();
-	    break;
-	}
-
-	if (!checkclearopq(oap))
+	if (!check_text_locked(cap->oap) && !checkclearopq(oap))
 	    do_exmode(TRUE);
 	break;
 
     case ',':
 	nv_pcmark(cap);
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index a1c324ed8d20..c8df09f4ec1e 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -1011,10 +1011,32 @@ func Test_sub_change_window()
   bwipe!
   bwipe!
   delfunc Repl
 endfunc
 
+" This was undoign a change in between computing the length and using it.
+func Do_Test_sub_undo_change()
+  new
+  norm o0000000000000000000000000000000000000000000000000000
+  silent! s/\%')/\=Repl()
+  bwipe!
+endfunc
+
+func Test_sub_undo_change()
+  func Repl()
+    silent! norm g-
+  endfunc
+  call Do_Test_sub_undo_change()
+
+  func! Repl()
+    silent earlier
+  endfunc
+  call Do_Test_sub_undo_change()
+
+  delfunc Repl
+endfunc
+
 " Test for the 2-letter and 3-letter :substitute commands
 func Test_substitute_short_cmd()
   new
   call setline(1, ['one', 'one one one'])
   s/one/two
diff --git a/src/undo.c b/src/undo.c
index cac09f0f58df..81cc28e8b801 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -2325,10 +2325,16 @@ undo_time(
     int		    dosec = sec;
     int		    dofile = file;
     int		    above = FALSE;
     int		    did_undo = TRUE;
 
+    if (text_locked())
+    {
+	text_locked_msg();
+	return;
+    }
+
     // First make sure the current undoable change is synced.
     if (curbuf->b_u_synced == FALSE)
 	u_sync(TRUE);
 
     u_newcount = 0;
diff --git a/src/version.c b/src/version.c
index 9751865c7adf..cd6c33162204 100644
--- a/src/version.c
+++ b/src/version.c
@@ -732,10 +732,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    5023,
 /**/
     5022,
 /**/
     5021,
 /**/

Vulnerable code:     if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW))
    {
	// This command is not allowed while editing a cmdline: beep.
	clearopbeep(oap);
	text_locked_msg();
	goto normal_end;
    }
    if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
    if (text_locked())
    {
	clearopbeep(cap->oap);
	text_locked_msg();
    }
	if (text_locked())
	{
	    clearopbeep(cap->oap);
	    text_locked_msg();
	    break;
	}

	if (!checkclearopq(oap))
Fixed code: /*
 * If currently editing a cmdline or text is locked: beep and give an error
 * message, return TRUE.
 */
    static int
check_text_locked(oparg_T *oap)
{
    if (text_locked())
    {
	clearopbeep(oap);
	text_locked_msg();
	return TRUE;
    }
    return FALSE;
}

    if ((nv_cmds[idx].cmd_flags & NV_NCW)
				&& (check_text_locked(oap) || curbuf_locked()))
	// this command is not allowed now
    if (check_text_locked(cap->oap))
	if (!check_text_locked(cap->oap) && !checkclearopq(oap))
" This was undoign a change in between computing the length and using it.
func Do_Test_sub_undo_change()
  new
  norm o0000000000000000000000000000000000000000000000000000
  silent! s/\%')/\=Repl()
  bwipe!
endfunc

func Test_sub_undo_change()
  func Repl()
    silent! norm g-
  endfunc
  call Do_Test_sub_undo_change()

  func! Repl()
    silent earlier
  endfunc
  call Do_Test_sub_undo_change()

  delfunc Repl
endfunc

    if (text_locked())
    {
	text_locked_msg();
	return;
    }

/**/
    5023,