CVE-2022-3491 (C, published 2022-12-03T13:15Z)
Description: value: Heap-based Buffer Overflow in GitHub repository vim/vim prior to 9.0.0742.
Severity: nan (CVSS v3 base score: 7.8)
Weakness: CWE-122 \u2014 Heap-based Buffer Overflow
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().
Repository: https://github.com/vim/vim
Files changed:
src/testdir/test_vim9_func.vim
src/testdir/test_vim9_script.vim
src/version.c
src/vim9cmds.c
src/vim9compile.c
Commit message: patch 9.0.0742: reading past end of the line when compiling a function

Problem:    Reading past end of the line when compiling a function with
            errors.
Solution:   Do not return an invalid pointer.  Fix skipping redirection.


Vulnerable code:
      def T()
          redir =>l[0]
	    if (lhs->lhs_append)
		// First load the current variable value.
		if (compile_load_lhs_with_index(lhs, lhs->lhs_whole,
		    return NULL;
	    }
	    // Gets the redirected text and put it on the stack, then store it
	    // in the variable.
	    generate_instr_type(cctx, ISN_REDIREND, &t_string);
	    if (lhs->lhs_append)
		generate_CONCAT(cctx, 2);
	    if (lhs->lhs_has_index)
	    {
		// Use the info in "lhs" to store the value at the index in the
		// list or dict.
		if (compile_assign_unlet(lhs->lhs_whole, lhs, TRUE,
	    }
	    else if (generate_store_lhs(cctx, lhs, -1, FALSE) == FAIL)
		return NULL;
	    VIM_CLEAR(lhs->lhs_name);
	    VIM_CLEAR(lhs->lhs_whole);
	generate_instr(cctx, ISN_REDIRSTART);
	lhs->lhs_append = append;
	if (lhs->lhs_has_index)
	    lhs->lhs_whole = vim_strnsave(arg, lhs->lhs_varlen_total);
	    if (lhs->lhs_whole == NULL)
		return NULL;
	if (name[1] != '@'
			&& (!valid_yank_reg(name[1], FALSE) || name[1] == '.'))
	{
	    emsg_invreg(name[1]);
	}

Fixed code:
def Test_invalid_redir()
  var lines =<< trim END
      def Tone()
        if 1
          redi =>@0
          redi END
        endif
      enddef
      defcompile
  END
  v9.CheckScriptFailure(lines, 'E354:')
  delfunc g:Tone

  # this was reading past the end of the line
  lines =<< trim END
      def Ttwo()
        if 0
          redi =>@0
          redi END
        endif
      enddef
      defcompile
  END
  v9.CheckScriptFailure(lines, 'E354:')
  delfunc g:Ttwo
enddef

      def Tredir()
          redir => l[0]
  delfunc g:Tredir

  lines =<< trim END
      def Tredir()
        if 0
          redir => l[0]
        endif
        echo 'executed'
        if 0
          redir END
        endif
      enddef
      defcompile
  END
  v9.CheckScriptSuccess(lines)
  delfunc g:Tredir

  lines =<< trim END
      def Tredir()
        var l = ['']
        if 1
          redir => l[0]
        endif
        echo 'executed'
        if 0
          redir END
        else
          redir END
        endif
      enddef
      defcompile
  END
  v9.CheckScriptSuccess(lines)
  delfunc g:Tredir

  lines =<< trim END
      let doit = 1
      def Tredir()
        var l = ['']
        if g:doit
          redir => l[0]
        endif
        echo 'executed'
        if g:doit
          redir END
        endif
      enddef
      defcompile
  END
  v9.CheckScriptSuccess(lines)
  delfunc g:Tredir
/**/
    742,
	    if (cctx->ctx_skip != SKIP_YES)
		if (lhs->lhs_append)
		{
		    // First load the current variable value.
		    if (compile_load_lhs_with_index(lhs, lhs->lhs_whole,
			return NULL;
		}
		// Gets the redirected text and put it on the stack, then store
		// it in the variable.
		generate_instr_type(cctx, ISN_REDIREND, &t_string);
		if (lhs->lhs_append)
		    generate_CONCAT(cctx, 2);
		if (lhs->lhs_has_index)
		{
		    // Use the info in "lhs" to store the value at the index in
		    // the list or dict.
		    if (compile_assign_unlet(lhs->lhs_whole, lhs, TRUE,
			return NULL;
		}
		else if (generate_store_lhs(cctx, lhs, -1, FALSE) == FAIL)
		VIM_CLEAR(lhs->lhs_name);
		VIM_CLEAR(lhs->lhs_whole);
	    }
	if (cctx->ctx_skip == SKIP_YES)
	    VIM_CLEAR(lhs->lhs_name);
	}
	else
	{
	    generate_instr(cctx, ISN_REDIRSTART);
	    lhs->lhs_append = append;
	    if (lhs->lhs_has_index)
	    {
		lhs->lhs_whole = vim_strnsave(arg, lhs->lhs_varlen_total);
		if (lhs->lhs_whole == NULL)
		    return NULL;
	    }
/*
 * Return TRUE if "name" is a valid register to use.
 * Return FALSE and give an error message if not.
 */
    static int
valid_dest_reg(int name)
{
    if ((name == '@' || valid_yank_reg(name, FALSE)) && name != '.')
	return TRUE;
    emsg_invreg(name);
    return FAIL;
}

	if (!valid_dest_reg(name[1]))
    {
	if (!valid_dest_reg(var_start[1]))
	    return FAIL;
    }