CVE-2022-3016: Use After Free (CWE-416)
value: Use After Free in GitHub repository vim/vim prior to 9.0.0286.
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: 2022-08-28T12:15Z
Repository: https://github.com/vim/vim
Language: C
Files changed: src/quickfix.c, src/testdir/test_quickfix.vim, src/version.c
Commit date: 2022-08-27 20:59:57 +0100
Commit message: patch 9.0.0286: using freed memory when location list changed in autocmd

Problem:    Using freed memory when location list changed in autocmd.
Solution:   Return QF_ABORT and handle it. (Yegappan Lakshmanan,
            closes #10993)

Diff: diff --git a/src/quickfix.c b/src/quickfix.c
index 6af62e8dfe56..78f6880d8020 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -592,10 +592,11 @@ enum {
     QF_OK = 1,
     QF_END_OF_INPUT = 2,
     QF_NOMEM = 3,
     QF_IGNORE_LINE = 4,
     QF_MULTISCAN = 5,
+    QF_ABORT = 6
 };
 
 /*
  * State information used to parse lines and add entries to a quickfix/location
  * list.
@@ -3151,11 +3152,11 @@ qf_jump_to_usable_window(int qf_fnum, int newwin, int *opened_window)
 }
 
 /*
  * Edit the selected file or help file.
  * Returns OK if successfully edited the file, FAIL on failing to open the
- * buffer and NOTDONE if the quickfix/location list was freed by an autocmd
+ * buffer and QF_ABORT if the quickfix/location list was freed by an autocmd
  * when opening the buffer.
  */
     static int
 qf_jump_edit_buffer(
 	qf_info_T	*qi,
@@ -3197,18 +3198,18 @@ qf_jump_edit_buffer(
 
 	if (wp == NULL && curwin->w_llist != qi)
 	{
 	    emsg(_(e_current_window_was_closed));
 	    *opened_window = FALSE;
-	    return NOTDONE;
+	    return QF_ABORT;
 	}
     }
 
     if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid))
     {
 	emsg(_(e_current_quickfix_list_was_changed));
-	return NOTDONE;
+	return QF_ABORT;
     }
 
     // Check if the list was changed.  The pointers may happen to be identical,
     // thus also check qf_changedtick.
     if (old_qf_curlist != qi->qf_curlist
@@ -3217,11 +3218,11 @@ qf_jump_edit_buffer(
     {
 	if (qfl_type == QFLT_QUICKFIX)
 	    emsg(_(e_current_quickfix_list_was_changed));
 	else
 	    emsg(_(e_current_location_list_was_changed));
-	return NOTDONE;
+	return QF_ABORT;
     }
 
     return retval;
 }
 
@@ -3315,11 +3316,12 @@ qf_jump_print_msg(
  * Find a usable window for opening a file from the quickfix/location list. If
  * a window is not found then open a new window. If 'newwin' is TRUE, then open
  * a new window.
  * Returns OK if successfully jumped or opened a window. Returns FAIL if not
  * able to jump/open a window.  Returns NOTDONE if a file is not associated
- * with the entry.
+ * with the entry.  Returns QF_ABORT if the quickfix/location list was modified
+ * by an autocmd.
  */
     static int
 qf_jump_open_window(
 	qf_info_T	*qi,
 	qfline_T	*qf_ptr,
@@ -3342,11 +3344,11 @@ qf_jump_open_window(
     {
 	if (qfl_type == QFLT_QUICKFIX)
 	    emsg(_(e_current_quickfix_list_was_changed));
 	else
 	    emsg(_(e_current_location_list_was_changed));
-	return FAIL;
+	return QF_ABORT;
     }
 
     // If currently in the quickfix window, find another window to show the
     // file in.
     if (bt_quickfix(curbuf) && !*opened_window)
@@ -3366,22 +3368,22 @@ qf_jump_open_window(
     {
 	if (qfl_type == QFLT_QUICKFIX)
 	    emsg(_(e_current_quickfix_list_was_changed));
 	else
 	    emsg(_(e_current_location_list_was_changed));
-	return FAIL;
+	return QF_ABORT;
     }
 
     return OK;
 }
 
 /*
  * Edit a selected file from the quickfix/location list and jump to a
  * particular line/column, adjust the folds and display a message about the
  * jump.
  * Returns OK on success and FAIL on failing to open the file/buffer.  Returns
- * NOTDONE if the quickfix/location list is freed by an autocmd when opening
+ * QF_ABORT if the quickfix/location list is freed by an autocmd when opening
  * the file.
  */
     static int
 qf_jump_to_buffer(
 	qf_info_T	*qi,
@@ -3506,18 +3508,24 @@ qf_jump_newwin(qf_info_T	*qi,
     prev_winid = curwin->w_id;
 
     retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window);
     if (retval == FAIL)
 	goto failed;
+    if (retval == QF_ABORT)
+    {
+	qi = NULL;
+	qf_ptr = NULL;
+	goto theend;
+    }
     if (retval == NOTDONE)
 	goto theend;
 
     retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, prev_winid,
 				  &opened_window, old_KeyTyped, print_message);
-    if (retval == NOTDONE)
+    if (retval == QF_ABORT)
     {
-	// Quickfix/location list is freed by an autocmd
+	// Quickfix/location list was modified by an autocmd
 	qi = NULL;
 	qf_ptr = NULL;
     }
 
     if (retval != OK)
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
index 762fa8d8d0e3..31d36ef1d63c 100644
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -6361,7 +6361,24 @@ func Test_quickfixtextfunc_recursive()
 
   let &quickfixtextfunc = ''
   cclose
 endfunc
 
+" Test for replacing the location list from an autocmd. This used to cause a
+" read from freed memory.
+func Test_loclist_replace_autocmd()
+  %bw!
+  call setloclist(0, [], 'f')
+  let s:bufnr = bufnr()
+  cal setloclist(0, [{'0': 0, '': ''}])
+  au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r')
+  lopen
+  try
+    exe "norm j\<CR>"
+  catch
+  endtry
+  lnext
+  %bw!
+  call setloclist(0, [], 'f')
+endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 6b4bdfab29b2..206548373a01 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,10 +705,12 @@ static char *(features[]) =
 	NULL
 };
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    286,
 /**/
     285,
 /**/
     284,
 /**/

Vulnerable code:  * buffer and NOTDONE if the quickfix/location list was freed by an autocmd
	    return NOTDONE;
	return NOTDONE;
	return NOTDONE;
 * with the entry.
	return FAIL;
	return FAIL;
 * NOTDONE if the quickfix/location list is freed by an autocmd when opening
    if (retval == NOTDONE)
	// Quickfix/location list is freed by an autocmd
Fixed code:     QF_ABORT = 6
 * buffer and QF_ABORT if the quickfix/location list was freed by an autocmd
	    return QF_ABORT;
	return QF_ABORT;
	return QF_ABORT;
 * with the entry.  Returns QF_ABORT if the quickfix/location list was modified
 * by an autocmd.
	return QF_ABORT;
	return QF_ABORT;
 * QF_ABORT if the quickfix/location list is freed by an autocmd when opening
    if (retval == QF_ABORT)
    {
	qi = NULL;
	qf_ptr = NULL;
	goto theend;
    }
    if (retval == QF_ABORT)
	// Quickfix/location list was modified by an autocmd
" Test for replacing the location list from an autocmd. This used to cause a
" read from freed memory.
func Test_loclist_replace_autocmd()
  %bw!
  call setloclist(0, [], 'f')
  let s:bufnr = bufnr()
  cal setloclist(0, [{'0': 0, '': ''}])
  au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r')
  lopen
  try
    exe "norm j\<CR>"
  catch
  endtry
  lnext
  %bw!
  call setloclist(0, [], 'f')
endfunc
/**/
    286,