CVE-2023-46246: Use After Free (CWE-416)
value: Vim is an improved version of the good old UNIX editor Vi. Heap-use-after-free in memory allocated in the function `ga_grow_inner` in in the file `src/alloc.c` at line 748, which is freed in the file `src/ex_docmd.c` in the function `do_cmdline` at line 1010 and then used again in `src/cmdhist.c` at line 759. When using the `:history` command, it's possible that the provided argument overflows the accepted value. Causing an Integer Overflow and potentially later an use-after-free. This vulnerability has been patched in version 9.0.2068.

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: 5.5
Published: 2023-10-27T19:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/cmdhist.c, src/errors.h, src/ex_getln.c, src/testdir/test_history.vim, src/version.c
Commit date: 2023-10-26 21:29:32 +0200
Commit message: patch 9.0.2068: [security] overflow in :history

Problem:  [security] overflow in :history
Solution: Check that value fits into int

The get_list_range() function, used to parse numbers for the :history
and :clist command internally uses long variables to store the numbers.
However function arguments are integer pointers, which can then
overflow.

Check that the return value from the vim_str2nr() function is not larger
than INT_MAX and if yes, bail out with an error. I guess nobody uses a
cmdline/clist history that needs so many entries... (famous last words).

It is only a moderate vulnerability, so impact should be low.

Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-q22m-h7m2-9mgm

Signed-off-by: Christian Brabandt <cb@256bit.org>

Diff: diff --git a/src/cmdhist.c b/src/cmdhist.c
index d398ca7a687b..96a9b3e95b86 100644
--- a/src/cmdhist.c
+++ b/src/cmdhist.c
@@ -740,11 +740,14 @@ ex_history(exarg_T *eap)
     }
     else
 	end = arg;
     if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL)
     {
-	semsg(_(e_trailing_characters_str), end);
+	if (*end != NUL)
+	    semsg(_(e_trailing_characters_str), end);
+	else
+	    semsg(_(e_val_too_large), arg);
 	return;
     }
 
     for (; !got_int && histype1 <= histype2; ++histype1)
     {
diff --git a/src/errors.h b/src/errors.h
index 79a785e1e295..72957d8b93bd 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3558,5 +3558,7 @@ EXTERN char e_aptypes_is_null_nr_str[]
 	INIT(= "E1507: Internal error: ap_types or ap_types[idx] is NULL: %d: %s");
 EXTERN char e_xattr_e2big[]
 	INIT(= N_("E1508: Size of the extended attribute value is larger than the maximum size allowed"));
 EXTERN char e_xattr_other[]
 	INIT(= N_("E1509: Error occurred when reading or writing extended attribute"));
+EXTERN char e_val_too_large[]
+	INIT(= N_("E1510: Value too large: %s"));
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 9683e2ebd5af..8f0be520886b 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -4375,22 +4375,30 @@ get_list_range(char_u **str, int *num1, int *num2)
     *str = skipwhite(*str);
     if (**str == '-' || vim_isdigit(**str))  // parse "from" part of range
     {
 	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE, NULL);
 	*str += len;
+	// overflow
+	if (num > INT_MAX)
+	    return FAIL;
+
 	*num1 = (int)num;
 	first = TRUE;
     }
     *str = skipwhite(*str);
     if (**str == ',')			// parse "to" part of range
     {
 	*str = skipwhite(*str + 1);
 	vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, FALSE, NULL);
 	if (len > 0)
 	{
-	    *num2 = (int)num;
 	    *str = skipwhite(*str + len);
+	    // overflow
+	    if (num > INT_MAX)
+		return FAIL;
+
+	    *num2 = (int)num;
 	}
 	else if (!first)		// no number given at all
 	    return FAIL;
     }
     else if (first)			// only one number given
diff --git a/src/testdir/test_history.vim b/src/testdir/test_history.vim
index bb6d67172585..482328ab4aae 100644
--- a/src/testdir/test_history.vim
+++ b/src/testdir/test_history.vim
@@ -252,6 +252,14 @@ func Test_history_crypt_key()
   call assert_equal('set bs=2 key-= ts=8', histget(':'))
 
   set key& bs& ts&
 endfunc
 
+" The following used to overflow and causing an use-after-free
+func Test_history_max_val()
+
+  set history=10
+  call assert_fails(':history 2147483648', 'E1510:')
+  set history&
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 1ae5c98714d1..0284594f7653 100644
--- a/src/version.c
+++ b/src/version.c
@@ -702,10 +702,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2068,
 /**/
     2067,
 /**/
     2066,
 /**/

Vulnerable code: 	semsg(_(e_trailing_characters_str), end);
	    *num2 = (int)num;
Fixed code: 	if (*end != NUL)
	    semsg(_(e_trailing_characters_str), end);
	else
	    semsg(_(e_val_too_large), arg);
EXTERN char e_val_too_large[]
	INIT(= N_("E1510: Value too large: %s"));
	// overflow
	if (num > INT_MAX)
	    return FAIL;

	    // overflow
	    if (num > INT_MAX)
		return FAIL;

	    *num2 = (int)num;
" The following used to overflow and causing an use-after-free
func Test_history_max_val()

  set history=10
  call assert_fails(':history 2147483648', 'E1510:')
  set history&
endfunc

/**/
    2068,