CVE-2021-4173 (C, published 2021-12-27T13:15Z)
Description: value: vim is vulnerable to Use After Free
Severity: MEDIUM (CVSS v3 base score: 7.8)
Weakness: CWE-416 \u2014 Use After Free
The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
Repository: https://github.com/vim/vim
Files changed:
src/proto/userfunc.pro
src/testdir/test_vim9_func.vim
src/userfunc.c
src/version.c
src/vim9compile.c
src/vim9execute.c
Commit message: patch 8.2.3902: Vim9: double free with nested :def function

Problem:    Vim9: double free with nested :def function.
Solution:   Pass "line_to_free" from compile_def_function() and make sure
            cmdlinep is valid.


Vulnerable code:
ufunc_T *define_function(exarg_T *eap, char_u *name_arg);
	    vim_free(*line_to_free);
			if (*line_to_free != NULL)
	if (cmdline != NULL)
    if (cmdline != NULL)
define_function(exarg_T *eap, char_u *name_arg)
    char_u	*line_to_free = NULL;
			 eap, &line_to_free) == FAIL)
    if (get_function_body(eap, &newlines, line_arg, &line_to_free) == FAIL
    vim_free(line_to_free);
    (void)define_function(eap, NULL);
compile_nested_function(exarg_T *eap, cctx_T *cctx)
    ufunc = define_function(eap, lambda_name);
	char_u *func_name = vim_strnsave(name_start + 2,
						    name_end - name_start - 2);

	if (func_name == NULL)
	    r = FAIL;
	else
	{
	    r = generate_NEWFUNC(cctx, lambda_name, func_name);
	    lambda_name = NULL;
	}
	lvar_T	*lvar = reserve_local(cctx, name_start, name_end - name_start,
		    line = compile_nested_function(&ea, &cctx);
		    define_function(&ea, NULL);

Fixed code:
ufunc_T *define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free);
def Test_nested_functin_with_nextcmd()
  var lines =<< trim END
      vim9script
      # Define an outer function
      def FirstFunction()
        # Define an inner function
        def SecondFunction()
          # the function has a body, a double free is detected.
          AAAAA

         # enddef followed by | or } followed by # one or more characters
         enddef|BBBB
      enddef

      # Compile all functions
      defcompile
  END
  CheckScriptFailure(lines, 'E476: Invalid command: AAAAA')
enddef

	    if (*eap->cmdlinep == *line_to_free)
		*eap->cmdlinep = theline;
	    vim_free(*line_to_free);
			if (*line_to_free != NULL
					    && *eap->cmdlinep != *line_to_free)
	if (eap.nextcmd != NULL)
    if (eap.nextcmd != NULL)
	    if (cmdline == line_to_free)
		line_to_free = NULL;
define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free)
			 eap, line_to_free) == FAIL)
    if (get_function_body(eap, &newlines, line_arg, line_to_free) == FAIL
    char_u *line_to_free = NULL;

    (void)define_function(eap, NULL, &line_to_free);
    vim_free(line_to_free);
/**/
    3902,
compile_nested_function(exarg_T *eap, cctx_T *cctx, char_u **line_to_free)
    int		off;
    char_u	*func_name;

    // This may free the current line, make a copy of the name.
    off = is_global ? 2 : 0;
    func_name = vim_strnsave(name_start + off, name_end - name_start - off);
    if (func_name == NULL)
    {
	r = FAIL;
	goto theend;
    }

    ufunc = define_function(eap, lambda_name, line_to_free);
	r = generate_NEWFUNC(cctx, lambda_name, func_name);
	func_name = NULL;
	lambda_name = NULL;
	lvar_T	*lvar = reserve_local(cctx, func_name, name_end - name_start,
    vim_free(func_name);
		    line = compile_nested_function(&ea, &cctx, &line_to_free);
		    char_u  *line_to_free = NULL;
		    define_function(&ea, NULL, &line_to_free);
		    vim_free(line_to_free);