Commit: patch 9.2.0403: Vim9: def function sandbox bypass

2 views
Skip to first unread message

Christian Brabandt

unread,
Apr 27, 2026, 2:30:15 PM (23 hours ago) Apr 27
to vim...@googlegroups.com
patch 9.2.0403: Vim9: def function sandbox bypass

Commit: https://github.com/vim/vim/commit/f1a9449206b019f6443915c4d56ee0cc51492a87
Author: Christian Brabandt <c...@256bit.org>
Date: Mon Apr 27 18:14:49 2026 +0000

patch 9.2.0403: Vim9: def function sandbox bypass

Problem: Vim9: def function sandbox bypass
(Srinivas Piskala Ganesh Babu)
Solution: Check for sandbox flag in call_user_func() and call_dfunc()
when executing Vim9 script functions

closes: #20071

Supported by AI

Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index 9c652101a..e6142c2df 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -4629,4 +4629,41 @@ func Test_uriencoding()
call v9.CheckLegacyAndVim9Success(lines)
endfunc

+" Note: legacy func, not vim9 def, to avoid the test file being vim9script
+func Test_vim9_def_fc_sandbox()
+ sandbox def! g:Bad()
+ system('echo unsafe')
+ enddef
+
+ call assert_fails('call g:Bad()', 'E48:')
+ delfunction g:Bad
+endfunc
+
+func Test_vim9_def_call_dfunc_fc_sandbox()
+ " Inner has FC_SANDBOX, outer does not.
+ " Calling outer goes through call_dfunc into inner, exercising
+ " the sandbox handling in call_dfunc and func_return.
+ sandbox def! g:Inner()
+ system('echo unsafe')
+ enddef
+
+ def! g:Outer()
+ g:Inner()
+ enddef
+
+ call assert_fails('call g:Outer()', 'E48:')
+ delfunction g:Inner
+ delfunction g:Outer
+endfunc
+
+" Deferred body inside a sandboxed def function must still run sandboxed.
+func Test_vim9_def_defer_fc_sandbox()
+ sandbox def! g:BadDefer()
+ defer system('echo unsafe')
+ enddef
+
+ call assert_fails('call g:BadDefer()', 'E48:')
+ delfunction g:BadDefer
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/userfunc.c b/src/userfunc.c
index e861a9eac..261518538 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -3038,6 +3038,11 @@ call_user_func(

if (fp->uf_def_status != UF_NOT_COMPILED)
{
+ if (fp->uf_flags & FC_SANDBOX)
+ {
+ using_sandbox = TRUE;
+ ++sandbox;
+ }
#ifdef FEAT_PROFILE
ufunc_T *caller = fc->fc_caller == NULL ? NULL : fc->fc_caller->fc_func;
#endif
@@ -3050,6 +3055,8 @@ call_user_func(
if (call_def_function(fp, argcount, argvars, 0,
funcexe->fe_partial, funcexe->fe_object, fc, rettv) == FAIL)
retval = FCERR_FAILED;
+ if (using_sandbox)
+ --sandbox;
funcdepth_decrement();
#ifdef FEAT_PROFILE
if (do_profiling == PROF_YES && (fp->uf_profiling
diff --git a/src/version.c b/src/version.c
index 45f571932..72f88365f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 403,
/**/
402,
/**/
diff --git a/src/vim9execute.c b/src/vim9execute.c
index d83b7968b..e1ddb7c1d 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -742,6 +742,9 @@ call_dfunc(
else
ectx->ec_outer_ref = NULL;

+ if (ufunc->uf_flags & FC_SANDBOX)
+ ++sandbox;
+
++ufunc->uf_calls;

// Set execution state to the start of the called function.
@@ -1290,6 +1293,9 @@ func_return(ectx_T *ectx)
if (dfunc->df_defer_var_idx > 0)
invoke_defer_funcs(ectx);

+ if (dfunc->df_ufunc->uf_flags & FC_SANDBOX)
+ --sandbox;
+
// No check for uf_refcount being zero, cannot think of a way that would
// happen.
--dfunc->df_ufunc->uf_calls;
Reply all
Reply to author
Forward
0 new messages