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.barrettruth@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Barrett Ruth
2026-04-27 17:18:17 +00:00
committed by Christian Brabandt
parent a622dda915
commit dd9b31fb62
3 changed files with 71 additions and 0 deletions
+14
View File
@@ -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;
+55
View File
@@ -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
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
400,
/**/
399,
/**/