From 8e2730a315b8b06192f5fc822dc218dbb3cff7ae Mon Sep 17 00:00:00 2001
From: Bram Moolenaar <Bram@vim.org>
Date: Wed, 8 Jul 2020 22:01:49 +0200
Subject: [PATCH] patch 8.2.1161: Vim9: using freed memory

Problem:    Vim9: using freed memory.
Solution:   Put pointer back in evalarg instead of freeing it.
---
 src/eval.c         | 60 +++++++++++++++++++++++++++++++---------------
 src/proto/eval.pro |  2 +-
 src/structs.h      |  5 +++-
 src/userfunc.c     | 29 ++++++++++++----------
 src/version.c      |  2 ++
 src/vim9compile.c  |  2 ++
 6 files changed, 67 insertions(+), 33 deletions(-)

diff --git a/src/eval.c b/src/eval.c
index 68fb9cddf8..6a1bc4c7a0 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -379,10 +379,17 @@ skip_expr(char_u **pp)
  * Skip over an expression at "*pp".
  * If in Vim9 script and line breaks are encountered, the lines are
  * concatenated.  "evalarg->eval_tofree" will be set accordingly.
+ * "arg" is advanced to just after the expression.
+ * "start" is set to the start of the expression, "end" to just after the end.
+ * Also when the expression is copied to allocated memory.
  * Return FAIL for an error, OK otherwise.
  */
     int
-skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
+skip_expr_concatenate(
+	char_u	    **arg,
+	char_u	    **start,
+	char_u	    **end,
+	evalarg_T   *evalarg)
 {
     typval_T	rettv;
     int		res;
@@ -398,12 +405,14 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
 	if (ga_grow(gap, 1) == OK)
 	    ++gap->ga_len;
     }
+    *start = *arg;
 
     // Don't evaluate the expression.
     if (evalarg != NULL)
 	evalarg->eval_flags &= ~EVAL_EVALUATE;
-    *end = skipwhite(*end);
-    res = eval1(end, &rettv, evalarg);
+    *arg = skipwhite(*arg);
+    res = eval1(arg, &rettv, evalarg);
+    *end = *arg;
     if (evalarg != NULL)
 	evalarg->eval_flags = save_flags;
 
@@ -419,7 +428,7 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
 	else
 	{
 	    char_u	    *p;
-	    size_t	    endoff = STRLEN(*end);
+	    size_t	    endoff = STRLEN(*arg);
 
 	    // Line breaks encountered, concatenate all the lines.
 	    *((char_u **)gap->ga_data) = *start;
@@ -428,7 +437,14 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
 	    // free the lines only when using getsourceline()
 	    if (evalarg->eval_cookie != NULL)
 	    {
+		// Do not free the first line, the caller can still use it.
 		*((char_u **)gap->ga_data) = NULL;
+		// Do not free the last line, "arg" points into it, free it
+		// later.
+		vim_free(evalarg->eval_tofree);
+		evalarg->eval_tofree =
+				    ((char_u **)gap->ga_data)[gap->ga_len - 1];
+		((char_u **)gap->ga_data)[gap->ga_len - 1] = NULL;
 		ga_clear_strings(gap);
 	    }
 	    else
@@ -437,8 +453,8 @@ skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg)
 	    if (p == NULL)
 		return FAIL;
 	    *start = p;
-	    vim_free(evalarg->eval_tofree);
-	    evalarg->eval_tofree = p;
+	    vim_free(evalarg->eval_tofree_lambda);
+	    evalarg->eval_tofree_lambda = p;
 	    // Compute "end" relative to the end.
 	    *end = *start + STRLEN(*start) - endoff;
 	}
@@ -1936,7 +1952,7 @@ eval_next_line(evalarg_T *evalarg)
 	((char_u **)gap->ga_data)[gap->ga_len] = line;
 	++gap->ga_len;
     }
-    else
+    else if (evalarg->eval_cookie != NULL)
     {
 	vim_free(evalarg->eval_tofree);
 	evalarg->eval_tofree = line;
@@ -1962,25 +1978,31 @@ skipwhite_and_linebreak(char_u *arg, evalarg_T *evalarg)
 }
 
 /*
- * After using "evalarg" filled from "eap" free the memory.
+ * After using "evalarg" filled from "eap": free the memory.
  */
     void
 clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
 {
-    if (evalarg != NULL && evalarg->eval_tofree != NULL)
+    if (evalarg != NULL)
     {
-	if (eap != NULL)
+	if (evalarg->eval_tofree != NULL)
 	{
-	    // We may need to keep the original command line, e.g. for
-	    // ":let" it has the variable names.  But we may also need the
-	    // new one, "nextcmd" points into it.  Keep both.
-	    vim_free(eap->cmdline_tofree);
-	    eap->cmdline_tofree = *eap->cmdlinep;
-	    *eap->cmdlinep = evalarg->eval_tofree;
+	    if (eap != NULL)
+	    {
+		// We may need to keep the original command line, e.g. for
+		// ":let" it has the variable names.  But we may also need the
+		// new one, "nextcmd" points into it.  Keep both.
+		vim_free(eap->cmdline_tofree);
+		eap->cmdline_tofree = *eap->cmdlinep;
+		*eap->cmdlinep = evalarg->eval_tofree;
+	    }
+	    else
+		vim_free(evalarg->eval_tofree);
+	    evalarg->eval_tofree = NULL;
 	}
-	else
-	    vim_free(evalarg->eval_tofree);
-	evalarg->eval_tofree = NULL;
+
+	vim_free(evalarg->eval_tofree_lambda);
+	evalarg->eval_tofree_lambda = NULL;
     }
 }
 
diff --git a/src/proto/eval.pro b/src/proto/eval.pro
index 3e87907957..5d04e01acb 100644
--- a/src/proto/eval.pro
+++ b/src/proto/eval.pro
@@ -10,7 +10,7 @@ int eval_expr_typval(typval_T *expr, typval_T *argv, int argc, typval_T *rettv);
 int eval_expr_to_bool(typval_T *expr, int *error);
 char_u *eval_to_string_skip(char_u *arg, exarg_T *eap, int skip);
 int skip_expr(char_u **pp);
-int skip_expr_concatenate(char_u **start, char_u **end, evalarg_T *evalarg);
+int skip_expr_concatenate(char_u **arg, char_u **start, char_u **end, evalarg_T *evalarg);
 char_u *eval_to_string(char_u *arg, int convert);
 char_u *eval_to_string_safe(char_u *arg, int use_sandbox);
 varnumber_T eval_to_number(char_u *expr);
diff --git a/src/structs.h b/src/structs.h
index 4ae94fd04e..bdc763b52d 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -1773,8 +1773,11 @@ typedef struct {
     // "eval_ga.ga_data" is a list of pointers to lines.
     garray_T	eval_ga;
 
-    // pointer to the line obtained with getsourceline()
+    // pointer to the last line obtained with getsourceline()
     char_u	*eval_tofree;
+
+    // pointer to the lines concatenated for a lambda.
+    char_u	*eval_tofree_lambda;
 } evalarg_T;
 
 // Flags for expression evaluation.
diff --git a/src/userfunc.c b/src/userfunc.c
index 01c97dedd4..6858068c82 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -389,8 +389,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
     partial_T   *pt = NULL;
     int		varargs;
     int		ret;
-    char_u	*start;
-    char_u	*s, *e;
+    char_u	*s;
+    char_u	*start, *end;
     int		*old_eval_lavars = eval_lavars_used;
     int		eval_lavars = FALSE;
     char_u	*tofree = NULL;
@@ -399,10 +399,10 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
     ga_init(&newlines);
 
     // First, check if this is a lambda expression. "->" must exist.
-    start = skipwhite(*arg + 1);
-    ret = get_function_args(&start, '-', NULL, NULL, NULL, NULL, TRUE,
+    s = skipwhite(*arg + 1);
+    ret = get_function_args(&s, '-', NULL, NULL, NULL, NULL, TRUE,
 								   NULL, NULL);
-    if (ret == FAIL || *start != '>')
+    if (ret == FAIL || *s != '>')
 	return NOTDONE;
 
     // Parse the arguments again.
@@ -423,8 +423,8 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
 
     // Get the start and the end of the expression.
     *arg = skipwhite_and_linebreak(*arg + 1, evalarg);
-    s = *arg;
-    ret = skip_expr_concatenate(&s, arg, evalarg);
+    start = *arg;
+    ret = skip_expr_concatenate(arg, &start, &end, evalarg);
     if (ret == FAIL)
 	goto errret;
     if (evalarg != NULL)
@@ -434,7 +434,6 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
 	evalarg->eval_tofree = NULL;
     }
 
-    e = *arg;
     *arg = skipwhite_and_linebreak(*arg, evalarg);
     if (**arg != '}')
     {
@@ -463,13 +462,13 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
 	    goto errret;
 
 	// Add "return " before the expression.
-	len = 7 + (int)(e - s) + 1;
+	len = 7 + (int)(end - start) + 1;
 	p = alloc(len);
 	if (p == NULL)
 	    goto errret;
 	((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
 	STRCPY(p, "return ");
-	vim_strncpy(p + 7, s, e - s);
+	vim_strncpy(p + 7, start, end - start);
 	if (strstr((char *)p + 7, "a:") == NULL)
 	    // No a: variables are used for sure.
 	    flags |= FC_NOARGS;
@@ -509,7 +508,10 @@ get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg)
     }
 
     eval_lavars_used = old_eval_lavars;
-    vim_free(tofree);
+    if (evalarg->eval_tofree == NULL)
+	evalarg->eval_tofree = tofree;
+    else
+	vim_free(tofree);
     return OK;
 
 errret:
@@ -517,7 +519,10 @@ errret:
     ga_clear_strings(&newlines);
     vim_free(fp);
     vim_free(pt);
-    vim_free(tofree);
+    if (evalarg->eval_tofree == NULL)
+	evalarg->eval_tofree = tofree;
+    else
+	vim_free(tofree);
     eval_lavars_used = old_eval_lavars;
     return FAIL;
 }
diff --git a/src/version.c b/src/version.c
index 7b5f8f5d07..8886382893 100644
--- a/src/version.c
+++ b/src/version.c
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1161,
 /**/
     1160,
 /**/
diff --git a/src/vim9compile.c b/src/vim9compile.c
index c0e4674de4..941309ffbd 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -3113,6 +3113,8 @@ compile_lambda(char_u **arg, cctx_T *cctx)
     // Compile it into instructions.
     compile_def_function(ufunc, TRUE, cctx);
 
+    clear_evalarg(&evalarg, NULL);
+
     if (ufunc->uf_def_status == UF_COMPILED)
 	return generate_FUNCREF(cctx, ufunc->uf_dfunc_idx);
     return FAIL;
-- 
GitLab