changeset 16728:e55c26aaf484 v8.1.1366

patch 8.1.1366: using expressions in a modeline is unsafe commit https://github.com/vim/vim/commit/110289e78195b6d01e1e6ad26ad450de476d41c1 Author: Bram Moolenaar <Bram@vim.org> Date: Thu May 23 15:38:06 2019 +0200 patch 8.1.1366: using expressions in a modeline is unsafe Problem: Using expressions in a modeline is unsafe. Solution: Disallow using expressions in a modeline, unless the 'modelineexpr' option is set. Update help, add more tests.
author Bram Moolenaar <Bram@vim.org>
date Thu, 23 May 2019 15:45:06 +0200
parents 8be69877c5de
children c775ddbeb6fe
files runtime/doc/options.txt src/option.c src/option.h src/testdir/test49.in src/testdir/test_modeline.vim src/version.c
diffstat 6 files changed, 169 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 8.1.  Last change: 2019 May 08
+*options.txt*	For Vim version 8.1.  Last change: 2019 May 23
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -578,14 +578,17 @@ backslash in front of the ':' will be re
    /* vi:set dir=c\:\tmp: */ ~
 This sets the 'dir' option to "c:\tmp".  Only a single backslash before the
 ':' is removed.  Thus to include "\:" you have to specify "\\:".
-
+							*E992*
 No other commands than "set" are supported, for security reasons (somebody
 might create a Trojan horse text file with modelines).  And not all options
-can be set.  For some options a flag is set, so that when it's used the
-|sandbox| is effective.  Still, there is always a small risk that a modeline
-causes trouble.  E.g., when some joker sets 'textwidth' to 5 all your lines
-are wrapped unexpectedly.  So disable modelines before editing untrusted text.
-The mail ftplugin does this, for example.
+can be set.  For some options a flag is set, so that when the value is used
+the |sandbox| is effective.  Some options can only be set from the modeline
+when 'modelineexpr' is set (the default is off).
+
+Still, there is always a small risk that a modeline causes trouble.  E.g.,
+when some joker sets 'textwidth' to 5 all your lines are wrapped unexpectedly.
+So disable modelines before editing untrusted text.  The mail ftplugin does
+this, for example.
 
 Hint: If you would like to do something else than setting an option, you could
 define an autocommand that checks the file for a specific string.  For
@@ -1149,6 +1152,7 @@ A jump table for the options with a shor
 
 	The expression will be evaluated in the |sandbox| when set from a
 	modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'balloonexpr' |textlock|.
@@ -3226,7 +3230,7 @@ A jump table for the options with a shor
 	The expression will be evaluated in the |sandbox| if set from a
 	modeline, see |sandbox-option|.
 	This option can't be set from a |modeline| when the 'diff' option is
-	on.
+	on or the 'modelineexpr' option is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'foldexpr' |textlock|.
@@ -3359,6 +3363,7 @@ A jump table for the options with a shor
 
 	The expression will be evaluated in the |sandbox| if set from a
 	modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'foldtext' |textlock|.
@@ -3396,6 +3401,7 @@ A jump table for the options with a shor
 	The expression will be evaluated in the |sandbox| when set from a
 	modeline, see |sandbox-option|.  That stops the option from working,
 	since changing the buffer text is not allowed.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 	NOTE: This option is set to "" when 'compatible' is set.
 
 					*'formatlistpat'* *'flp'*
@@ -3452,6 +3458,8 @@ A jump table for the options with a shor
 	Also see 'swapsync' for controlling fsync() on swap files.
 	'fsync' also applies to |writefile()|, unless a flag is used to
 	overrule it.
+	This option cannot be set from a |modeline| or in the |sandbox|, for
+	security reasons.
 
 				   *'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
 'gdefault' 'gd'		boolean	(default off)
@@ -3619,7 +3627,7 @@ A jump table for the options with a shor
 						*'guiheadroom'* *'ghr'*
 'guiheadroom' 'ghr'	number	(default 50)
 			global
-- 			{only for GTK and X11 GUI}
+			{only for GTK and X11 GUI}
 	The number of pixels subtracted from the screen height when fitting
 	the GUI window on the screen.  Set this before the GUI is started,
 	e.g., in your |gvimrc| file.  When zero, the whole screen height will
@@ -3777,6 +3785,7 @@ A jump table for the options with a shor
 	'guitabtooltip' is used for the tooltip, see below.
 	The expression will be evaluated in the |sandbox| when set from a
 	modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	Only used when the GUI tab pages line is displayed.  'e' must be
 	present in 'guioptions'.  For the non-GUI tab pages line 'tabline' is
@@ -4027,6 +4036,7 @@ A jump table for the options with a shor
 	When this option contains printf-style '%' items, they will be
 	expanded according to the rules used for 'statusline'.  See
 	'titlestring' for example settings.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 	{not available when compiled without the |+statusline| feature}
 
 			*'ignorecase'* *'ic'* *'noignorecase'* *'noic'*
@@ -4044,6 +4054,8 @@ A jump table for the options with a shor
 	This option specifies a function that will be called to
 	activate or deactivate the Input Method.
 	It is not used in the GUI.
+	The expression will be evaluated in the |sandbox| when set from a
+	modeline, see |sandbox-option|.
 
 	Example: >
 		function ImActivateFunc(active)
@@ -4160,6 +4172,8 @@ A jump table for the options with a shor
 		set imstatusfunc=ImStatusFunc
 <
 	NOTE: This function is invoked very often.  Keep it fast.
+	The expression will be evaluated in the |sandbox| when set from a
+	modeline, see |sandbox-option|.
 
 						*'imstyle'* *'imst'*
 'imstyle' 'imst'	number (default 1)
@@ -4176,6 +4190,8 @@ A jump table for the options with a shor
 	|single-repeat|, etc.  Therefore over-the-spot style becomes the
 	default now.  This should work fine for most people, however if you
 	have any problem with it, try using on-the-spot style.
+	The expression will be evaluated in the |sandbox| when set from a
+	modeline, see |sandbox-option|.
 
 						*'include'* *'inc'*
 'include' 'inc'		string	(default "^\s*#\s*include")
@@ -4210,6 +4226,7 @@ A jump table for the options with a shor
 
 	The expression will be evaluated in the |sandbox| when set from a
 	modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'includeexpr' |textlock|.
@@ -4297,6 +4314,7 @@ A jump table for the options with a shor
 
 	The expression will be evaluated in the |sandbox| when set from a
 	modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'indentexpr' |textlock|.
@@ -4893,6 +4911,12 @@ A jump table for the options with a shor
 <	This option cannot be set from a |modeline| or in the |sandbox|, for
 	security reasons.
 
+						*'makespellmem'* *'msm'*
+'makespellmem' 'msm'		string	(default "460000,2000,500")
+			global
+	Values relevant only when compressing a spell file, see |spell|.
+	This option cannot be set from a |modeline| or in the |sandbox|.
+
 						*'matchpairs'* *'mps'*
 'matchpairs' 'mps'	string	(default "(:),{:},[:]")
 			local to buffer
@@ -4915,7 +4939,6 @@ A jump table for the options with a shor
 						*'matchtime'* *'mat'*
 'matchtime' 'mat'	number	(default 5)
 			global
-			{in Nvi}
 	Tenths of a second to show the matching paren, when 'showmatch' is
 	set.  Note that this is not in milliseconds, like other options that
 	set a time.  This is to be compatible with Nvi.
@@ -5049,6 +5072,17 @@ A jump table for the options with a shor
 'modeline' 'ml'		boolean	(Vim default: on (off for root),
 				 Vi default: off)
 			local to buffer
+	If 'modeline' is on 'modelines' gives the number of lines that is
+	checked for set commands.  If 'modeline' is off or 'modelines' is zero
+	no lines are checked.  See |modeline|.
+
+			   *'modelineexpr'* *'mle'* *'nomodelineexpr'* *'nomle'*
+'modelineexpr' 'mle'	boolean (default: off)
+			global
+	When on allow some options that are an expression to be set in the
+	modeline.  Check the option for whether it is affected by
+	'modelineexpr'.  Also see |modeline|.
+
 						*'modelines'* *'mls'*
 'modelines' 'mls'	number	(default 5)
 			global
@@ -5059,9 +5093,9 @@ A jump table for the options with a shor
 	set and to the Vim default value when 'compatible' is reset.
 
 				*'modifiable'* *'ma'* *'nomodifiable'* *'noma'*
+				*E21*
 'modifiable' 'ma'	boolean	(default on)
 			local to buffer
-			*E21*
 	When off the buffer contents cannot be changed.  The 'fileformat' and
 	'fileencoding' options also can't be changed.
 	Can be reset on startup with the |-M| command line argument.
@@ -6058,6 +6092,8 @@ A jump table for the options with a shor
 	When this option is not empty, it determines the content of the ruler
 	string, as displayed for the 'ruler' option.
 	The format of this option is like that of 'statusline'.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
+
 	The default ruler width is 17 characters.  To make the ruler 15
 	characters wide, put "%15(" at the start and "%)" at the end.
 	Example: >
@@ -6598,7 +6634,8 @@ A jump table for the options with a shor
 		"Pattern not found", "Back at original", etc.
 	  q	use "recording" instead of "recording @a"
 	  F	don't give the file info when editing a file, like `:silent`
-		was used for the command
+		was used for the command; note that this also affects messages
+		from autocommands
 	  S     do not show search count message when searching, e.g.
 	        "[1/5]"
 
@@ -7165,6 +7202,7 @@ A jump table for the options with a shor
 
 	The 'statusline' option will be evaluated in the |sandbox| if set from
 	a modeline, see |sandbox-option|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	It is not allowed to change text or jump to another window while
 	evaluating 'statusline' |textlock|.
@@ -7345,6 +7383,7 @@ A jump table for the options with a shor
 
 	When changing something that is used in 'tabline' that does not
 	trigger it to be updated, use |:redrawtabline|.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
 
 	Keep in mind that only one of the tab pages is the current one, others
 	are invisible and you can't jump to their windows.
@@ -7873,8 +7912,11 @@ A jump table for the options with a shor
 	non-empty 't_ts' option).
 	When Vim was compiled with HAVE_X11 defined, the original title will
 	be restored if possible, see |X11|.
+
 	When this option contains printf-style '%' items, they will be
 	expanded according to the rules used for 'statusline'.
+	This option cannot be set in a modeline when 'modelineexpr' is off.
+
 	Example: >
     :auto BufEnter * let &titlestring = hostname() . "/" . expand("%:p")
     :set title titlestring=%<%F%=%l/%L-%P titlelen=70
@@ -8060,6 +8102,8 @@ A jump table for the options with a shor
 	undo file that exists is used.  When it cannot be read an error is
 	given, no further entry is used.
 	See |undo-persistence|.
+	This option cannot be set from a |modeline| or in the |sandbox|, for
+	security reasons.
 
 				*'undofile'* *'noundofile'* *'udf'* *'noudf'*
 'undofile' 'udf'	boolean	(default off)
@@ -8369,6 +8413,8 @@ A jump table for the options with a shor
 	When equal to "NONE" no viminfo file will be read or written.
 	This option can be set with the |-i| command line flag.  The |--clean|
 	command line flag sets it to "NONE".
+	This option cannot be set from a |modeline| or in the |sandbox|, for
+	security reasons.
 
 					    *'virtualedit'* *'ve'*
 'virtualedit' 've'	string	(default "")
--- a/src/option.c
+++ b/src/option.c
@@ -467,6 +467,7 @@ struct vimoption
 				  * there is a redraw flag */
 #define P_NDNAME      0x8000000L /* only normal dir name chars allowed */
 #define P_RWINONLY   0x10000000L /* only redraw current window */
+#define P_MLE	     0x20000000L /* under control of 'modelineexpr' */
 
 #define ISK_LATIN1  (char_u *)"@,48-57,_,192-255"
 
@@ -650,7 +651,7 @@ static struct vimoption options[] =
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"balloonexpr", "bexpr", P_STRING|P_ALLOCED|P_VI_DEF|P_VIM,
+    {"balloonexpr", "bexpr", P_STRING|P_ALLOCED|P_VI_DEF|P_VIM|P_MLE,
 #if defined(FEAT_BEVAL) && defined(FEAT_EVAL)
 			    (char_u *)&p_bexpr, PV_BEXPR,
 			    {(char_u *)"", (char_u *)0L}
@@ -727,7 +728,7 @@ static struct vimoption options[] =
 			    (char_u *)&p_cmp, PV_NONE,
 			    {(char_u *)"internal,keepascii", (char_u *)0L}
 			    SCTX_INIT},
-    {"cdpath",	    "cd",   P_STRING|P_EXPAND|P_VI_DEF|P_COMMA|P_NODUP,
+    {"cdpath",	    "cd",   P_STRING|P_EXPAND|P_VI_DEF|P_SECURE|P_COMMA|P_NODUP,
 #ifdef FEAT_SEARCHPATH
 			    (char_u *)&p_cdpath, PV_NONE,
 			    {(char_u *)",,", (char_u *)0L}
@@ -1175,7 +1176,7 @@ static struct vimoption options[] =
 			    {(char_u *)NULL, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"foldexpr",    "fde",  P_STRING|P_ALLOCED|P_VIM|P_VI_DEF|P_RWIN,
+    {"foldexpr",    "fde",  P_STRING|P_ALLOCED|P_VIM|P_VI_DEF|P_RWIN|P_MLE,
 #if defined(FEAT_FOLDING) && defined(FEAT_EVAL)
 			    (char_u *)VAR_WIN, PV_FDE,
 			    {(char_u *)"0", (char_u *)NULL}
@@ -1258,7 +1259,7 @@ static struct vimoption options[] =
 			    {(char_u *)NULL, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"foldtext",    "fdt",  P_STRING|P_ALLOCED|P_VIM|P_VI_DEF|P_RWIN,
+    {"foldtext",    "fdt",  P_STRING|P_ALLOCED|P_VIM|P_VI_DEF|P_RWIN|P_MLE,
 #if defined(FEAT_FOLDING) && defined(FEAT_EVAL)
 			    (char_u *)VAR_WIN, PV_FDT,
 			    {(char_u *)"foldtext()", (char_u *)NULL}
@@ -1267,7 +1268,7 @@ static struct vimoption options[] =
 			    {(char_u *)NULL, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"formatexpr", "fex",   P_STRING|P_ALLOCED|P_VI_DEF|P_VIM,
+    {"formatexpr", "fex",   P_STRING|P_ALLOCED|P_VI_DEF|P_VIM|P_MLE,
 #ifdef FEAT_EVAL
 			    (char_u *)&p_fex, PV_FEX,
 			    {(char_u *)"", (char_u *)0L}
@@ -1406,7 +1407,7 @@ static struct vimoption options[] =
 			    (char_u *)NULL, PV_NONE,
 #endif
 			    {(char_u *)TRUE, (char_u *)0L} SCTX_INIT},
-    {"guitablabel",  "gtl", P_STRING|P_VI_DEF|P_RWIN,
+    {"guitablabel",  "gtl", P_STRING|P_VI_DEF|P_RWIN|P_MLE,
 #if defined(FEAT_GUI_TABLINE)
 			    (char_u *)&p_gtl, PV_NONE,
 			    {(char_u *)"", (char_u *)0L}
@@ -1477,7 +1478,7 @@ static struct vimoption options[] =
 			    (char_u *)NULL, PV_NONE,
 #endif
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
-    {"iconstring",  NULL,   P_STRING|P_VI_DEF,
+    {"iconstring",  NULL,   P_STRING|P_VI_DEF|P_MLE,
 #ifdef FEAT_TITLE
 			    (char_u *)&p_iconstring, PV_NONE,
 #else
@@ -1549,7 +1550,7 @@ static struct vimoption options[] =
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"includeexpr", "inex", P_STRING|P_ALLOCED|P_VI_DEF,
+    {"includeexpr", "inex", P_STRING|P_ALLOCED|P_VI_DEF|P_MLE,
 #if defined(FEAT_FIND_ID) && defined(FEAT_EVAL)
 			    (char_u *)&p_inex, PV_INEX,
 			    {(char_u *)"", (char_u *)0L}
@@ -1561,7 +1562,7 @@ static struct vimoption options[] =
     {"incsearch",   "is",   P_BOOL|P_VI_DEF|P_VIM,
 			    (char_u *)&p_is, PV_NONE,
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
-    {"indentexpr", "inde",  P_STRING|P_ALLOCED|P_VI_DEF|P_VIM,
+    {"indentexpr", "inde",  P_STRING|P_ALLOCED|P_VI_DEF|P_VIM|P_MLE,
 #if defined(FEAT_CINDENT) && defined(FEAT_EVAL)
 			    (char_u *)&p_inde, PV_INDE,
 			    {(char_u *)"", (char_u *)0L}
@@ -1888,6 +1889,9 @@ static struct vimoption options[] =
     {"modeline",    "ml",   P_BOOL|P_VIM,
 			    (char_u *)&p_ml, PV_ML,
 			    {(char_u *)FALSE, (char_u *)TRUE} SCTX_INIT},
+    {"modelineexpr", "mle",  P_BOOL|P_VI_DEF,
+			    (char_u *)&p_mle, PV_NONE,
+			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
     {"modelines",   "mls",  P_NUM|P_VI_DEF,
 			    (char_u *)&p_mls, PV_NONE,
 			    {(char_u *)5L, (char_u *)0L} SCTX_INIT},
@@ -2311,7 +2315,7 @@ static struct vimoption options[] =
 			    (char_u *)NULL, PV_NONE,
 #endif
 			    {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
-    {"rulerformat", "ruf",  P_STRING|P_VI_DEF|P_ALLOCED|P_RSTAT,
+    {"rulerformat", "ruf",  P_STRING|P_VI_DEF|P_ALLOCED|P_RSTAT|P_MLE,
 #ifdef FEAT_STL_OPT
 			    (char_u *)&p_ruf, PV_NONE,
 #else
@@ -2577,7 +2581,7 @@ static struct vimoption options[] =
     {"startofline", "sol",  P_BOOL|P_VI_DEF|P_VIM,
 			    (char_u *)&p_sol, PV_NONE,
 			    {(char_u *)TRUE, (char_u *)0L} SCTX_INIT},
-    {"statusline"  ,"stl",  P_STRING|P_VI_DEF|P_ALLOCED|P_RSTAT,
+    {"statusline"  ,"stl",  P_STRING|P_VI_DEF|P_ALLOCED|P_RSTAT|P_MLE,
 #ifdef FEAT_STL_OPT
 			    (char_u *)&p_stl, PV_STL,
 #else
@@ -2624,7 +2628,7 @@ static struct vimoption options[] =
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"tabline",	    "tal",  P_STRING|P_VI_DEF|P_RALL,
+    {"tabline",	    "tal",  P_STRING|P_VI_DEF|P_RALL|P_MLE,
 #ifdef FEAT_STL_OPT
 			    (char_u *)&p_tal, PV_NONE,
 #else
@@ -2802,7 +2806,7 @@ static struct vimoption options[] =
 			    {(char_u *)0L, (char_u *)0L}
 #endif
 			    SCTX_INIT},
-    {"titlestring", NULL,   P_STRING|P_VI_DEF,
+    {"titlestring", NULL,   P_STRING|P_VI_DEF|P_MLE,
 #ifdef FEAT_TITLE
 			    (char_u *)&p_titlestring, PV_NONE,
 #else
@@ -4549,6 +4553,11 @@ do_set(
 		    errmsg = _("E520: Not allowed in a modeline");
 		    goto skip;
 		}
+		if ((flags & P_MLE) && !p_mle)
+		{
+		    errmsg = _("E992: Not allowed in a modeline when 'modelineexpr' is off");
+		    goto skip;
+		}
 #ifdef FEAT_DIFF
 		/* In diff mode some options are overruled.  This avoids that
 		 * 'foldmethod' becomes "marker" instead of "diff" and that
--- a/src/option.h
+++ b/src/option.h
@@ -631,6 +631,7 @@ EXTERN long	p_mis;		/* 'menuitems' */
 #ifdef FEAT_SPELL
 EXTERN char_u	*p_msm;		/* 'mkspellmem' */
 #endif
+EXTERN long	p_mle;		/* 'modelineexpr' */
 EXTERN long	p_mls;		/* 'modelines' */
 EXTERN char_u	*p_mouse;	/* 'mouse' */
 #ifdef FEAT_GUI
--- a/src/testdir/test49.in
+++ b/src/testdir/test49.in
@@ -5,7 +5,7 @@ test49.failed, try to add one or more "G
 
 STARTTEST
 :so small.vim
-:se nocp nomore viminfo+=nviminfo
+:se nocp nomore viminfo+=nviminfo modelineexpr
 :lang mess C
 :so test49.vim
 :" Go back to this file and append the results from register r.
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -60,14 +60,17 @@ func Test_modeline_keymap()
   set keymap= iminsert=0 imsearch=-1
 endfunc
 
-func s:modeline_fails(what, text)
+func s:modeline_fails(what, text, error)
+  if !exists('+' .. a:what)
+    return
+  endif
   let fname = "Xmodeline_fails_" . a:what
   call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname)
   let modeline = &modeline
   set modeline
   filetype plugin on
   syntax enable
-  call assert_fails('split ' . fname, 'E474:')
+  call assert_fails('split ' . fname, a:error)
   call assert_equal("", &filetype)
   call assert_equal("", &syntax)
 
@@ -79,16 +82,90 @@ func s:modeline_fails(what, text)
 endfunc
 
 func Test_modeline_filetype_fails()
-  call s:modeline_fails('filetype', 'ft=evil$CMD')
+  call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:')
 endfunc
 
 func Test_modeline_syntax_fails()
-  call s:modeline_fails('syntax', 'syn=evil$CMD')
+  call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:')
 endfunc
 
 func Test_modeline_keymap_fails()
-  if !has('keymap')
-    return
-  endif
-  call s:modeline_fails('keymap', 'keymap=evil$CMD')
+  call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:')
 endfunc
+
+func Test_modeline_fails_always()
+  call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:')
+  call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:')
+  call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:')
+  call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:')
+  call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:')
+  call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:')
+  call s:modeline_fails('directory', 'directory=Something()', 'E520:')
+  call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
+  call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
+  call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
+  call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
+  call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
+  call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')
+  call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:')
+  call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:')
+  call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:')
+  call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:')
+  call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:')
+  call s:modeline_fails('langmap', 'langmap=Something()', 'E520:')
+  call s:modeline_fails('luadll', 'luadll=Something()', 'E520:')
+  call s:modeline_fails('makeef', 'makeef=Something()', 'E520:')
+  call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:')
+  call s:modeline_fails('makespellmem', 'makespellmem=Something()', 'E520:')
+  call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
+  call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
+  call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
+  call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
+  call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
+  call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:')
+  call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:')
+  call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:')
+  call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:')
+  call s:modeline_fails('pythonhome', 'pythondll=Something()', 'E520:')
+  call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:')
+  call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:')
+  call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:')
+  call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:')
+  call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:')
+  call s:modeline_fails('secure', 'secure=Something()', 'E520:')
+  call s:modeline_fails('shell', 'shell=Something()', 'E520:')
+  call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:')
+  call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:')
+  call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:')
+  call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:')
+  call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:')
+  call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:')
+  call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:')
+  call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:')
+  call s:modeline_fails('titleold', 'titleold=Something()', 'E520:')
+  call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:')
+  call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:')
+  call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:')
+  call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:')
+  call s:modeline_fails('undodir', 'undodir=Something()', 'E520:')
+  " only check a few terminal options
+  call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:')
+  call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:')
+  call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:')
+  call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:')
+endfunc
+
+func Test_modeline_fails_modelineexpr()
+  call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:')
+  call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:')
+  call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:')
+  call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:')
+  call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:')
+  call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:')
+  call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:')
+  call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:')
+  call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:')
+  call s:modeline_fails('statusline', 'statusline=Something()', 'E992:')
+  call s:modeline_fails('tabline', 'tabline=Something()', 'E992:')
+  call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -768,6 +768,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1366,
+/**/
     1365,
 /**/
     1364,