[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: