CVE-2022-0156 (C, published 2022-01-10T16:15Z)
Description: value: vim is vulnerable to Use After Free
Severity: MEDIUM (CVSS v3 base score: 5.5)
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/alloc.c
src/message.c
src/proto/alloc.pro
src/proto/userfunc.pro
src/testdir/test_vim9_func.vim
src/usercmd.c
src/userfunc.c
src/version.c
src/vim9compile.c
src/viminfo.c
Commit message: patch 8.2.4040: keeping track of allocated lines is too complicated

Problem:    Keeping track of allocated lines in user functions is too
            complicated.
Solution:   Instead of freeing individual lines keep them all until the end.


Vulnerable code:
ga_init2(garray_T *gap, int itemsize, int growsize)
ga_add_string(garray_T *gap, char_u *p)
	ga_add_string(&ignore_error_list, error);
void ga_init2(garray_T *gap, int itemsize, int growsize);
ufunc_T *define_function(exarg_T *eap, char_u *name_arg, char_u **line_to_free);

	if (ga_add_string(&ga, p) == FAIL)
		if (ga_add_string(&ga, line) == FAIL)
 * Get a next line, store it in "eap" if appropriate and use "line_to_free" to
 * handle freeing the line later.
	char_u		**line_to_free,
	if (*eap->cmdlinep == *line_to_free)
	vim_free(*line_to_free);
	*line_to_free = theline;
    char_u	**line_to_free)
	    char_u *theline = get_function_line(eap, line_to_free, 0,
	char_u	    **line_to_free)
	    theline = get_function_line(eap, line_to_free, indent,
			// change "eap->cmdlinep".
			if (*line_to_free != NULL
					    && *eap->cmdlinep != *line_to_free)
			    *eap->cmdlinep = *line_to_free;
			    *line_to_free = NULL;
    char_u	*line_to_free = NULL;
    if (get_function_body(&eap, &newlines, NULL, &line_to_free) == FAIL)
    {
	if (cmdline != line_to_free)
	    vim_free(cmdline);
    }
	    if (cmdline == line_to_free)
		line_to_free = NULL;
    vim_free(line_to_free);
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);
compile_nested_function(exarg_T *eap, cctx_T *cctx, char_u **line_to_free)
    ufunc = define_function(eap, lambda_name, line_to_free);
    char_u	*line_to_free = NULL;
		vim_free(line_to_free);
		line_to_free = line;
		    line = compile_nested_function(&ea, &cctx, &line_to_free);
    vim_free(line_to_free);
	    ga_add_string(&virp->vir_barlines, virp->vir_line);
		    ga_add_string(&virp->vir_barlines, virp->vir_line);

Fixed code:
ga_init2(garray_T *gap, size_t itemsize, int growsize)
ga_copy_string(garray_T *gap, char_u *p)
/*
 * Add string "p" to "gap".
 * When out of memory "p" is freed and FAIL is returned.
 */
    int
ga_add_string(garray_T *gap, char_u *p)
{
    if (ga_grow(gap, 1) == FAIL)
	return FAIL;
    ((char_u **)(gap->ga_data))[gap->ga_len++] = p;
    return OK;
}

	ga_copy_string(&ignore_error_list, error);
void ga_init2(garray_T *gap, size_t itemsize, int growsize);
int ga_copy_string(garray_T *gap, char_u *p);
ufunc_T *define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free);
def Test_error_in_function_args()
  var lines =<< trim END
      def FirstFunction()
        def SecondFunction(J  =
        # Nois
        # one
         
         enddef|BBBB
      enddef
      # Compile all functions
      defcompile
  END
  CheckScriptFailure(lines, 'E488:')
enddef

	if (ga_copy_string(&ga, p) == FAIL)
		if (ga_copy_string(&ga, line) == FAIL)
 * Get a next line, store it in "eap" if appropriate and put the line in
 * "lines_to_free" to free the line later.
	garray_T	*lines_to_free,
	if (lines_to_free->ga_len > 0
		&& *eap->cmdlinep == ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1])
	ga_add_string(lines_to_free, theline);
    garray_T	*lines_to_free)
	    char_u *theline = get_function_line(eap, lines_to_free, 0,
	garray_T    *lines_to_free)
	    theline = get_function_line(eap, lines_to_free, indent,
			// change "eap->cmdlinep" to point to the last fetched
			// line.
			if (lines_to_free->ga_len > 0
				&& *eap->cmdlinep !=
					    ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1])
			    // *cmdlinep will be freed later, thus remove the
			    // line from lines_to_free.
			    *eap->cmdlinep = ((char_u **)lines_to_free->ga_data)
						   [lines_to_free->ga_len - 1];
			    --lines_to_free->ga_len;
    if (get_function_body(&eap, &newlines, NULL,
					     &evalarg->eval_tofree_ga) == FAIL)
 * "lines_to_free" is a list of strings to be freed later.
define_function(exarg_T *eap, char_u *name_arg, garray_T *lines_to_free)
			 eap, lines_to_free) == FAIL)
    if (get_function_body(eap, &newlines, line_arg, lines_to_free) == FAIL
    garray_T lines_to_free;
    ga_init2(&lines_to_free, sizeof(char_u *), 50);
    (void)define_function(eap, NULL, &lines_to_free);
    ga_clear_strings(&lines_to_free);
/**/
    4040,
compile_nested_function(exarg_T *eap, cctx_T *cctx, garray_T *lines_to_free)
    ufunc = define_function(eap, lambda_name, lines_to_free);
    garray_T	lines_to_free;
    // allocated lines are freed at the end
    ga_init2(&lines_to_free, sizeof(char_u *), 50);

		if (ga_add_string(&lines_to_free, line) == FAIL)
		    goto erret;
		    line = compile_nested_function(&ea, &cctx, &lines_to_free);
    ga_clear_strings(&lines_to_free);
	    ga_copy_string(&virp->vir_barlines, virp->vir_line);
		    ga_copy_string(&virp->vir_barlines, virp->vir_line);