patch 9.2.0400: sandbox callbacks selected through 'complete'
Commit:
https://github.com/vim/vim/commit/dd9b31fb62c0003be6cfd18847982b26efc73d34
Author: Barrett Ruth <
br.barr...@gmail.com>
Date: Mon Apr 27 17:18:17 2026 +0000
patch 9.2.0400: sandbox callbacks selected through 'complete'
Problem: Modeline-tainted 'complete' values can invoke completion
callbacks outside the sandbox.
Solution: Enter the sandbox for both 'complete' callback phases and add
a regression test (Barrett Ruth)
closes: #20078
Signed-off-by: Barrett Ruth <
br.barr...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/insexpand.c b/src/insexpand.c
index 0019c7eb4..a603ed7b0 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -3631,6 +3631,9 @@ expand_by_function(int type, char_u *base, callback_T *cb)
int save_State = State;
int retval;
int is_cpt_function = (cb != NULL);
+ int use_sandbox = is_cpt_function
+ && was_set_insecurely(curwin,
+ (char_u *)"complete", OPT_LOCAL);
if (!is_cpt_function)
{
@@ -3652,8 +3655,12 @@ expand_by_function(int type, char_u *base, callback_T *cb)
// switching to another window, it should not be needed and may end up in
// Insert mode in another buffer.
++textlock;
+ if (use_sandbox)
+ ++sandbox;
retval = call_callback(cb, 0, &rettv, 2, args);
+ if (use_sandbox)
+ --sandbox;
// Call a function, which returns a list or dict.
if (retval == OK)
@@ -6760,6 +6767,9 @@ get_userdefined_compl_info(
pos_T pos;
int save_State = State;
int is_cpt_function = (cb != NULL);
+ int use_sandbox = is_cpt_function
+ && was_set_insecurely(curwin,
+ (char_u *)"complete", OPT_LOCAL);
if (!is_cpt_function)
{
@@ -6782,7 +6792,11 @@ get_userdefined_compl_info(
args[2].v_type = VAR_UNKNOWN;
pos = curwin->w_cursor;
++textlock;
+ if (use_sandbox)
+ ++sandbox;
col = call_callback_retnr(cb, 2, args);
+ if (use_sandbox)
+ --sandbox;
--textlock;
State = save_State;
diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim
index b78a4258f..6884ab473 100644
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -283,6 +283,61 @@ func Test_modeline_fails_modelineexpr()
call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
endfunc
+func Test_modeline_complete_uses_sandbox()
+ let modeline = &modeline
+ let modelineexpr = &modelineexpr
+ let modelinestrict = &modelinestrict
+
+ func! ModelineCompletePwnFindstart(findstart, base)
+ if a:findstart
+ call writefile(['findstart'], 'Xmodeline_complete_proof')
+ return 0
+ endif
+ return ['match']
+ endfunc
+
+ func! ModelineCompletePwnMatches(findstart, base)
+ if a:findstart
+ return 0
+ endif
+ call writefile(['matches'], 'Xmodeline_complete_proof')
+ return ['match']
+ endfunc
+
+ try
+ set modeline modelineexpr nomodelinestrict
+
+ call writefile([
+ \ 'vim: set complete=FModelineCompletePwnFindstart :',
+ \ 'body',
+ \ ], 'Xmodeline_complete_attack', 'D')
+ call delete('Xmodeline_complete_proof')
+ edit Xmodeline_complete_attack
+ call cursor(2, 1)
+ call assert_fails('call feedkeys("i\<C-N>\<Esc>", "xt")', 'E48:')
+ call assert_false(filereadable('Xmodeline_complete_proof'))
+ bwipe!
+
+ call writefile([
+ \ 'vim: set complete=FModelineCompletePwnMatches :',
+ \ 'body',
+ \ ], 'Xmodeline_complete_attack', 'D')
+ call delete('Xmodeline_complete_proof')
+ edit Xmodeline_complete_attack
+ call cursor(2, 1)
+ call assert_fails('call feedkeys("i\<C-N>\<Esc>", "xt")', 'E48:')
+ call assert_false(filereadable('Xmodeline_complete_proof'))
+ bwipe!
+ finally
+ let &modeline = modeline
+ let &modelineexpr = modelineexpr
+ let &modelinestrict = modelinestrict
+ call delete('Xmodeline_complete_proof')
+ delfunc ModelineCompletePwnFindstart
+ delfunc ModelineCompletePwnMatches
+ endtry
+endfunc
+
func Test_modeline_setoption_verbose()
let modeline = &modeline
set modeline
diff --git a/src/version.c b/src/version.c
index 454153955..8dfe9b8f7 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 */
+/**/
+ 400,
/**/
399,
/**/