vim-patch:9.2.0400: sandbox callbacks selected through 'complete' (#39452)

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: vim/vim#20078

dd9b31fb62
This commit is contained in:
Barrett Ruth
2026-04-27 19:09:33 -04:00
committed by GitHub
parent a0820481f2
commit 0a7201095b
2 changed files with 69 additions and 1 deletions

View File

@@ -3203,6 +3203,7 @@ static void expand_by_function(int type, char *base, Callback *cb)
assert(curbuf != NULL);
const bool is_cpt_function = (cb != NULL);
const bool use_sandbox = is_cpt_function && was_set_insecurely(curwin, kOptComplete, OPT_LOCAL);
if (!is_cpt_function) {
char *funcname = get_complete_funcname(type);
if (*funcname == NUL) {
@@ -3224,9 +3225,16 @@ static void expand_by_function(int type, char *base, Callback *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++;
}
// Call a function, which returns a list or dict.
if (callback_call(cb, 2, args, &rettv)) {
const bool called = callback_call(cb, 2, args, &rettv);
if (use_sandbox) {
sandbox--;
}
if (called) {
switch (rettv.v_type) {
case VAR_LIST:
matchlist = rettv.vval.v_list;
@@ -5796,6 +5804,7 @@ static int get_userdefined_compl_info(colnr_T curs_col, Callback *cb, int *start
const int save_State = State;
const bool is_cpt_function = (cb != NULL);
const bool use_sandbox = is_cpt_function && was_set_insecurely(curwin, kOptComplete, OPT_LOCAL);
if (!is_cpt_function) {
// Call 'completefunc' or 'omnifunc' or 'thesaurusfunc' and get pattern
// length as a string
@@ -5816,7 +5825,13 @@ static int get_userdefined_compl_info(colnr_T curs_col, Callback *cb, int *start
pos_T pos = curwin->w_cursor;
textlock++;
if (use_sandbox) {
sandbox++;
}
colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args);
if (use_sandbox) {
sandbox--;
}
textlock--;
State = save_State;

View File

@@ -289,6 +289,59 @@ 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
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
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
call delete('Xmodeline_complete_proof')
delfunc ModelineCompletePwnFindstart
delfunc ModelineCompletePwnMatches
endtry
endfunc
func Test_modeline_setoption_verbose()
let modeline = &modeline
set modeline