CVE-2022-1942: Heap-based Buffer Overflow (CWE-122)
value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 8.2.
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: 2022-05-31T14:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/buffer.c, src/ex_getln.c, src/proto/ex_getln.pro, src/testdir/test_substitute.vim, src/version.c, src/window.c
Commit date: 2022-05-30 15:23:09 +0100
Commit message: patch 8.2.5043: can open a cmdline window from a substitute expression

Problem:    Can open a cmdline window from a substitute expression.
Solution:   Disallow opening a command line window when text or buffer is
            locked.

Diff: diff --git a/src/buffer.c b/src/buffer.c
index efec431c822d..e775398d0294 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2405,16 +2405,11 @@ buflist_getfile(
 
     // if alternate file is the current buffer, nothing to do
     if (buf == curbuf)
 	return OK;
 
-    if (text_locked())
-    {
-	text_locked_msg();
-	return FAIL;
-    }
-    if (curbuf_locked())
+    if (text_or_buf_locked())
 	return FAIL;
 
     // altfpos may be changed by getfile(), get it now
     if (lnum == 0)
     {
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 9dadfbf2fabe..623bd1d4984a 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -2737,10 +2737,25 @@ get_text_locked_msg(void)
 	return e_invalid_in_cmdline_window;
 #endif
     return e_not_allowed_to_change_text_or_change_window;
 }
 
+/*
+ * Check for text, window or buffer locked.
+ * Give an error message and return TRUE if something is locked.
+ */
+    int
+text_or_buf_locked(void)
+{
+    if (text_locked())
+    {
+	text_locked_msg();
+	return TRUE;
+    }
+    return curbuf_locked();
+}
+
 /*
  * Check if "curbuf_lock" or "allbuf_lock" is set and return TRUE when it is
  * and give an error message.
  */
     int
@@ -4377,10 +4392,14 @@ open_cmdwin(void)
 #endif
 #ifdef FEAT_FOLDING
     int			save_KeyTyped;
 #endif
 
+    // Can't do this when text or buffer is locked.
+    if (text_or_buf_locked())
+	return K_IGNORE;
+
     // Can't do this recursively.  Can't do it when typing a password.
     if (cmdwin_type != 0
 # if defined(FEAT_CRYPT) || defined(FEAT_EVAL)
 	    || cmdline_star > 0
 # endif
diff --git a/src/proto/ex_getln.pro b/src/proto/ex_getln.pro
index 8c8bd0ebd4cd..bcc310c7dd0e 100644
--- a/src/proto/ex_getln.pro
+++ b/src/proto/ex_getln.pro
@@ -1,13 +1,14 @@
 /* ex_getln.c */
 void cmdline_init(void);
 char_u *getcmdline(int firstc, long count, int indent, getline_opt_T do_concat);
 char_u *getcmdline_prompt(int firstc, char_u *prompt, int attr, int xp_context, char_u *xp_arg);
 int check_opt_wim(void);
+int text_locked(void);
 void text_locked_msg(void);
 char *get_text_locked_msg(void);
-int text_locked(void);
+int text_or_buf_locked(void);
 int curbuf_locked(void);
 int allbuf_locked(void);
 char_u *getexline(int c, void *cookie, int indent, getline_opt_T options);
 char_u *getexmodeline(int promptc, void *cookie, int indent, getline_opt_T options);
 int cmdline_overstrike(void);
@@ -30,12 +31,12 @@ void escape_fname(char_u **pp);
 void tilde_replace(char_u *orig_pat, int num_files, char_u **files);
 cmdline_info_T *get_cmdline_info(void);
 void f_getcmdcompltype(typval_T *argvars, typval_T *rettv);
 void f_getcmdline(typval_T *argvars, typval_T *rettv);
 void f_getcmdpos(typval_T *argvars, typval_T *rettv);
-void f_setcmdpos(typval_T *argvars, typval_T *rettv);
 void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv);
+void f_setcmdpos(typval_T *argvars, typval_T *rettv);
 void f_getcmdtype(typval_T *argvars, typval_T *rettv);
 int get_cmdline_firstc(void);
 int get_list_range(char_u **str, int *num1, int *num2);
 char *check_cedit(void);
 int is_in_cmdwin(void);
diff --git a/src/testdir/test_substitute.vim b/src/testdir/test_substitute.vim
index c8df09f4ec1e..3c87fafe74cf 100644
--- a/src/testdir/test_substitute.vim
+++ b/src/testdir/test_substitute.vim
@@ -1033,10 +1033,35 @@ func Test_sub_undo_change()
   call Do_Test_sub_undo_change()
 
   delfunc Repl
 endfunc
 
+" This was opening a command line window from the expression
+func Test_sub_open_cmdline_win()
+  " the error only happens in a very specific setup, run a new Vim instance to
+  " get a clean starting point.
+  let lines =<< trim [SCRIPT]
+    norm o0000000000000000000000000000000000000000000000000000
+    func Replace()
+      norm q/
+    endfunc
+    s/\%')/\=Replace()
+    redir >Xresult
+    messages
+    redir END
+    qall!
+  [SCRIPT]
+  call writefile(lines, 'Xscript')
+  if RunVim([], [], '-u NONE -S Xscript')
+    let messages = readfile('Xresult')
+    call assert_match('E565: Not allowed to change text or change window', messages[3])
+  endif
+
+  call delete('Xscript')
+  call delete('Xresult')
+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/version.c b/src/version.c
index 18a1fdb41cb6..a15bb3ed8d6a 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 */
+/**/
+    5043,
 /**/
     5042,
 /**/
     5041,
 /**/
diff --git a/src/window.c b/src/window.c
index f2913d4a76ef..9b5ac97286cd 100644
--- a/src/window.c
+++ b/src/window.c
@@ -4583,18 +4583,15 @@ win_goto(win_T *wp)
     {
 	emsg(_(e_not_allowed_to_enter_popup_window));
 	return;
     }
 #endif
-    if (text_locked())
+    if (text_or_buf_locked())
     {
 	beep_flush();
-	text_locked_msg();
 	return;
     }
-    if (curbuf_locked())
-	return;
 
     if (wp->w_buffer != curbuf)
 	reset_VIsual_and_resel();
     else if (VIsual_active)
 	wp->w_cursor = curwin->w_cursor;

Vulnerable code:     if (text_locked())
    {
	text_locked_msg();
	return FAIL;
    }
    if (curbuf_locked())
int text_locked(void);
void f_setcmdpos(typval_T *argvars, typval_T *rettv);
    if (text_locked())
	text_locked_msg();
    if (curbuf_locked())
	return;
Fixed code:     if (text_or_buf_locked())
/*
 * Check for text, window or buffer locked.
 * Give an error message and return TRUE if something is locked.
 */
    int
text_or_buf_locked(void)
{
    if (text_locked())
    {
	text_locked_msg();
	return TRUE;
    }
    return curbuf_locked();
}

    // Can't do this when text or buffer is locked.
    if (text_or_buf_locked())
	return K_IGNORE;

int text_locked(void);
int text_or_buf_locked(void);
void f_setcmdpos(typval_T *argvars, typval_T *rettv);
" This was opening a command line window from the expression
func Test_sub_open_cmdline_win()
  " the error only happens in a very specific setup, run a new Vim instance to
  " get a clean starting point.
  let lines =<< trim [SCRIPT]
    norm o0000000000000000000000000000000000000000000000000000
    func Replace()
      norm q/
    endfunc
    s/\%')/\=Replace()
    redir >Xresult
    messages
    redir END
    qall!
  [SCRIPT]
  call writefile(lines, 'Xscript')
  if RunVim([], [], '-u NONE -S Xscript')
    let messages = readfile('Xresult')
    call assert_match('E565: Not allowed to change text or change window', messages[3])
  endif

  call delete('Xscript')
  call delete('Xresult')
endfunc

/**/
    5043,
    if (text_or_buf_locked())