]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0400: sandbox callbacks selected through 'complete' v9.2.0400
authorBarrett Ruth <br.barrettruth@gmail.com>
Mon, 27 Apr 2026 17:18:17 +0000 (17:18 +0000)
committerChristian Brabandt <cb@256bit.org>
Mon, 27 Apr 2026 17:18:17 +0000 (17:18 +0000)
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>
src/insexpand.c
src/testdir/test_modeline.vim
src/version.c

index 0019c7eb45926881916a0d2774c646f04788ea83..a603ed7b08b02c02272942f053be6986ea4eb165 100644 (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;
index b78a4258f032a10b5eab690f82cb0d6cc9d7e363..6884ab473d7d2d5c5666905606c798ec080a41d8 100644 (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
index 45415395531e3723c6fdd097b57a0cefb346b70a..8dfe9b8f78f6ca9e2666c34d9fc965182223f73b 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    400,
 /**/
     399,
 /**/