Bug#931143: unblock: neovim/0.3.4-3
Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
Please unblock package neovim
This upload contains the rest of the fixes needed to address
CVE-2019-12735/#930024.
unblock neovim/0.3.4-3
-- System Information:
Debian Release: 10.0
APT prefers unstable-debug
APT policy: (500, 'unstable-debug'), (500, 'unstable'), (1, 'experimental-debug'), (1, 'experimental')
Architecture: amd64 (x86_64)
Kernel: Linux 4.19.0-5-amd64 (SMP w/4 CPU cores)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE=en_US.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled
diffstat for neovim-0.3.4 neovim-0.3.4
changelog | 28
patches/0001-debcherry-fixup-patch.patch | 1066 ++++++++++
patches/0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch | 36
patches/0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch | 36
patches/0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch | 104
patches/0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch | 41
patches/0005-vim-patch-8.1.0206-duplicate-test-function-name.patch | 35
patches/0006-vim-patch-8.1.1382-error-when-editing-test-file.patch | 59
patches/0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch | 57
patches/series | 8
10 files changed, 1433 insertions(+), 37 deletions(-)
diff -Nru neovim-0.3.4/debian/changelog neovim-0.3.4/debian/changelog
--- neovim-0.3.4/debian/changelog 2019-06-05 21:38:14.000000000 -0400
+++ neovim-0.3.4/debian/changelog 2019-06-26 21:21:33.000000000 -0400
@@ -1,3 +1,31 @@
+neovim (0.3.4-3) unstable; urgency=high
+
+ * Backport additional changes to address CVE-2019-12735 (Closes: #930024)
+ + vim-patch:8.1.0177: defining function in sandbox is inconsistent
+ + vim-patch:8.1.0189: function defined in sandbox not tested
+ + vim-patch:8.1.0538: evaluating a modeline might invoke using a shell
+ command
+ + vim-patch:8.1.0539: cannot build without the sandbox
+ + vim-patch:8.1.0540: may evaluate insecure value when appending to option
+ + vim-patch:8.1.0544: setting 'filetype' in a modeline causes an error
+ + vim-patch:8.1.0613: when executing an insecure function the secure flag
+ is stuck
+ + vim-patch:8.1.1046: the "secure" variable is used inconsistently
+ + vim-patch:8.1.0205: invalid memory access with invalid modeline
+ + vim-patch:8.1.0206: duplicate test function name
+ + vim-patch:8.1.0506: modeline test fails when run by root
+ + vim-patch:8.1.0546: modeline test with keymap fails
+ + vim-patch:8.1.0547: modeline test with keymap still fails
+ + vim-patch:8.1.1366: using expressions in a modeline is unsafe
+ + vim-patch:8.1.1367: can set 'modelineexpr' in modeline
+ + vim-patch:8.1.1368: modeline test fails with python but without
+ pythonhome
+ + vim-patch:8.1.1382: error when editing test file
+ + vim-patch:8.1.1401: misspelled mkspellmem as makespellmem
+ * Backport patch to prevent use of nvim's API within the sandbox
+
+ -- James McCoy <jamessan@debian.org> Wed, 26 Jun 2019 21:21:33 -0400
+
neovim (0.3.4-2) unstable; urgency=high
[ Efraim Flashner ]
diff -Nru neovim-0.3.4/debian/patches/0001-debcherry-fixup-patch.patch neovim-0.3.4/debian/patches/0001-debcherry-fixup-patch.patch
--- neovim-0.3.4/debian/patches/0001-debcherry-fixup-patch.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0001-debcherry-fixup-patch.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,1066 @@
+From d39c384696e94bd8cb4a8830f0ec2e801619a970 Mon Sep 17 00:00:00 2001
+From: James McCoy <jamessan@jamessan.com>
+Date: Wed, 26 Jun 2019 21:32:44 -0400
+Subject: [PATCH 1/7] debcherry fixup patch
+
+ed179f931 vim-patch:8.1.1401: misspelled mkspellmem as makespellmem
+ - no changes against upstream or conflicts
+41a3ff9fe vim-patch:8.1.1368: modeline test fails with python but without pythonhome
+ - no changes against upstream or conflicts
+12c5b6885 vim-patch:8.1.1367: can set 'modelineexpr' in modeline
+ - no changes against upstream or conflicts
+cffc3f5f8 vim-patch:8.1.1366: using expressions in a modeline is unsafe
+ - extra changes or conflicts
+a15defc3c vim-patch:8.1.0547: modeline test with keymap still fails
+ - extra changes or conflicts
+c550a5e94 vim-patch:8.1.0546: modeline test with keymap fails
+ - no changes against upstream or conflicts
+0605eb856 vim-patch:8.1.0506: modeline test fails when run by root
+ - no changes against upstream or conflicts
+cbec04e98 vim-patch:8.1.0205: invalid memory access with invalid modeline
+ - extra changes or conflicts
+ed7ca8f1e vim-patch:8.1.1046: the "secure" variable is used inconsistently
+ - no changes against upstream or conflicts
+4f223fb12 vim-patch:8.1.0613: when executing an insecure function the secure flag is stuck
+ - extra changes or conflicts
+4fc181350 fixup: use vim_snprintf, ASCII_ISALNUM
+ - no changes against upstream or conflicts
+2789ea195 vim-patch:8.1.0544: setting 'filetype' in a modeline causes an error
+ - extra changes or conflicts
+94b32dd17 vim-patch:8.1.0540: may evaluate insecure value when appending to option
+ - extra changes or conflicts
+a36f7e776 vim-patch:8.1.0539: cannot build without the sandbox
+ - extra changes or conflicts
+b1ab0ee1a vim-patch:8.1.0538: evaluating a modeline might invoke using a shell command
+ - extra changes or conflicts
+bc4e31de5 vim-patch:8.0.0003
+ - no changes against upstream or conflicts
+53bde37a8 vim-patch:8.0.0376
+ - extra changes or conflicts
+aa0c704e7 vim-patch:8.0.0322
+ - extra changes or conflicts
+29b888573 vim-patch:8.0.0057
+ - no changes against upstream or conflicts
+fb1670cdd vim-patch:8.0.0056
+ - extra changes or conflicts
+---
+ runtime/doc/options.txt | 75 +++++++++---
+ src/nvim/buffer.c | 6 +
+ src/nvim/generators/gen_options.lua | 1 +
+ src/nvim/option.c | 177 +++++++++++++++++++---------
+ src/nvim/option_defs.h | 1 +
+ src/nvim/options.lua | 21 ++++
+ src/nvim/testdir/test49.in | 2 +-
+ src/nvim/testdir/test_alot.vim | 1 +
+ src/nvim/testdir/test_autocmd.vim | 23 ++++
+ src/nvim/testdir/test_modeline.vim | 173 +++++++++++++++++++++++++++
+ 10 files changed, 406 insertions(+), 74 deletions(-)
+ create mode 100644 src/nvim/testdir/test_modeline.vim
+
+diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
+index 534b2025c..e8471d561 100644
+--- a/runtime/doc/options.txt
++++ b/runtime/doc/options.txt
+@@ -478,14 +478,17 @@ backslash in front of the ':' will be removed. Example:
+ /* 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
+@@ -2439,7 +2442,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+ 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|.
+@@ -2554,6 +2557,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+
+ 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|.
+@@ -2589,16 +2593,8 @@ A jump table for the options with a short description can be found at |Q_op|.
+ 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.
+-
+- *'formatoptions'* *'fo'*
+-'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt")
+- local to buffer
+- This is a sequence of letters which describes how automatic
+- formatting is to be done. See |fo-table|. When the 'paste' option is
+- on, no formatting is done (like 'formatoptions' is empty). Commas can
+- be inserted for readability.
+- To avoid problems with flags that are added in the future, use the
+- "+=" and "-=" feature of ":set" |add-option-flags|.
++ This option cannot be set in a modeline when 'modelineexpr' is off.
++ NOTE: This option is set to "" when 'compatible' is set.
+
+ *'formatlistpat'* *'flp'*
+ 'formatlistpat' 'flp' string (default: "^\s*\d\+[\]:.)}\t ]\s*")
+@@ -2613,6 +2609,16 @@ A jump table for the options with a short description can be found at |Q_op|.
+ The default recognizes a number, followed by an optional punctuation
+ character and white space.
+
++ *'formatoptions'* *'fo'*
++'formatoptions' 'fo' string (default: "tcqj", Vi default: "vt")
++ local to buffer
++ This is a sequence of letters which describes how automatic
++ formatting is to be done. See |fo-table|. When the 'paste' option is
++ on, no formatting is done (like 'formatoptions' is empty). Commas can
++ be inserted for readability.
++ To avoid problems with flags that are added in the future, use the
++ "+=" and "-=" feature of ":set" |add-option-flags|.
++
+ *'formatprg'* *'fp'*
+ 'formatprg' 'fp' string (default "")
+ global or local to buffer |global-local|
+@@ -2643,6 +2649,9 @@ A jump table for the options with a short description can be found at |Q_op|.
+ - system signals low battery life
+ - Nvim exits abnormally
+
++ This option cannot be set from a |modeline| or in the |sandbox|, for
++ security reasons.
++
+ *'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
+ 'gdefault' 'gd' boolean (default off)
+ global
+@@ -2978,6 +2987,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+ '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
+@@ -3106,6 +3116,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+ 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.
+
+ *'ignorecase'* *'ic'* *'noignorecase'* *'noic'*
+ 'ignorecase' 'ic' boolean (default off)
+@@ -3209,6 +3220,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+
+ 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|.
+@@ -3277,6 +3289,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+
+ 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|.
+@@ -3879,10 +3892,23 @@ A jump table for the options with a short description can be found at |Q_op|.
+ < If you have less than 512 Mbyte |:mkspell| may fail for some
+ languages, no matter what you set 'mkspellmem' to.
+
++ This option cannot be set from a |modeline| or in the |sandbox|.
++
+ *'modeline'* *'ml'* *'nomodeline'* *'noml'*
+ '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
+@@ -4622,6 +4648,8 @@ A jump table for the options with a short description can be found at |Q_op|.
+ 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: >
+@@ -5252,7 +5280,8 @@ A jump table for the options with a short description can be found at |Q_op|.
+ "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
+
+ This gives you the opportunity to avoid that a change between buffers
+ requires you to hit <Enter>, but still gives as useful a message as
+@@ -5527,7 +5556,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+
+ After this option has been set successfully, Vim will source the files
+ "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang'
+- up to the first comma, dot or underscore.
++ up to the first character that is not an ASCII letter and not a dash.
+ Also see |set-spc-auto|.
+
+
+@@ -5773,6 +5802,7 @@ A jump table for the options with a short description can be found at |Q_op|.
+
+ 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|.
+@@ -5927,6 +5957,8 @@ A jump table for the options with a short description can be found at |Q_op|.
+ the text to be displayed. Use "%1T" for the first label, "%2T" for
+ the second one, etc. Use "%X" items for closing labels.
+
++ 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.
+
+@@ -6203,8 +6235,11 @@ A jump table for the options with a short description can be found at |Q_op|.
+ global
+ When this option is not empty, it will be used for the title of the
+ window. This happens only when the 'title' option is on.
++
+ 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
+@@ -6238,6 +6273,8 @@ A jump table for the options with a short description can be found at |Q_op|.
+ 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)
+diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
+index 8b107041b..99e910d15 100644
+--- a/src/nvim/buffer.c
++++ b/src/nvim/buffer.c
+@@ -4923,9 +4923,15 @@ chk_modeline (
+ *e = NUL; /* truncate the set command */
+
+ if (*s != NUL) { /* skip over an empty "::" */
++ const int secure_save = secure;
+ save_SID = current_SID;
+ current_SID = SID_MODELINE;
++ // Make sure no risky things are executed as a side effect.
++ secure = 1;
++
+ retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags);
++
++ secure = secure_save;
+ current_SID = save_SID;
+ if (retval == FAIL) /* stop if error found */
+ break;
+diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
+index fdc00d5dc..d9c65e17c 100644
+--- a/src/nvim/generators/gen_options.lua
++++ b/src/nvim/generators/gen_options.lua
+@@ -79,6 +79,7 @@ local get_flags = function(o)
+ {'pri_mkrc'},
+ {'deny_in_modelines', 'P_NO_ML'},
+ {'deny_duplicates', 'P_NODUP'},
++ {'modelineexpr', 'P_MLE'},
+ }) do
+ local key_name = flag_desc[1]
+ local def_name = flag_desc[2] or ('P_' .. key_name:upper())
+diff --git a/src/nvim/option.c b/src/nvim/option.c
+index 8c692f9f4..d36a5de20 100644
+--- a/src/nvim/option.c
++++ b/src/nvim/option.c
+@@ -251,6 +251,7 @@ typedef struct vimoption {
+ #define P_RWINONLY 0x10000000U ///< only redraw current window
+ #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed
+ #define P_UI_OPTION 0x40000000U ///< send option to remote ui
++#define P_MLE 0x80000000U ///< under control of 'modelineexpr'
+
+ #define HIGHLIGHT_INIT \
+ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \
+@@ -1200,7 +1201,7 @@ do_set (
+ }
+ len++;
+ if (opt_idx == -1) {
+- key = find_key_option(arg + 1);
++ key = find_key_option(arg + 1, true);
+ }
+ } else {
+ len = 0;
+@@ -1214,7 +1215,7 @@ do_set (
+ }
+ opt_idx = findoption_len((const char *)arg, (size_t)len);
+ if (opt_idx == -1) {
+- key = find_key_option(arg);
++ key = find_key_option(arg, false);
+ }
+ }
+
+@@ -1281,6 +1282,11 @@ do_set (
+ errmsg = (char_u *)_("E520: Not allowed in a modeline");
+ goto skip;
+ }
++ if ((flags & P_MLE) && !p_mle) {
++ errmsg = (char_u *)_(
++ "E992: Not allowed in a modeline when 'modelineexpr' is off");
++ goto skip;
++ }
+ /* In diff mode some options are overruled. This avoids that
+ * 'foldmethod' becomes "marker" instead of "diff" and that
+ * "wrap" gets set. */
+@@ -1355,6 +1361,10 @@ do_set (
+ && nextchar != NUL && !ascii_iswhite(afterchar))
+ errmsg = e_trailing;
+ } else {
++
++ int value_is_replaced = !prepending && !adding && !removing;
++ int value_checked = false;
++
+ if (flags & P_BOOL) { /* boolean */
+ if (nextchar == '=' || nextchar == ':') {
+ errmsg = e_invarg;
+@@ -1774,12 +1784,32 @@ do_set (
+ // buffer is closed by autocommands.
+ saved_newval = (newval != NULL) ? xstrdup((char *)newval) : 0;
+
+- // Handle side effects, and set the global value for
+- // ":set" on local options. Note: when setting 'syntax'
+- // or 'filetype' autocommands may be triggered that can
+- // cause havoc.
+- errmsg = did_set_string_option(opt_idx, (char_u **)varp,
+- new_value_alloced, oldval, errbuf, opt_flags);
++ {
++ uint32_t *p = insecure_flag(opt_idx, opt_flags);
++ const int secure_saved = secure;
++
++ // When an option is set in the sandbox, from a
++ // modeline or in secure mode, then deal with side
++ // effects in secure mode. Also when the value was
++ // set with the P_INSECURE flag and is not
++ // completely replaced.
++ if ((opt_flags & OPT_MODELINE)
++ || sandbox != 0
++ || (!value_is_replaced && (*p & P_INSECURE))) {
++ secure = 1;
++ }
++
++ // Handle side effects, and set the global value
++ // for ":set" on local options. Note: when setting
++ // 'syntax' or 'filetype' autocommands may be
++ // triggered that can cause havoc.
++ errmsg = did_set_string_option(opt_idx, (char_u **)varp,
++ new_value_alloced, oldval,
++ errbuf, sizeof(errbuf),
++ opt_flags, &value_checked);
++
++ secure = secure_saved;
++ }
+
+ if (errmsg == NULL) {
+ if (!starting) {
+@@ -1806,8 +1836,7 @@ do_set (
+ }
+
+ if (opt_idx >= 0)
+- did_set_option(opt_idx, opt_flags,
+- !prepending && !adding && !removing);
++ did_set_option(opt_idx, opt_flags, value_is_replaced, value_checked);
+ }
+
+ skip:
+@@ -1872,7 +1901,9 @@ static void
+ did_set_option (
+ int opt_idx,
+ int opt_flags, /* possibly with OPT_MODELINE */
+- int new_value /* value was replaced completely */
++ int new_value, /* value was replaced completely */
++ int value_checked /* value was checked to be safe, no need to
++ set P_INSECURE */
+ )
+ {
+ options[opt_idx].flags |= P_WAS_SET;
+@@ -1881,20 +1912,22 @@ did_set_option (
+ * set the P_INSECURE flag. Otherwise, if a new value is stored reset the
+ * flag. */
+ uint32_t *p = insecure_flag(opt_idx, opt_flags);
+- if (secure
+- || sandbox != 0
+- || (opt_flags & OPT_MODELINE))
++ if (!value_checked && (secure
++ || sandbox != 0
++ || (opt_flags & OPT_MODELINE))) {
+ *p = *p | P_INSECURE;
+- else if (new_value)
++ } else if (new_value) {
+ *p = *p & ~P_INSECURE;
++ }
+ }
+
+-static char_u *illegal_char(char_u *errbuf, int c)
++static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c)
+ {
+- if (errbuf == NULL)
++ if (errbuf == NULL) {
+ return (char_u *)"";
+- sprintf((char *)errbuf, _("E539: Illegal character <%s>"),
+- (char *)transchar(c));
++ }
++ vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"),
++ (char *)transchar(c));
+ return errbuf;
+ }
+
+@@ -1904,10 +1937,12 @@ static char_u *illegal_char(char_u *errbuf, int c)
+ */
+ static int string_to_key(char_u *arg)
+ {
+- if (*arg == '<')
+- return find_key_option(arg + 1);
+- if (*arg == '^')
++ if (*arg == '<') {
++ return find_key_option(arg + 1, true);
++ }
++ if (*arg == '^') {
+ return Ctrl_chr(arg[1]);
++ }
+ return *arg;
+ }
+
+@@ -2383,10 +2418,12 @@ static char *set_string_option(const int opt_idx, const char *const value,
+ char *const saved_oldval = xstrdup(oldval);
+ char *const saved_newval = xstrdup(s);
+
++ int value_checked = false;
+ char *const r = (char *)did_set_string_option(
+- opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, NULL, opt_flags);
++ opt_idx, (char_u **)varp, (int)true, (char_u *)oldval,
++ NULL, 0, opt_flags, &value_checked);
+ if (r == NULL) {
+- did_set_option(opt_idx, opt_flags, true);
++ did_set_option(opt_idx, opt_flags, true, value_checked);
+ }
+
+ // call autocommand after handling side effects
+@@ -2427,13 +2464,16 @@ static bool valid_filetype(char_u *val)
+ * Returns NULL for success, or an error message for an error.
+ */
+ static char_u *
+-did_set_string_option (
+- int opt_idx, /* index in options[] table */
+- char_u **varp, /* pointer to the option variable */
+- int new_value_alloced, /* new value was allocated */
+- char_u *oldval, /* previous value of the option */
+- char_u *errbuf, /* buffer for errors, or NULL */
+- int opt_flags /* OPT_LOCAL and/or OPT_GLOBAL */
++did_set_string_option(
++ int opt_idx, // index in options[] table
++ char_u **varp, // pointer to the option variable
++ int new_value_alloced, // new value was allocated
++ char_u *oldval, // previous value of the option
++ char_u *errbuf, // buffer for errors, or NULL
++ size_t errbuflen, // length of errors buffer
++ int opt_flags, // OPT_LOCAL and/or OPT_GLOBAL
++ int *value_checked // value was checked to be safe, no
++ // need to set P_INSECURE
+ )
+ {
+ char_u *errmsg = NULL;
+@@ -2651,8 +2691,20 @@ did_set_string_option (
+ if (!valid_filetype(*varp)) {
+ errmsg = e_invarg;
+ } else {
++ int secure_save = secure;
++
++ // Reset the secure flag, since the value of 'keymap' has
++ // been checked to be safe.
++ secure = 0;
++
+ // load or unload key mapping tables
+ errmsg = keymap_init();
++
++ secure = secure_save;
++
++ // Since we check the value, there is no need to set P_INSECURE,
++ // even when the value comes from a modeline.
++ *value_checked = true;
+ }
+
+ if (errmsg == NULL) {
+@@ -2738,7 +2790,7 @@ did_set_string_option (
+ while (*s && *s != ':') {
+ if (vim_strchr((char_u *)COM_ALL, *s) == NULL
+ && !ascii_isdigit(*s) && *s != '-') {
+- errmsg = illegal_char(errbuf, *s);
++ errmsg = illegal_char(errbuf, errbuflen, *s);
+ break;
+ }
+ ++s;
+@@ -2790,7 +2842,7 @@ did_set_string_option (
+ for (s = p_shada; *s; ) {
+ /* Check it's a valid character */
+ if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) {
+- errmsg = illegal_char(errbuf, *s);
++ errmsg = illegal_char(errbuf, errbuflen, *s);
+ break;
+ }
+ if (*s == 'n') { /* name is always last one */
+@@ -2810,9 +2862,9 @@ did_set_string_option (
+
+ if (!ascii_isdigit(*(s - 1))) {
+ if (errbuf != NULL) {
+- sprintf((char *)errbuf,
+- _("E526: Missing number after <%s>"),
+- transchar_byte(*(s - 1)));
++ vim_snprintf((char *)errbuf, errbuflen,
++ _("E526: Missing number after <%s>"),
++ transchar_byte(*(s - 1)));
+ errmsg = errbuf;
+ } else
+ errmsg = (char_u *)"";
+@@ -2990,7 +3042,7 @@ did_set_string_option (
+ if (!*s)
+ break;
+ if (vim_strchr((char_u *)".wbuksid]tU", *s) == NULL) {
+- errmsg = illegal_char(errbuf, *s);
++ errmsg = illegal_char(errbuf, errbuflen, *s);
+ break;
+ }
+ if (*++s != NUL && *s != ',' && *s != ' ') {
+@@ -3004,9 +3056,9 @@ did_set_string_option (
+ }
+ } else {
+ if (errbuf != NULL) {
+- sprintf((char *)errbuf,
+- _("E535: Illegal character after <%c>"),
+- *--s);
++ vim_snprintf((char *)errbuf, errbuflen,
++ _("E535: Illegal character after <%c>"),
++ *--s);
+ errmsg = errbuf;
+ } else
+ errmsg = (char_u *)"";
+@@ -3163,12 +3215,20 @@ did_set_string_option (
+ errmsg = e_invarg;
+ } else {
+ value_changed = STRCMP(oldval, *varp) != 0;
++
++ // Since we check the value, there is no need to set P_INSECURE,
++ // even when the value comes from a modeline.
++ *value_checked = true;
+ }
+ } else if (gvarp == &p_syn) {
+ if (!valid_filetype(*varp)) {
+ errmsg = e_invarg;
+ } else {
+ value_changed = STRCMP(oldval, *varp) != 0;
++
++ // Since we check the value, there is no need to set P_INSECURE,
++ // even when the value comes from a modeline.
++ *value_checked = true;
+ }
+ } else if (varp == &curwin->w_p_winhl) {
+ if (!parse_winhl_opt(curwin)) {
+@@ -3194,7 +3254,7 @@ did_set_string_option (
+ if (p != NULL) {
+ for (s = *varp; *s; ++s)
+ if (vim_strchr(p, *s) == NULL) {
+- errmsg = illegal_char(errbuf, *s);
++ errmsg = illegal_char(errbuf, errbuflen, *s);
+ break;
+ }
+ }
+@@ -3258,6 +3318,11 @@ did_set_string_option (
+ // already set to this value.
+ if (!(opt_flags & OPT_MODELINE) || value_changed) {
+ static int ft_recursive = 0;
++ int secure_save = secure;
++
++ // Reset the secure flag, since the value of 'filetype' has
++ // been checked to be safe.
++ secure = 0;
+
+ ft_recursive++;
+ did_filetype = true;
+@@ -3270,6 +3335,7 @@ did_set_string_option (
+ if (varp != &(curbuf->b_p_ft)) {
+ varp = NULL;
+ }
++ secure = secure_save;
+ }
+ }
+ if (varp == &(curwin->w_s->b_p_spl)) {
+@@ -3287,11 +3353,13 @@ did_set_string_option (
+ * '.encoding'.
+ */
+ for (p = q; *p != NUL; ++p)
+- if (vim_strchr((char_u *)"_.,", *p) != NULL)
++ if (!ASCII_ISALNUM(*p) && *p != '-')
+ break;
+- vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim",
+- (int)(p - q), q);
+- source_runtime(fname, DIP_ALL);
++ if (p > q) {
++ vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim",
++ (int)(p - q), q);
++ source_runtime(fname, DIP_ALL);
++ }
+ }
+ }
+
+@@ -3553,7 +3621,7 @@ char_u *check_stl_option(char_u *s)
+ continue;
+ }
+ if (vim_strchr(STL_ALL, *s) == NULL) {
+- return illegal_char(errbuf, *s);
++ return illegal_char(errbuf, sizeof(errbuf), *s);
+ }
+ if (*s == '{') {
+ s++;
+@@ -4892,19 +4960,20 @@ char *set_option_value(const char *const name, const long number,
+ return NULL;
+ }
+
+-/*
+- * Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
+- */
+-int find_key_option_len(const char_u *arg, size_t len)
++// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
++// When "has_lt" is true there is a '<' before "*arg_arg".
++// Returns 0 when the key is not recognized.
++int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt)
+ {
+- int key;
++ int key = 0;
+ int modifiers;
++ const char_u *arg = arg_arg;
+
+ // Don't use get_special_key_code() for t_xx, we don't want it to call
+ // add_termcap_entry().
+ if (len >= 4 && arg[0] == 't' && arg[1] == '_') {
+ key = TERMCAP2KEY(arg[2], arg[3]);
+- } else {
++ } else if (has_lt) {
+ arg--; // put arg at the '<'
+ modifiers = 0;
+ key = find_special_key(&arg, len + 1, &modifiers, true, true, false);
+@@ -4915,9 +4984,9 @@ int find_key_option_len(const char_u *arg, size_t len)
+ return key;
+ }
+
+-static int find_key_option(const char_u *arg)
++static int find_key_option(const char_u *arg, bool has_lt)
+ {
+- return find_key_option_len(arg, STRLEN(arg));
++ return find_key_option_len(arg, STRLEN(arg), has_lt);
+ }
+
+ /*
+diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
+index 6a0d0e32e..f64b07c27 100644
+--- a/src/nvim/option_defs.h
++++ b/src/nvim/option_defs.h
+@@ -503,6 +503,7 @@ EXTERN long p_mmd; // 'maxmapdepth'
+ EXTERN long p_mmp; // 'maxmempattern'
+ EXTERN long p_mis; // 'menuitems'
+ EXTERN char_u *p_msm; // 'mkspellmem'
++EXTERN long p_mle; // 'modelineexpr'
+ EXTERN long p_mls; // 'modelines'
+ EXTERN char_u *p_mouse; // 'mouse'
+ EXTERN char_u *p_mousem; // 'mousemodel'
+diff --git a/src/nvim/options.lua b/src/nvim/options.lua
+index 0cc6f58c5..7ca1b4d64 100644
+--- a/src/nvim/options.lua
++++ b/src/nvim/options.lua
+@@ -8,6 +8,7 @@
+ -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil},
+ -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil,
+ -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,
++-- modelineexpr=nil,
+ -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true,
+ -- alloced=nil,
+ -- save_pv_indir=nil,
+@@ -286,6 +287,7 @@ return {
+ deny_duplicates=true,
+ vi_def=true,
+ expand=true,
++ secure=true,
+ varname='p_cdpath',
+ defaults={if_true={vi=",,"}}
+ },
+@@ -856,6 +858,7 @@ return {
+ type='string', scope={'window'},
+ vi_def=true,
+ vim=true,
++ modelineexpr=true,
+ alloced=true,
+ redraw={'current_window'},
+ defaults={if_true={vi="0"}}
+@@ -931,6 +934,7 @@ return {
+ type='string', scope={'window'},
+ vi_def=true,
+ vim=true,
++ modelineexpr=true,
+ alloced=true,
+ redraw={'current_window'},
+ defaults={if_true={vi="foldtext()"}}
+@@ -940,6 +944,7 @@ return {
+ type='string', scope={'buffer'},
+ vi_def=true,
+ vim=true,
++ modelineexpr=true,
+ alloced=true,
+ varname='p_fex',
+ defaults={if_true={vi=""}}
+@@ -1053,6 +1058,7 @@ return {
+ full_name='guitablabel', abbreviation='gtl',
+ type='string', scope={'global'},
+ vi_def=true,
++ modelineexpr=true,
+ redraw={'current_window'},
+ enable_if=false,
+ },
+@@ -1143,6 +1149,7 @@ return {
+ full_name='iconstring',
+ type='string', scope={'global'},
+ vi_def=true,
++ modelineexpr=true,
+ varname='p_iconstring',
+ defaults={if_true={vi=""}}
+ },
+@@ -1209,6 +1216,7 @@ return {
+ full_name='includeexpr', abbreviation='inex',
+ type='string', scope={'buffer'},
+ vi_def=true,
++ modelineexpr=true,
+ alloced=true,
+ varname='p_inex',
+ defaults={if_true={vi=""}}
+@@ -1225,6 +1233,7 @@ return {
+ type='string', scope={'buffer'},
+ vi_def=true,
+ vim=true,
++ modelineexpr=true,
+ alloced=true,
+ varname='p_inde',
+ defaults={if_true={vi=""}}
+@@ -1538,6 +1547,14 @@ return {
+ varname='p_ml',
+ defaults={if_true={vi=false, vim=true}}
+ },
++ {
++ full_name='modelineexpr', abbreviation='mle',
++ type='bool', scope={'global'},
++ vi_def=true,
++ secure=true,
++ varname='p_mle',
++ defaults={if_true={vi=false}}
++ },
+ {
+ full_name='modelines', abbreviation='mls',
+ type='number', scope={'global'},
+@@ -1898,6 +1915,7 @@ return {
+ type='string', scope={'global'},
+ vi_def=true,
+ alloced=true,
++ modelineexpr=true,
+ redraw={'statuslines'},
+ varname='p_ruf',
+ defaults={if_true={vi=""}}
+@@ -2293,6 +2311,7 @@ return {
+ type='string', scope={'global', 'window'},
+ vi_def=true,
+ alloced=true,
++ modelineexpr=true,
+ redraw={'statuslines'},
+ varname='p_stl',
+ defaults={if_true={vi=""}}
+@@ -2352,6 +2371,7 @@ return {
+ full_name='tabline', abbreviation='tal',
+ type='string', scope={'global'},
+ vi_def=true,
++ modelineexpr=true,
+ redraw={'all_windows'},
+ varname='p_tal',
+ defaults={if_true={vi=""}}
+@@ -2511,6 +2531,7 @@ return {
+ full_name='titlestring',
+ type='string', scope={'global'},
+ vi_def=true,
++ modelineexpr=true,
+ varname='p_titlestring',
+ defaults={if_true={vi=""}}
+ },
+diff --git a/src/nvim/testdir/test49.in b/src/nvim/testdir/test49.in
+index 435e62765..eb17ace2f 100644
+--- a/src/nvim/testdir/test49.in
++++ b/src/nvim/testdir/test49.in
+@@ -4,7 +4,7 @@ If after adding a new test, the test output doesn't appear properly in
+ test49.failed, try to add one or more "G"s at the line ending in "test.out"
+
+ STARTTEST
+-:se nomore
++:se nomore modelineexpr
+ :lang mess C
+ :so test49.vim
+ :" Go back to this file and append the results from register r.
+diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
+index 0602ff6a4..9d81d2162 100644
+--- a/src/nvim/testdir/test_alot.vim
++++ b/src/nvim/testdir/test_alot.vim
+@@ -28,6 +28,7 @@ source test_lambda.vim
+ source test_mapping.vim
+ source test_menu.vim
+ source test_messages.vim
++source test_modeline.vim
+ source test_move.vim
+ source test_partial.vim
+ source test_popup.vim
+diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
+index 253d6750e..12b6a38ef 100644
+--- a/src/nvim/testdir/test_autocmd.vim
++++ b/src/nvim/testdir/test_autocmd.vim
+@@ -652,6 +652,29 @@ func Test_OptionSet_diffmode_close()
+ "delfunc! AutoCommandOptionSet
+ endfunc
+
++func Test_OptionSet_modeline()
++ throw 'skipped: Nvim does not support test_override()'
++ call test_override('starting', 1)
++ au! OptionSet
++ augroup set_tabstop
++ au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
++ augroup END
++ call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline')
++ set modeline
++ let v:errmsg = ''
++ call assert_fails('split XoptionsetModeline', 'E12:')
++ call assert_equal(7, &ts)
++ call assert_equal('', v:errmsg)
++
++ augroup set_tabstop
++ au!
++ augroup END
++ bwipe!
++ set ts&
++ call delete('XoptionsetModeline')
++ call test_override('starting', 0)
++endfunc
++
+ " Test for Bufleave autocommand that deletes the buffer we are about to edit.
+ func Test_BufleaveWithDelete()
+ new | edit Xfile1
+diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim
+new file mode 100644
+index 000000000..1e196e07f
+--- /dev/null
++++ b/src/nvim/testdir/test_modeline.vim
+@@ -0,0 +1,173 @@
++" Tests for parsing the modeline.
++
++func Test_modeline_invalid()
++ " This was reading allocated memory in the past.
++ call writefile(['vi:0', 'nothing'], 'Xmodeline')
++ let modeline = &modeline
++ set modeline
++ call assert_fails('set Xmodeline', 'E518:')
++
++ let &modeline = modeline
++ bwipe!
++ call delete('Xmodeline')
++ endfunc
++
++func Test_modeline_filetype()
++ call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype')
++ let modeline = &modeline
++ set modeline
++ filetype plugin on
++ split Xmodeline_filetype
++ call assert_equal("c", &filetype)
++ call assert_equal(1, b:did_ftplugin)
++ call assert_equal("ccomplete#Complete", &ofu)
++
++ bwipe!
++ call delete('Xmodeline_filetype')
++ let &modeline = modeline
++ filetype plugin off
++endfunc
++
++func Test_modeline_syntax()
++ call writefile(['vim: set syn=c :', 'nothing'], 'Xmodeline_syntax')
++ let modeline = &modeline
++ set modeline
++ syntax enable
++ split Xmodeline_syntax
++ call assert_equal("c", &syntax)
++ call assert_equal("c", b:current_syntax)
++
++ bwipe!
++ call delete('Xmodeline_syntax')
++ let &modeline = modeline
++ syntax off
++endfunc
++
++func Test_modeline_keymap()
++ if !has('keymap')
++ return
++ endif
++ call writefile(['vim: set keymap=greek :', 'nothing'], 'Xmodeline_keymap')
++ let modeline = &modeline
++ set modeline
++ split Xmodeline_keymap
++ call assert_equal("greek", &keymap)
++ call assert_match('greek\|grk', b:keymap_name)
++
++ bwipe!
++ call delete('Xmodeline_keymap')
++ let &modeline = modeline
++ set keymap= iminsert=0 imsearch=-1
++endfunc
++
++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, a:error)
++ call assert_equal("", &filetype)
++ call assert_equal("", &syntax)
++
++ bwipe!
++ call delete(fname)
++ let &modeline = modeline
++ filetype plugin off
++ syntax off
++endfunc
++
++func Test_modeline_filetype_fails()
++ call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:')
++endfunc
++
++func Test_modeline_syntax_fails()
++ call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:')
++endfunc
++
++func Test_modeline_keymap_fails()
++ 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('mkspellmem', 'mkspellmem=Something()', 'E520:')
++ call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
++ call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
++ call s:modeline_fails('modelineexpr', 'modelineexpr=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', 'pythonhome=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
++ " Skip these since nvim doesn't support termcodes as 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
diff -Nru neovim-0.3.4/debian/patches/0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch neovim-0.3.4/debian/patches/0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch
--- neovim-0.3.4/debian/patches/0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch 2019-06-05 21:38:14.000000000 -0400
+++ neovim-0.3.4/debian/patches/0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch 1969-12-31 19:00:00.000000000 -0500
@@ -1,36 +0,0 @@
-From 46112054bf3c6d00c53cf8a904e0687b508a4f0a Mon Sep 17 00:00:00 2001
-From: "Justin M. Keyes" <justinkz@gmail.com>
-Date: Wed, 29 May 2019 00:33:22 +0200
-Subject: [PATCH] vim-patch:8.1.1365: :source should check sandbox #10082
-
-Problem: Source command doesn't check for the sandbox. (Armin Razmjou)
-Solution: Check for the sandbox when sourcing a file.
-https://github.com/vim/vim/commit/53575521406739cf20bbe4e384d88e7dca11f040
-
-(cherry picked from commit 4553fc5e6cb6c8c43f57c173d01b31a61e51d13f)
-
-Signed-off-by: James McCoy <jamessan@debian.org>
-Closes: CVE-2019-12735
-Closes: #930024
----
- src/nvim/getchar.c | 7 +++++++
- 1 file changed, 7 insertions(+)
-
-diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
-index 94702a9a3..243e6afce 100644
---- a/src/nvim/getchar.c
-+++ b/src/nvim/getchar.c
-@@ -1244,6 +1244,13 @@ openscript (
- EMSG(_(e_nesting));
- return;
- }
-+
-+ // Disallow sourcing a file in the sandbox, the commands would be executed
-+ // later, possibly outside of the sandbox.
-+ if (check_secure()) {
-+ return;
-+ }
-+
- if (ignore_script)
- /* Not reading from script, also don't open one. Warning message? */
- return;
diff -Nru neovim-0.3.4/debian/patches/0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch neovim-0.3.4/debian/patches/0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch
--- neovim-0.3.4/debian/patches/0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,36 @@
+From 0deb5bd4710ca912420bd796e74f6a7175d7753f Mon Sep 17 00:00:00 2001
+From: "Justin M. Keyes" <justinkz@gmail.com>
+Date: Wed, 29 May 2019 00:33:22 +0200
+Subject: [PATCH 2/7] vim-patch:8.1.1365: :source should check sandbox #10082
+
+Problem: Source command doesn't check for the sandbox. (Armin Razmjou)
+Solution: Check for the sandbox when sourcing a file.
+https://github.com/vim/vim/commit/53575521406739cf20bbe4e384d88e7dca11f040
+
+(cherry picked from commit 4553fc5e6cb6c8c43f57c173d01b31a61e51d13f)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+Closes: CVE-2019-12735
+Closes: #930024
+---
+ src/nvim/getchar.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
+index 94702a9a3..243e6afce 100644
+--- a/src/nvim/getchar.c
++++ b/src/nvim/getchar.c
+@@ -1244,6 +1244,13 @@ openscript (
+ EMSG(_(e_nesting));
+ return;
+ }
++
++ // Disallow sourcing a file in the sandbox, the commands would be executed
++ // later, possibly outside of the sandbox.
++ if (check_secure()) {
++ return;
++ }
++
+ if (ignore_script)
+ /* Not reading from script, also don't open one. Warning message? */
+ return;
diff -Nru neovim-0.3.4/debian/patches/0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch neovim-0.3.4/debian/patches/0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch
--- neovim-0.3.4/debian/patches/0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,104 @@
+From ee6325436e32803a8b2e4678ff334c7b9423ef7e Mon Sep 17 00:00:00 2001
+From: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>
+Date: Sat, 23 Mar 2019 00:58:00 -0400
+Subject: [PATCH 3/7] vim-patch:8.1.0177: defining function in sandbox is
+ inconsistent
+
+Problem: Defining function in sandbox is inconsistent, cannot use :function
+ but can define a lambda.
+Solution: Allow defining a function in the sandbox, but also use the sandbox
+ when executing it. (closes vim/vim#3182)
+https://github.com/vim/vim/commit/93343725b5fa1cf580a24302455980faacae8ee2
+
+(cherry picked from commit c202e4a86856594a6602a795314a081c497c2df5)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+---
+ src/nvim/eval.c | 19 ++++++++++++++++++-
+ src/nvim/ex_cmds.lua | 2 +-
+ 2 files changed, 19 insertions(+), 2 deletions(-)
+
+diff --git a/src/nvim/eval.c b/src/nvim/eval.c
+index 5191328b5..9163673a3 100644
+--- a/src/nvim/eval.c
++++ b/src/nvim/eval.c
+@@ -241,13 +241,14 @@ typedef enum {
+ ///< the value (prevents error message).
+ } GetLvalFlags;
+
+-// function flags
++// flags used in uf_flags
+ #define FC_ABORT 0x01 // abort function on error
+ #define FC_RANGE 0x02 // function accepts range
+ #define FC_DICT 0x04 // Dict function, uses "self"
+ #define FC_CLOSURE 0x08 // closure, uses outer scope variables
+ #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
+ #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
++#define FC_SANDBOX 0x40 // function defined in the sandbox
+
+ // The names of packages that once were loaded are remembered.
+ static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL };
+@@ -5853,6 +5854,9 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
++ if (sandbox) {
++ flags |= FC_SANDBOX;
++ }
+ fp->uf_varargs = true;
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+@@ -20315,6 +20319,9 @@ void ex_function(exarg_T *eap)
+ if (prof_def_func())
+ func_do_profile(fp);
+ fp->uf_varargs = varargs;
++ if (sandbox) {
++ flags |= FC_SANDBOX;
++ }
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ID = current_SID;
+@@ -21305,6 +21312,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
+ char_u *save_sourcing_name;
+ linenr_T save_sourcing_lnum;
+ scid_T save_current_SID;
++ bool using_sandbox = false;
+ funccall_T *fc;
+ int save_did_emsg;
+ static int depth = 0;
+@@ -21462,6 +21470,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
+ save_sourcing_name = sourcing_name;
+ save_sourcing_lnum = sourcing_lnum;
+ sourcing_lnum = 1;
++
++ if (fp->uf_flags & FC_SANDBOX) {
++ using_sandbox = true;
++ sandbox++;
++ }
++
+ // need space for new sourcing_name:
+ // * save_sourcing_name
+ // * "["number"].." or "function "
+@@ -21622,6 +21636,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
+ if (do_profiling_yes) {
+ script_prof_restore(&wait_start);
+ }
++ if (using_sandbox) {
++ sandbox--;
++ }
+
+ if (p_verbose >= 12 && sourcing_name != NULL) {
+ ++no_wait_return;
+diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
+index 79ca5363e..fae21d8c9 100644
+--- a/src/nvim/ex_cmds.lua
++++ b/src/nvim/ex_cmds.lua
+@@ -1004,7 +1004,7 @@ return {
+ },
+ {
+ command='function',
+- flags=bit.bor(EXTRA, BANG, CMDWIN),
++ flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_function',
+ },
diff -Nru neovim-0.3.4/debian/patches/0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch neovim-0.3.4/debian/patches/0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch
--- neovim-0.3.4/debian/patches/0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,41 @@
+From 5be949ce2df74446f33ea2949620234b41c046b0 Mon Sep 17 00:00:00 2001
+From: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>
+Date: Sat, 23 Mar 2019 01:26:07 -0400
+Subject: [PATCH 4/7] vim-patch:8.1.0189: function defined in sandbox not
+ tested
+
+Problem: Function defined in sandbox not tested.
+Solution: Add a text.
+https://github.com/vim/vim/commit/d90a144eda047816acffc7a8f297b43a7120710e
+
+(cherry picked from commit f514b7fbbc75b4464af5abe44d5f859a0d904495)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+---
+ src/nvim/testdir/test_functions.vim | 16 ++++++++++++++++
+ 1 file changed, 16 insertions(+)
+
+diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
+index 7dc9f31ce..292f69d70 100644
+--- a/src/nvim/testdir/test_functions.vim
++++ b/src/nvim/testdir/test_functions.vim
+@@ -1037,3 +1037,19 @@ func Test_func_range_with_edit()
+ call delete('Xfuncrange2')
+ bwipe!
+ endfunc
++
++sandbox function Fsandbox()
++ normal ix
++endfunc
++
++func Test_func_sandbox()
++ sandbox let F = {-> 'hello'}
++ call assert_equal('hello', F())
++
++ sandbox let F = {-> execute("normal ix\<Esc>")}
++ call assert_fails('call F()', 'E48:')
++ unlet F
++
++ call assert_fails('call Fsandbox()', 'E48:')
++ delfunc Fsandbox
++endfunc
diff -Nru neovim-0.3.4/debian/patches/0005-vim-patch-8.1.0206-duplicate-test-function-name.patch neovim-0.3.4/debian/patches/0005-vim-patch-8.1.0206-duplicate-test-function-name.patch
--- neovim-0.3.4/debian/patches/0005-vim-patch-8.1.0206-duplicate-test-function-name.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0005-vim-patch-8.1.0206-duplicate-test-function-name.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,35 @@
+From 86093f2847efb7e0579247b7ff850e4cafe71912 Mon Sep 17 00:00:00 2001
+From: Jan Edmund Lazo <jan.lazo@mail.utoronto.ca>
+Date: Sat, 25 May 2019 14:47:17 -0400
+Subject: [PATCH 5/7] vim-patch:8.1.0206: duplicate test function name
+
+Problem: Duplicate test function name.
+Solution: Rename both functions.
+https://github.com/vim/vim/commit/cd96eef3a869557bd3d2d4497861d87cb525db06
+
+(cherry picked from commit 6683cb60b805fa305a069cf7873be8b605d78a3d)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+---
+ src/nvim/testdir/test_glob2regpat.vim | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/nvim/testdir/test_glob2regpat.vim b/src/nvim/testdir/test_glob2regpat.vim
+index fdf17946b..e6e41f13e 100644
+--- a/src/nvim/testdir/test_glob2regpat.vim
++++ b/src/nvim/testdir/test_glob2regpat.vim
+@@ -1,12 +1,12 @@
+ " Test glob2regpat()
+
+-func Test_invalid()
++func Test_glob2regpat_invalid()
+ call assert_fails('call glob2regpat(1.33)', 'E806:')
+ call assert_fails('call glob2regpat("}")', 'E219:')
+ call assert_fails('call glob2regpat("{")', 'E220:')
+ endfunc
+
+-func Test_valid()
++func Test_glob2regpat_valid()
+ call assert_equal('^foo\.', glob2regpat('foo.*'))
+ call assert_equal('^foo.$', glob2regpat('foo?'))
+ call assert_equal('\.vim$', glob2regpat('*.vim'))
diff -Nru neovim-0.3.4/debian/patches/0006-vim-patch-8.1.1382-error-when-editing-test-file.patch neovim-0.3.4/debian/patches/0006-vim-patch-8.1.1382-error-when-editing-test-file.patch
--- neovim-0.3.4/debian/patches/0006-vim-patch-8.1.1382-error-when-editing-test-file.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0006-vim-patch-8.1.1382-error-when-editing-test-file.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,59 @@
+From e45938b8c7465d854b3d4feeff6acfc71835078f Mon Sep 17 00:00:00 2001
+From: James McCoy <jamessan@jamessan.com>
+Date: Sat, 22 Jun 2019 22:09:32 -0400
+Subject: [PATCH 6/7] vim-patch:8.1.1382: error when editing test file
+
+Problem: Error when editing test file.
+Solution: Remove part of modeline.
+https://github.com/vim/vim/commit/3020a87cb121123abf1e9a1eca0eddac241fc481
+
+(cherry picked from commit 13f3a21226fdd31c42ba927ff12ac6c34c940311)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+---
+ src/nvim/testdir/test49.in | 2 +-
+ src/nvim/testdir/test49.vim | 3 +--
+ src/nvim/testdir/test_vimscript.vim | 1 -
+ 3 files changed, 2 insertions(+), 4 deletions(-)
+
+diff --git a/src/nvim/testdir/test49.in b/src/nvim/testdir/test49.in
+index eb17ace2f..435e62765 100644
+--- a/src/nvim/testdir/test49.in
++++ b/src/nvim/testdir/test49.in
+@@ -4,7 +4,7 @@ If after adding a new test, the test output doesn't appear properly in
+ test49.failed, try to add one or more "G"s at the line ending in "test.out"
+
+ STARTTEST
+-:se nomore modelineexpr
++:se nomore
+ :lang mess C
+ :so test49.vim
+ :" Go back to this file and append the results from register r.
+diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
+index 467abcd9b..837e55ebc 100644
+--- a/src/nvim/testdir/test49.vim
++++ b/src/nvim/testdir/test49.vim
+@@ -1,6 +1,6 @@
+ " Vim script language tests
+ " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com>
+-" Last Change: 2016 Feb 07
++" Last Change: 2019 May 24
+
+ "-------------------------------------------------------------------------------
+ " Test environment {{{1
+@@ -9005,5 +9005,4 @@ Xcheck 50443995
+ "-------------------------------------------------------------------------------
+ " Modelines {{{1
+ " vim: ts=8 sw=4 tw=80 fdm=marker
+-" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "")
+ "-------------------------------------------------------------------------------
+diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
+index 5b16f6d20..c856fd720 100644
+--- a/src/nvim/testdir/test_vimscript.vim
++++ b/src/nvim/testdir/test_vimscript.vim
+@@ -1297,5 +1297,4 @@ endfunc
+ "-------------------------------------------------------------------------------
+ " Modelines {{{1
+ " vim: ts=8 sw=4 tw=80 fdm=marker
+-" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "")
+ "-------------------------------------------------------------------------------
diff -Nru neovim-0.3.4/debian/patches/0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch neovim-0.3.4/debian/patches/0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch
--- neovim-0.3.4/debian/patches/0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch 1969-12-31 19:00:00.000000000 -0500
+++ neovim-0.3.4/debian/patches/0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch 2019-06-26 21:21:33.000000000 -0400
@@ -0,0 +1,57 @@
+From 3e2e812f17fd05687dd9ffdbd42ccf9d31ee9bc7 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Bj=C3=B6rn=20Linse?= <bjorn.linse@gmail.com>
+Date: Wed, 26 Jun 2019 08:11:51 +0200
+Subject: [PATCH 7/7] eval/api: don't allow the API to be called in the
+ sandbox.
+
+Identifying and maintaining a "secure" subset of the API would be too
+much busywork. So just disable the entire thing.
+
+(cherry picked from commit 413b313ad2cfd5a1ee32369b944436e14fc8bfb3)
+
+Signed-off-by: James McCoy <jamessan@debian.org>
+---
+ src/nvim/eval.c | 4 ++++
+ test/functional/eval/api_functions_spec.lua | 9 ++++++++-
+ 2 files changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/src/nvim/eval.c b/src/nvim/eval.c
+index 9163673a3..618cd50a7 100644
+--- a/src/nvim/eval.c
++++ b/src/nvim/eval.c
+@@ -6516,6 +6516,10 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+
+ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ {
++ if (check_restricted() || check_secure()) {
++ return;
++ }
++
+ ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
+
+ Array args = ARRAY_DICT_INIT;
+diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua
+index 6f440c7d8..951c5d685 100644
+--- a/test/functional/eval/api_functions_spec.lua
++++ b/test/functional/eval/api_functions_spec.lua
+@@ -4,7 +4,8 @@ local lfs = require('lfs')
+ local neq, eq, command = helpers.neq, helpers.eq, helpers.command
+ local clear, curbufmeths = helpers.clear, helpers.curbufmeths
+ local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
+-local insert = helpers.insert
++local insert, meth_pcall = helpers.insert, helpers.meth_pcall
++local meths = helpers.meths
+
+ describe('api functions', function()
+ before_each(clear)
+@@ -145,4 +146,10 @@ describe('api functions', function()
+ ]])
+ screen:detach()
+ end)
++
++ it('cannot be called from sandbox', function()
++ eq({false, 'Vim(call):E48: Not allowed in sandbox'},
++ meth_pcall(command, "sandbox call nvim_input('ievil')"))
++ eq({''}, meths.buf_get_lines(0, 0, -1, true))
++ end)
+ end)
diff -Nru neovim-0.3.4/debian/patches/series neovim-0.3.4/debian/patches/series
--- neovim-0.3.4/debian/patches/series 2019-06-05 21:38:14.000000000 -0400
+++ neovim-0.3.4/debian/patches/series 2019-06-26 21:21:33.000000000 -0400
@@ -1,2 +1,8 @@
# exported from git by git-debcherry
-0001-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch
+0001-debcherry-fixup-patch.patch
+0002-vim-patch-8.1.1365-source-should-check-sandbox-10082.patch
+0003-vim-patch-8.1.0177-defining-function-in-sandbox-is-i.patch
+0004-vim-patch-8.1.0189-function-defined-in-sandbox-not-t.patch
+0005-vim-patch-8.1.0206-duplicate-test-function-name.patch
+0006-vim-patch-8.1.1382-error-when-editing-test-file.patch
+0007-eval-api-don-t-allow-the-API-to-be-called-in-the-san.patch
Reply to: