CVE-2021-4187: Use After Free (CWE-416)
value: vim is vulnerable to Use After Free
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 7.8
Published: 2021-12-29T17:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/testdir/test_vim9_func.vim, src/userfunc.c, src/version.c
Commit date: 2021-12-28 17:23:12 +0000
Commit message: patch 8.2.3923: Vim9: double free with split argument list in nested function

Problem:    Vim9: double free if a nested function has a line break in the
            argument list.
Solution:   Set cmdlinep when freeing the previous line.

Diff: diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim
index 1a14c101679..88c0af8e0da 100644
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -1667,11 +1667,11 @@ enddef
 def Test_error_in_nested_function()
   # Error in called function requires unwinding the call stack.
   assert_fails('FuncWithForwardCall()', 'E1096:', '', 1, 'FuncWithForwardCall')
 enddef
 
-def Test_nested_functin_with_nextcmd()
+def Test_nested_function_with_nextcmd()
   var lines =<< trim END
       vim9script
       # Define an outer function
       def FirstFunction()
         # Define an inner function
@@ -1687,10 +1687,28 @@ def Test_nested_functin_with_nextcmd()
       defcompile
   END
   CheckScriptFailure(lines, 'E476: Invalid command: AAAAA')
 enddef
 
+def Test_nested_function_with_args_split()
+  var lines =<< trim END
+      vim9script
+      def FirstFunction()
+        def SecondFunction(
+        )
+        # had a double free if the right parenthesis of the nested function is
+        # on the next line
+         
+        enddef|BBBB
+      enddef
+      # Compile all functions
+      defcompile
+  END
+  # FIXME: this should fail on the BBBB
+  CheckScriptSuccess(lines)
+enddef
+
 def Test_return_type_wrong()
   CheckScriptFailure([
         'def Func(): number',
         'return "a"',
         'enddef',
diff --git a/src/userfunc.c b/src/userfunc.c
index e1028e77281..a7cbac3c867 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -217,10 +217,12 @@ get_function_args(
 	    // End of the line, get the next one.
 	    theline = eap->getline(':', eap->cookie, 0, TRUE);
 	    if (theline == NULL)
 		break;
 	    vim_free(*line_to_free);
+	    if (*eap->cmdlinep == *line_to_free)
+		*eap->cmdlinep = theline;
 	    *line_to_free = theline;
 	    whitep = (char_u *)" ";
 	    p = skipwhite(theline);
 	}
 
diff --git a/src/version.c b/src/version.c
index 6c8bced722b..5d22d98e8da 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,10 +747,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3923,
 /**/
     3922,
 /**/
     3921,
 /**/

Vulnerable code: def Test_nested_functin_with_nextcmd()
Fixed code: def Test_nested_function_with_nextcmd()
def Test_nested_function_with_args_split()
  var lines =<< trim END
      vim9script
      def FirstFunction()
        def SecondFunction(
        )
        # had a double free if the right parenthesis of the nested function is
        # on the next line
         
        enddef|BBBB
      enddef
      # Compile all functions
      defcompile
  END
  # FIXME: this should fail on the BBBB
  CheckScriptSuccess(lines)
enddef

	    if (*eap->cmdlinep == *line_to_free)
		*eap->cmdlinep = theline;
/**/
    3923,