patch 9.1.1802: 'nowrap' in a modeline may hide malicious code

Commit: 
https://github.com/vim/vim/commit/9d5208a9313dd8b0d62c97af5485f1715af98a1c
Author: zeertzjq <[email protected]>
Date:   Sun Sep 28 17:29:19 2025 +0000

    patch 9.1.1802: 'nowrap' in a modeline may hide malicious code
    
    Problem:  'nowrap' in a modeline may hide malicious code.
    Solution: Forcibly use '>' as 'listchars' "extends" if 'nowrap' was set
              from a modeline (zeertzjq).
    
    Manual `:setlocal nowrap` disables this behavior.  There is a separate
    problem with `:set nowrap` that also applies to some other options.
    
    related: #18214
    related: #18399
    closes: #18425
    
    Signed-off-by: zeertzjq <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index ebd7e53bc..748b5ae0a 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 26
+*options.txt*  For Vim version 9.1.  Last change: 2025 Sep 28
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -10259,6 +10259,11 @@ A jump table for the options with a short description 
can be found at |Q_op|.
 <      See 'sidescroll', 'listchars' and |wrap-off|.
        This option can't be set from a |modeline| when the 'diff' option is
        on.
+       If 'nowrap' was set from a |modeline| or in the |sandbox|, '>' is used
+       as the |lcs-extends| character regardless of the value of the 'list'
+       and 'listchars' options.  This is to prevent malicious code outside
+       the viewport from going unnoticed.  Use `:setlocal nowrap` manually
+       afterwards to disable this behavior.
 
                                                *'wrapmargin'* *'wm'*
 'wrapmargin' 'wm'      number  (default 0)
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 5447b28c6..4ad3eb2a4 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2025 Sep 26
+*version9.txt*  For Vim version 9.1.  Last change: 2025 Sep 28
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41710,6 +41710,9 @@ Options: ~
 - 'rulerformat' now supports the |stl-%!| item
 - use 'smoothscroll' logic for CTRL-F / CTRL-B for pagewise scrolling
   and CTRL-D / CTRL-U for half-pagewise scrolling
+- Setting 'nowrap' in a modeline could cause long lines to be hidden
+  off-screen.  To make this visible, the listchars "extend" suboption is set
+  to ">" by default, indicating text that extends beyond the window width.
 
 Ex commands: ~
 - allow to specify a priority when defining a new sign |:sign-define|
diff --git a/src/drawline.c b/src/drawline.c
index dc68c45c0..40a57e785 100644
--- a/src/drawline.c
+++ b/src/drawline.c
@@ -15,6 +15,22 @@
 
 #include "vim.h"
 
+/*
+ * Get the 'listchars' "extends" characters to use for "wp", or NUL if it
+ * shouldn't be used.
+ */
+    static int
+get_lcs_ext(win_T *wp)
+{
+    if (wp->w_p_wrap)
+       // Line never continues beyond the right of the screen with 'wrap'.
+       return NUL;
+    if (wp->w_p_wrap_flags & P_INSECURE)
+       // If 'nowrap' was set from a modeline, forcibly use '>'.
+       return '>';
+    return wp->w_p_list ? wp->w_lcs_chars.ext : NUL;
+}
+
 #ifdef FEAT_SYN_HL
 /*
  * Advance **color_cols and return TRUE when there are columns to draw.
@@ -732,10 +748,7 @@ text_prop_position(
 
        // With 'nowrap' add one to show the "extends" character if needed (it
        // doesn't show if the text just fits).
-       if (!wp->w_p_wrap
-               && n_used < *n_extra
-               && wp->w_lcs_chars.ext != NUL
-               && wp->w_p_list)
+       if (n_used < *n_extra && get_lcs_ext(wp) != NUL)
            ++n_used;
 
        // add 1 for NUL, 2 for when '…' is used
@@ -3947,12 +3960,10 @@ win_line(
            }
        }
 
-       // Show "extends" character from 'listchars' if beyond the line end and
-       // 'list' is set.
-       if (wp->w_lcs_chars.ext != NUL
+       // Show "extends" character from 'listchars' if beyond the line end.
+       int lcs_ext = get_lcs_ext(wp);
+       if (lcs_ext != NUL
                && wlv.draw_state == WL_LINE
-               && wp->w_p_list
-               && !wp->w_p_wrap
 #ifdef FEAT_DIFF
                && wlv.filler_todo <= 0
 #endif
@@ -3970,7 +3981,7 @@ win_line(
 #endif
                   ))
        {
-           c = wp->w_lcs_chars.ext;
+           c = lcs_ext;
            wlv.char_attr = hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT));
            mb_c = c;
            if (enc_utf8 && utf_char2len(c) > 1)
diff --git a/src/option.c b/src/option.c
index 88da9c099..4a4d21b88 100644
--- a/src/option.c
+++ b/src/option.c
@@ -3081,6 +3081,7 @@ insecure_flag(int opt_idx, int opt_flags)
     if (opt_flags & OPT_LOCAL)
        switch ((int)options[opt_idx].indir)
        {
+           case PV_WRAP:       return &curwin->w_p_wrap_flags;
 #ifdef FEAT_STL_OPT
            case PV_STL:        return &curwin->w_p_stl_flags;
 #endif
diff --git a/src/structs.h b/src/structs.h
index 981db0e7c..72838bda2 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -4214,6 +4214,7 @@ struct window_S
 #define GLOBAL_WO(p)   ((char *)(p) + sizeof(winopt_T))
 
     // A few options have local flags for P_INSECURE.
+    long_u     w_p_wrap_flags;     // flags for 'wrap'
 #ifdef FEAT_STL_OPT
     long_u     w_p_stl_flags;      // flags for 'statusline'
 #endif
diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim
index 1f8686328..a5762f7f6 100644
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -361,4 +361,53 @@ func Test_modeline_disable()
   call assert_equal(2, &sw)
 endfunc
 
+" If 'nowrap' is set from a modeline, '>' is used forcibly as lcs-extends.
+func Test_modeline_nowrap_lcs_extends()
+  call writefile([
+        \ 'aaa',
+        \ 'bbb',
+        \ 'ccc                    evil',
+        \ 'ddd                    vim: nowrap',
+        \ ], 'Xmodeline_nowrap', 'D')
+  call NewWindow(10, 20)
+
+  setlocal nolist listchars=
+  edit Xmodeline_nowrap
+  let expect_insecure = [
+        \ 'aaa                 ',
+        \ 'bbb                 ',
+        \ 'ccc                >',
+        \ 'ddd                >',
+        \ '~                   ',
+        \ ]
+  call assert_equal(expect_insecure, ScreenLines([1, 5], 20))
+
+  setlocal nowrap
+  let expect_secure = [
+        \ 'aaa                 ',
+        \ 'bbb                 ',
+        \ 'ccc                 ',
+        \ 'ddd                 ',
+        \ '~                   ',
+        \ ]
+  call assert_equal(expect_secure, ScreenLines([1, 5], 20))
+
+  setlocal list listchars=extends:+
+  let expect_secure = [
+        \ 'aaa                 ',
+        \ 'bbb                 ',
+        \ 'ccc                +',
+        \ 'ddd                +',
+        \ '~                   ',
+        \ ]
+  call assert_equal(expect_secure, ScreenLines([1, 5], 20))
+
+  edit Xmodeline_nowrap
+  call assert_equal(expect_insecure, ScreenLines([1, 5], 20))
+  setlocal nowrap
+  call assert_equal(expect_secure, ScreenLines([1, 5], 20))
+
+  call CloseWindow()
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 47d461971..4d03167a7 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 */
+/**/
+    1802,
 /**/
     1801,
 /**/

-- 
-- 
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/E1v2vSM-00HBPY-F8%40256bit.org.

Raspunde prin e-mail lui