CVE-2016-1248: Improper Input Validation (CWE-20)
value: vim before patch 8.0.0056 does not properly validate values for the 'filetype', 'syntax' and 'keymap' options, which may result in the execution of arbitrary code if a file with a specially crafted modeline is opened.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 7.8
Published: 2016-11-23T15:59Z
Repository: https://github.com/neovim/neovim
Language: Other
Files changed: src/nvim/option.c, src/nvim/testdir/test_options.vim
Commit date: 2016-11-20 14:07:46 -0500
Commit message: vim-patch:8.0.0056

Problem:    When setting 'filetype' there is no check for a valid name.
Solution:   Only allow valid characters in 'filetype', 'syntax' and 'keymap'.

https://github.com/vim/vim/commit/d0b5138ba4bccff8a744c99836041ef6322ed39a

Diff: diff --git a/src/nvim/option.c b/src/nvim/option.c
index 0f95974c..469aeecc 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2397,10 +2397,22 @@ static char *set_string_option(const int opt_idx, const char *const value,
   }
 
   return r;
 }
 
+/// Return true if "val" is a valid 'filetype' name.
+/// Also used for 'syntax' and 'keymap'.
+static bool valid_filetype(char_u *val)
+{
+  for (char_u *s = val; *s != NUL; s++) {
+    if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) {
+      return false;
+    }
+  }
+  return true;
+}
+
 /*
  * Handle string options that need some action to perform when changed.
  * Returns NULL for success, or an error message for an error.
  */
 static char_u *
@@ -2621,12 +2633,16 @@ did_set_string_option (
     /* Canonize printencoding if VIM standard one */
     p = enc_canonize(p_penc);
     xfree(p_penc);
     p_penc = p;
   } else if (varp == &curbuf->b_p_keymap) {
-    /* load or unload key mapping tables */
-    errmsg = keymap_init();
+    if (!valid_filetype(*varp)) {
+      errmsg = e_invarg;
+    } else {
+      // load or unload key mapping tables
+      errmsg = keymap_init();
+    }
 
     if (errmsg == NULL) {
       if (*curbuf->b_p_keymap != NUL) {
         /* Installed a new keymap, switch on using it. */
         curbuf->b_p_iminsert = B_IMODE_LMAP;
@@ -3116,12 +3132,20 @@ did_set_string_option (
   // inccommand
   } else if (varp == &p_icm) {
       if (check_opt_strings(p_icm, p_icm_values, false) != OK) {
         errmsg = e_invarg;
       }
-  // Options that are a list of flags.
+  } else if (gvarp == &p_ft) {
+    if (!valid_filetype(*varp)) {
+      errmsg = e_invarg;
+    }
+  } else if (gvarp == &p_syn) {
+    if (!valid_filetype(*varp)) {
+      errmsg = e_invarg;
+    }
   } else {
+    // Options that are a list of flags.
     p = NULL;
     if (varp == &p_ww)
       p = (char_u *)WW_ALL;
     if (varp == &p_shm)
       p = (char_u *)SHM_ALL;
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index cceb1801..93657f8f 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -36,5 +36,55 @@ function! Test_path_keep_commas()
   set path+=bar
   call assert_equal('foo,,bar', &path)
 
   set path&
 endfunction
+
+func Test_filetype_valid()
+  set ft=valid_name
+  call assert_equal("valid_name", &filetype)
+  set ft=valid-name
+  call assert_equal("valid-name", &filetype)
+
+  call assert_fails(":set ft=wrong;name", "E474:")
+  call assert_fails(":set ft=wrong\\\\name", "E474:")
+  call assert_fails(":set ft=wrong\\|name", "E474:")
+  call assert_fails(":set ft=wrong/name", "E474:")
+  call assert_fails(":set ft=wrong\\\nname", "E474:")
+  call assert_equal("valid-name", &filetype)
+
+  exe "set ft=trunc\x00name"
+  call assert_equal("trunc", &filetype)
+endfunc
+
+func Test_syntax_valid()
+  set syn=valid_name
+  call assert_equal("valid_name", &syntax)
+  set syn=valid-name
+  call assert_equal("valid-name", &syntax)
+
+  call assert_fails(":set syn=wrong;name", "E474:")
+  call assert_fails(":set syn=wrong\\\\name", "E474:")
+  call assert_fails(":set syn=wrong\\|name", "E474:")
+  call assert_fails(":set syn=wrong/name", "E474:")
+  call assert_fails(":set syn=wrong\\\nname", "E474:")
+  call assert_equal("valid-name", &syntax)
+
+  exe "set syn=trunc\x00name"
+  call assert_equal("trunc", &syntax)
+endfunc
+
+func Test_keymap_valid()
+  call assert_fails(":set kmp=valid_name", "E544:")
+  call assert_fails(":set kmp=valid_name", "valid_name")
+  call assert_fails(":set kmp=valid-name", "E544:")
+  call assert_fails(":set kmp=valid-name", "valid-name")
+
+  call assert_fails(":set kmp=wrong;name", "E474:")
+  call assert_fails(":set kmp=wrong\\\\name", "E474:")
+  call assert_fails(":set kmp=wrong\\|name", "E474:")
+  call assert_fails(":set kmp=wrong/name", "E474:")
+  call assert_fails(":set kmp=wrong\\\nname", "E474:")
+
+  call assert_fails(":set kmp=trunc\x00name", "E544:")
+  call assert_fails(":set kmp=trunc\x00name", "trunc")
+endfunc

Vulnerable code:     /* load or unload key mapping tables */
    errmsg = keymap_init();
  // Options that are a list of flags.
Fixed code: /// Return true if "val" is a valid 'filetype' name.
/// Also used for 'syntax' and 'keymap'.
static bool valid_filetype(char_u *val)
{
  for (char_u *s = val; *s != NUL; s++) {
    if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) {
      return false;
    }
  }
  return true;
}

    if (!valid_filetype(*varp)) {
      errmsg = e_invarg;
    } else {
      // load or unload key mapping tables
      errmsg = keymap_init();
    }
  } else if (gvarp == &p_ft) {
    if (!valid_filetype(*varp)) {
      errmsg = e_invarg;
    }
  } else if (gvarp == &p_syn) {
    if (!valid_filetype(*varp)) {
      errmsg = e_invarg;
    }
    // Options that are a list of flags.

func Test_filetype_valid()
  set ft=valid_name
  call assert_equal("valid_name", &filetype)
  set ft=valid-name
  call assert_equal("valid-name", &filetype)

  call assert_fails(":set ft=wrong;name", "E474:")
  call assert_fails(":set ft=wrong\\\\name", "E474:")
  call assert_fails(":set ft=wrong\\|name", "E474:")
  call assert_fails(":set ft=wrong/name", "E474:")
  call assert_fails(":set ft=wrong\\\nname", "E474:")
  call assert_equal("valid-name", &filetype)

  exe "set ft=trunc\x00name"
  call assert_equal("trunc", &filetype)
endfunc

func Test_syntax_valid()
  set syn=valid_name
  call assert_equal("valid_name", &syntax)
  set syn=valid-name
  call assert_equal("valid-name", &syntax)

  call assert_fails(":set syn=wrong;name", "E474:")
  call assert_fails(":set syn=wrong\\\\name", "E474:")
  call assert_fails(":set syn=wrong\\|name", "E474:")
  call assert_fails(":set syn=wrong/name", "E474:")
  call assert_fails(":set syn=wrong\\\nname", "E474:")
  call assert_equal("valid-name", &syntax)

  exe "set syn=trunc\x00name"
  call assert_equal("trunc", &syntax)
endfunc

func Test_keymap_valid()
  call assert_fails(":set kmp=valid_name", "E544:")
  call assert_fails(":set kmp=valid_name", "valid_name")
  call assert_fails(":set kmp=valid-name", "E544:")
  call assert_fails(":set kmp=valid-name", "valid-name")

  call assert_fails(":set kmp=wrong;name", "E474:")
  call assert_fails(":set kmp=wrong\\\\name", "E474:")
  call assert_fails(":set kmp=wrong\\|name", "E474:")
  call assert_fails(":set kmp=wrong/name", "E474:")
  call assert_fails(":set kmp=wrong\\\nname", "E474:")

  call assert_fails(":set kmp=trunc\x00name", "E544:")
  call assert_fails(":set kmp=trunc\x00name", "trunc")
endfunc