CVE-2022-0417: Heap-based Buffer Overflow (CWE-122)
value: Heap-based Buffer Overflow GitHub repository vim/vim prior to 8.2.
CWE description: A heap overflow condition is a buffer overflow, where the buffer that can be overwritten is allocated in the heap portion of memory, generally meaning that the buffer was allocated using a routine such as malloc().
CVSS v3 base score: 7.8
Published: 2022-02-01T13:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/indent.c, src/option.c, src/testdir/test_options.vim, src/version.c, src/vim.h
Commit date: 2022-01-28 20:47:49 +0000
Commit message: patch 8.2.4245: ":retab 0" may cause illegal memory access

Problem:    ":retab 0" may cause illegal memory access.
Solution:   Limit the value of 'tabstop' to 10000.

Diff: diff --git a/src/indent.c b/src/indent.c
index 8dd4ef99cd9..8e9b0d148cf 100644
--- a/src/indent.c
+++ b/src/indent.c
@@ -69,11 +69,11 @@ tabstop_set(char_u *var, int **array)
     for (cp = var; *cp != NUL;)
     {
 	int n = atoi((char *)cp);
 
 	// Catch negative values, overflow and ridiculous big values.
-	if (n < 0 || n > 9999)
+	if (n < 0 || n > TABSTOP_MAX)
 	{
 	    semsg(_(e_invalid_argument_str), cp);
 	    vim_free(*array);
 	    *array = NULL;
 	    return FAIL;
@@ -1647,11 +1647,11 @@ ex_retab(exarg_T *eap)
     if (new_ts < 0 && *eap->arg == '-')
     {
 	emsg(_(e_argument_must_be_positive));
 	return;
     }
-    if (new_ts < 0 || new_ts > 9999)
+    if (new_ts < 0 || new_ts > TABSTOP_MAX)
     {
 	semsg(_(e_invalid_argument_str), eap->arg);
 	return;
     }
     if (new_ts == 0)
diff --git a/src/option.c b/src/option.c
index bb952fee5c7..482e960b254 100644
--- a/src/option.c
+++ b/src/option.c
@@ -3750,10 +3750,15 @@ set_num_option(
     if (curbuf->b_p_ts <= 0)
     {
 	errmsg = e_argument_must_be_positive;
 	curbuf->b_p_ts = 8;
     }
+    else if (curbuf->b_p_ts > TABSTOP_MAX)
+    {
+	errmsg = e_invalid_argument;
+	curbuf->b_p_ts = 8;
+    }
     if (p_tm < 0)
     {
 	errmsg = e_argument_must_be_positive;
 	p_tm = 0;
     }
@@ -5981,11 +5986,11 @@ buf_copy_options(buf_T *buf, int flags)
 	    buf->b_p_vsts = vim_strsave(p_vsts);
 	    COPY_OPT_SCTX(buf, BV_VSTS);
 	    if (p_vsts && p_vsts != empty_option)
 		(void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
 	    else
-		buf->b_p_vsts_array = 0;
+		buf->b_p_vsts_array = NULL;
 	    buf->b_p_vsts_nopaste = p_vsts_nopaste
 				 ? vim_strsave(p_vsts_nopaste) : NULL;
 #endif
 	    buf->b_p_sn = p_sn;
 	    COPY_OPT_SCTX(buf, BV_SN);
@@ -6801,13 +6806,11 @@ paste_option_changed(void)
 	    buf->b_p_et = 0;	    // no expandtab
 #ifdef FEAT_VARTABS
 	    if (buf->b_p_vsts)
 		free_string_option(buf->b_p_vsts);
 	    buf->b_p_vsts = empty_option;
-	    if (buf->b_p_vsts_array)
-		vim_free(buf->b_p_vsts_array);
-	    buf->b_p_vsts_array = 0;
+	    VIM_CLEAR(buf->b_p_vsts_array);
 #endif
 	}
 
 	// set global options
 	p_sm = 0;		    // no showmatch
@@ -6849,16 +6852,15 @@ paste_option_changed(void)
 #ifdef FEAT_VARTABS
 	    if (buf->b_p_vsts)
 		free_string_option(buf->b_p_vsts);
 	    buf->b_p_vsts = buf->b_p_vsts_nopaste
 			 ? vim_strsave(buf->b_p_vsts_nopaste) : empty_option;
-	    if (buf->b_p_vsts_array)
-		vim_free(buf->b_p_vsts_array);
+	    vim_free(buf->b_p_vsts_array);
 	    if (buf->b_p_vsts && buf->b_p_vsts != empty_option)
 		(void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
 	    else
-		buf->b_p_vsts_array = 0;
+		buf->b_p_vsts_array = NULL;
 #endif
 	}
 
 	// restore global options
 	p_sm = save_sm;
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 2265894399d..2af13d94f44 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -366,10 +366,12 @@ func Test_set_errors()
   call assert_fails('set history=-1', 'E487:')
   call assert_fails('set report=-1', 'E487:')
   call assert_fails('set shiftwidth=-1', 'E487:')
   call assert_fails('set sidescroll=-1', 'E487:')
   call assert_fails('set tabstop=-1', 'E487:')
+  call assert_fails('set tabstop=10000', 'E474:')
+  call assert_fails('set tabstop=5500000000', 'E474:')
   call assert_fails('set textwidth=-1', 'E487:')
   call assert_fails('set timeoutlen=-1', 'E487:')
   call assert_fails('set updatecount=-1', 'E487:')
   call assert_fails('set updatetime=-1', 'E487:')
   call assert_fails('set winheight=-1', 'E487:')
diff --git a/src/version.c b/src/version.c
index 0caa33a310e..8275e8f2ae6 100644
--- a/src/version.c
+++ b/src/version.c
@@ -748,10 +748,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    4245,
 /**/
     4244,
 /**/
     4243,
 /**/
diff --git a/src/vim.h b/src/vim.h
index 15ed6262d9c..86acb6dcda4 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -2083,10 +2083,12 @@ typedef int sock_T;
 #define VAR_TYPE_BLOB	    10
 #define VAR_TYPE_INSTR	    11
 
 #define DICT_MAXNEST 100	// maximum nesting of lists and dicts
 
+#define TABSTOP_MAX 9999
+
 #ifdef FEAT_CLIPBOARD
 
 // VIM_ATOM_NAME is the older Vim-specific selection type for X11.  Still
 // supported for when a mix of Vim versions is used. VIMENC_ATOM_NAME includes
 // the encoding to support Vims using different 'encoding' values.

Vulnerable code: 	if (n < 0 || n > 9999)
    if (new_ts < 0 || new_ts > 9999)
		buf->b_p_vsts_array = 0;
	    if (buf->b_p_vsts_array)
		vim_free(buf->b_p_vsts_array);
	    buf->b_p_vsts_array = 0;
	    if (buf->b_p_vsts_array)
		vim_free(buf->b_p_vsts_array);
		buf->b_p_vsts_array = 0;
Fixed code: 	if (n < 0 || n > TABSTOP_MAX)
    if (new_ts < 0 || new_ts > TABSTOP_MAX)
    else if (curbuf->b_p_ts > TABSTOP_MAX)
    {
	errmsg = e_invalid_argument;
	curbuf->b_p_ts = 8;
    }
		buf->b_p_vsts_array = NULL;
	    VIM_CLEAR(buf->b_p_vsts_array);
	    vim_free(buf->b_p_vsts_array);
		buf->b_p_vsts_array = NULL;
  call assert_fails('set tabstop=10000', 'E474:')
  call assert_fails('set tabstop=5500000000', 'E474:')
/**/
    4245,
#define TABSTOP_MAX 9999