patch 9.2.0400: sandbox callbacks selected through 'complete'

Commit: 
https://github.com/vim/vim/commit/dd9b31fb62c0003be6cfd18847982b26efc73d34
Author: Barrett Ruth <[email protected]>
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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

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,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wHPmZ-004IMk-Mq%40256bit.org.

Raspunde prin e-mail lui