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.