CVE-2022-3297 (published 2022-09-25T19:15Z)

Use After Free in GitHub repository vim/vim prior to 9.0.0579.

CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-416
cwe_name: Use After Free
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.
extended_description: 
             
             
             
			 
             
             
         
         

Fix information:
repo_url: https://github.com/vim/vim
commit_details: repo_url: https://github.com/vim/vim
author: Bram Moolenaar
author_date: 2022-09-24 19:20:30+01:00
msg: patch 9.0.0579: using freed memory when 'tagfunc' wipes out buffer

Problem:    Using freed memory when 'tagfunc' wipes out buffer that holds
            'complete'.
Solution:   Make a copy of the option.  Make sure cursor position is valid.
repository: repo_url: https://github.com/vim/vim
repo_name: vim/vim
description: The official Vim repository
repo_language: Vim Script
owner: vim
file_changes: filename: insexpand.c
change_type: ModificationType.MODIFY
diff: @@ -2490,7 +2490,8 @@ ins_compl_next_buf(buf_T *buf, int flag)
 
     if (flag == 'w')		// just windows
     {
-	if (buf == curbuf || wp == NULL)  // first call for this flag/expansion
+	if (buf == curbuf || !win_valid(wp))
+	    // first call for this flag/expansion or window was closed
 	    wp = curwin;
 	while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin
 		&& wp->w_buffer->b_scanned)
@@ -3188,9 +3189,10 @@ enum
  */
 typedef struct
 {
-    char_u	*e_cpt;			// current entry in 'complete'
+    char_u	*e_cpt_copy;		// copy of 'complete'
+    char_u	*e_cpt;			// current entry in "e_cpt_copy"
     buf_T	*ins_buf;		// buffer being scanned
-    pos_T	*cur_match_pos;			// current match position
+    pos_T	*cur_match_pos;		// current match position
     pos_T	prev_match_pos;		// previous match position
     int		set_match_pos;		// save first_match_pos/last_match_pos
     pos_T	first_match_pos;	// first match position
@@ -3257,7 +3259,8 @@ process_next_cpt_value(
 	st->set_match_pos = TRUE;
     }
     else if (vim_strchr((char_u *)"buwU", *st->e_cpt) != NULL
-	    && (st->ins_buf = ins_compl_next_buf(st->ins_buf, *st->e_cpt)) != curbuf)
+	    && (st->ins_buf = ins_compl_next_buf(
+					   st->ins_buf, *st->e_cpt)) != curbuf)
     {
 	// Scan a buffer, but not the current one.
 	if (st->ins_buf->b_ml.ml_mfp != NULL)   // loaded buffer
@@ -3756,19 +3759,30 @@ get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini)
     static int
 ins_compl_get_exp(pos_T *ini)
 {
-    static ins_compl_next_state_T st;
+    static ins_compl_next_state_T   st;
+    static int			    st_cleared = FALSE;
     int		i;
     int		found_new_match;
     int		type = ctrl_x_mode;
 
     if (!compl_started)
     {
-	FOR_ALL_BUFFERS(st.ins_buf)
-	    st.ins_buf->b_scanned = 0;
+	buf_T *buf;
+
+	FOR_ALL_BUFFERS(buf)
+	    buf->b_scanned = 0;
+	if (!st_cleared)
+	{
+	    CLEAR_FIELD(st);
+	    st_cleared = TRUE;
+	}
 	st.found_all = FALSE;
 	st.ins_buf = curbuf;
-	st.e_cpt = (compl_cont_status & CONT_LOCAL)
-					    ? (char_u *)"." : curbuf->b_p_cpt;
+	vim_free(st.e_cpt_copy);
+	// Make a copy of 'complete', if case the buffer is wiped out.
+	st.e_cpt_copy = vim_strsave((compl_cont_status & CONT_LOCAL)
+					    ? (char_u *)"." : curbuf->b_p_cpt);
+	st.e_cpt = st.e_cpt_copy == NULL ? (char_u *)"" : st.e_cpt_copy;
 	st.last_match_pos = st.first_match_pos = *ini;
     }
     else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf))
@@ -4112,6 +4126,7 @@ ins_compl_next(
     int	    todo = count;
     int	    advance;
     int	    started = compl_started;
+    buf_T   *orig_curbuf = curbuf;
 
     // When user complete function return -1 for findstart which is next
     // time of 'always', compl_shown_match become NULL.
@@ -4144,6 +4159,13 @@ ins_compl_next(
 							&num_matches) == -1)
 	return -1;
 
+    if (curbuf != orig_curbuf)
+    {
+	// In case some completion function switched buffer, don't want to
+	// insert the completion elsewhere.
+	return -1;
+    }
+
     // Insert the text of the new completion, or the compl_leader.
     if (compl_no_insert && !started)
     {

programming_language: C
filename: move.c
change_type: ModificationType.MODIFY
diff: @@ -683,6 +683,7 @@ cursor_valid(void)
     void
 validate_cursor(void)
 {
+    check_cursor();
     check_cursor_moved(curwin);
     if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW))
 	curs_columns(TRUE);

programming_language: C++
filename: test_ins_complete.vim
change_type: ModificationType.MODIFY
diff: @@ -547,9 +547,8 @@ func Test_pum_with_preview_win()
 
   call writefile(lines, 'Xpreviewscript')
   let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
-  call TermWait(buf, 50)
   call term_sendkeys(buf, "Gi\<C-X>\<C-O>")
-  call TermWait(buf, 100)
+  call TermWait(buf, 200)
   call term_sendkeys(buf, "\<C-N>")
   call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {})
 
@@ -2172,4 +2171,21 @@ func Test_ins_complete_end_of_line()
   bwipe!
 endfunc
 
+func s:Tagfunc(t,f,o)
+  bwipe!
+  return []
+endfunc
+
+" This was using freed memory, since 'complete' was in a wiped out buffer.
+" Also using a window that was closed.
+func Test_tagfunc_wipes_out_buffer()
+  new
+  set complete=.,t,w,b,u,i
+  se tagfunc=s:Tagfunc
+  sil norm i
+
+  bwipe!
+endfunc
+
+
 " vim: shiftwidth=2 sts=2 expandtab

programming_language: Lua
filename: version.c
change_type: ModificationType.MODIFY
diff: @@ -699,6 +699,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    579,
 /**/
     578,
 /**/

programming_language: Ruby