patch 9.2.0960: double-free in string_reduce()
Commit:
https://github.com/vim/vim/commit/cd59994c455a20d39d5cc41b4978ecfd2bf4be2e
Author: Christian Brabandt <
c...@256bit.org>
Date: Mon Aug 17 19:05:55 2026 +0000
patch 9.2.0960: double-free in string_reduce()
Problem: string_reduce() copies *rettv into argv[0] before calling
eval_expr_typval(). When the evaluator fails early, rettv is
never reset and still aliases argv[0] v_string.
clear_tv(&argv[0]) frees it, leaving rettv dangling and when
in vim9script get_func_tv() frees it again (Ave Dva).
Solution: Set rettv->v_type = VAR_UNKNOWN like what is done in
list_reduce() and tuple_reduce(), use tv_get_string_strict()
in f_reduce()
closes: #21048
Supported by AI.
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/list.c b/src/list.c
index cf5374de2..0ea2e13bd 100644
--- a/src/list.c
+++ b/src/list.c
@@ -3472,7 +3472,7 @@ f_reduce(typval_T *argvars, typval_T *rettv)
else if (argvars[1].v_type == VAR_PARTIAL)
func_name = partial_name(argvars[1].vval.v_partial);
else
- func_name = tv_get_string(&argvars[1]);
+ func_name = tv_get_string_strict(&argvars[1]);
if (func_name == NULL || *func_name == NUL)
{
emsg(_(e_missing_function_argument));
diff --git a/src/strings.c b/src/strings.c
index 71f9d83e2..fa24586c1 100644
--- a/src/strings.c
+++ b/src/strings.c
@@ -1043,9 +1043,14 @@ string_reduce(
for ( ; *p != NUL; p += len)
{
argv[0] = *rettv;
+ rettv->v_type = VAR_UNKNOWN;
+
len = copy_first_char_to_tv(p, &argv[1]);
if (len < 0)
+ {
+ *rettv = argv[0];
break;
+ }
r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv);
diff --git a/src/testdir/test_listdict.vim b/src/testdir/test_listdict.vim
index 31b5f268d..88c2e07bd 100644
--- a/src/testdir/test_listdict.vim
+++ b/src/testdir/test_listdict.vim
@@ -1115,6 +1115,14 @@ func Test_reduce()
" should not crash
call assert_fails('echo reduce([1], test_null_function())', 'E1132:')
call assert_fails('echo reduce([1], test_null_partial())', 'E1132:')
+
+ " did cause double free
+ function! OuterReduce()
+ vim9 echo reduce('ab', 42)
+ endfunction
+ call assert_fails('call OuterReduce()', 'E1024:')
+ call assert_fails("echo reduce('ab', 'NoSuchFunc')", 'E117:')
+ delfunc OuterReduce
endfunc
" splitting a string to a List using split()
diff --git a/src/version.c b/src/version.c
index 65bfaa6f0..19542bdde 100644
--- a/src/version.c
+++ b/src/version.c
@@ -763,6 +763,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 960,
/**/
959,
/**/