diff --git a/vim-CVE-2023-4752.patch b/vim-CVE-2023-4752.patch new file mode 100644 index 00000000..a698a270 --- /dev/null +++ b/vim-CVE-2023-4752.patch @@ -0,0 +1,262 @@ +diff --git a/src/insexpand.c b/src/insexpand.c +index 8d76c68..2ce675d 100644 +--- a/src/insexpand.c ++++ b/src/insexpand.c +@@ -2162,7 +2162,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) +@@ -2675,10 +2676,12 @@ ins_compl_get_exp(pos_T *ini) + { + static pos_T first_match_pos; + static pos_T last_match_pos; +- static char_u *e_cpt = (char_u *)""; // curr. entry in 'complete' ++ static char_u *e_cpt; // curr. entry in 'complete' ++ static char_u *e_cpt_copy; // copy of curr. entry in 'complete' + static int found_all = FALSE; // Found all matches of a + // certain type. + static buf_T *ins_buf = NULL; // buffer being scanned ++ static int st_cleared = FALSE; + + pos_T *pos; + char_u **matches; +@@ -2697,12 +2700,34 @@ ins_compl_get_exp(pos_T *ini) + + if (!compl_started) + { +- FOR_ALL_BUFFERS(ins_buf) +- ins_buf->b_scanned = 0; ++ buf_T *buf; ++ ++ FOR_ALL_BUFFERS(buf) ++ buf->b_scanned = 0; ++ if (!st_cleared) ++ { ++ // this code does not match upstream commit, upstream clears struct `st` ++ // which includes the members which are arguments of `CLEAR_FIELD()` macros ++ // below. The code below should do the same thing as upstream `CLEAR_FIELD(st)` ++ CLEAR_FIELD(e_cpt); ++ CLEAR_FIELD(e_cpt_copy); ++ CLEAR_FIELD(ins_buf); ++ CLEAR_FIELD(pos); ++ CLEAR_FIELD(set_match_pos); ++ CLEAR_FIELD(first_match_pos); ++ CLEAR_FIELD(last_match_pos); ++ CLEAR_FIELD(found_all); ++ CLEAR_FIELD(dict); ++ CLEAR_FIELD(dict_f); ++ st_cleared = TRUE; ++ } + found_all = FALSE; + ins_buf = curbuf; +- e_cpt = (compl_cont_status & CONT_LOCAL) +- ? (char_u *)"." : curbuf->b_p_cpt; ++ vim_free(e_cpt_copy); ++ // Make a copy of 'complete', if case the buffer is wiped out. ++ e_cpt_copy = vim_strsave((compl_cont_status & CONT_LOCAL) ++ ? (char_u *)"." : curbuf->b_p_cpt); ++ e_cpt = e_cpt_copy == NULL ? (char_u *)"" : e_cpt_copy; + last_match_pos = first_match_pos = *ini; + } + else if (ins_buf != curbuf && !buf_valid(ins_buf)) +@@ -3100,7 +3125,7 @@ ins_compl_get_exp(pos_T *ini) + else + { + // Mark a buffer scanned when it has been scanned completely +- if (type == 0 || type == CTRL_X_PATH_PATTERNS) ++ if (buf_valid(ins_buf) && (type == 0 || type == CTRL_X_PATH_PATTERNS)) + ins_buf->b_scanned = TRUE; + + compl_started = FALSE; +@@ -3210,6 +3235,7 @@ ins_compl_next( + int found_end = FALSE; + 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. +@@ -3342,6 +3368,13 @@ ins_compl_next( + } + } + ++ 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) + { +diff --git a/src/move.c b/src/move.c +index 8ac1d0e..55cbd24 100644 +--- a/src/move.c ++++ b/src/move.c +@@ -649,6 +649,7 @@ cursor_valid(void) + void + validate_cursor(void) + { ++ check_cursor_lnum(); + check_cursor_moved(curwin); + if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) + curs_columns(TRUE); +diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak +index 108c528..e800864 100644 +--- a/src/testdir/Make_all.mak ++++ b/src/testdir/Make_all.mak +@@ -92,6 +92,7 @@ NEW_TESTS = \ + test_conceal \ + test_const \ + test_cpoptions \ ++ test_crash \ + test_crypt \ + test_cscope \ + test_cursor_func \ +@@ -348,6 +349,7 @@ NEW_TESTS_RES = \ + test_conceal.res \ + test_const.res \ + test_cpoptions.res \ ++ test_crash.res \ + test_crypt.res \ + test_cscope.res \ + test_cursor_func.res \ +diff --git a/src/testdir/crash/poc_tagfunc.vim b/src/testdir/crash/poc_tagfunc.vim +new file mode 100644 +index 0000000..49d9b6f +--- /dev/null ++++ b/src/testdir/crash/poc_tagfunc.vim +@@ -0,0 +1,6 @@ ++fu Tagfunc(t,f,o) ++ bw ++endf ++set tagfunc=Tagfunc ++n0 ++sil0norm0i +diff --git a/src/testdir/term_util.vim b/src/testdir/term_util.vim +index 8541ef3..3a06019 100644 +--- a/src/testdir/term_util.vim ++++ b/src/testdir/term_util.vim +@@ -54,6 +54,7 @@ endfunc + " "cols" - width of the terminal window (max. 78) + " "statusoff" - number of lines the status is offset from default + " "wait_for_ruler" - if zero then don't wait for ruler to show ++" "cmd" - run any other command, e.g. "xxd" (used in xxd test) + func RunVimInTerminal(arguments, options) + " If Vim doesn't exit a swap file remains, causing other tests to fail. + " Remove it here. +@@ -88,7 +89,11 @@ func RunVimInTerminal(arguments, options) + let reset_u7 = ' --cmd "set t_u7=" ' + endif + +- let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments ++ if empty(get(a:options, 'cmd', '')) ++ let cmd = GetVimCommandCleanTerm() .. reset_u7 .. a:arguments ++ else ++ let cmd = get(a:options, 'cmd') ++ endif + + let options = #{curwin: 1} + if &termwinsize == '' +@@ -114,7 +119,7 @@ func RunVimInTerminal(arguments, options) + + call TermWait(buf) + +- if get(a:options, 'wait_for_ruler', 1) ++ if get(a:options, 'wait_for_ruler', 1) && empty(get(a:options, 'cmd', '')) + " Wait for "All" or "Top" of the ruler to be shown in the last line or in + " the status line of the last window. This can be quite slow (e.g. when + " using valgrind). +diff --git a/src/testdir/test_crash.vim b/src/testdir/test_crash.vim +new file mode 100644 +index 0000000..4be1b49 +--- /dev/null ++++ b/src/testdir/test_crash.vim +@@ -0,0 +1,40 @@ ++" Some tests, that used to crash Vim ++source check.vim ++source screendump.vim ++ ++CheckScreendump ++ ++func Test_crash1() ++ if !executable('sh') ++ throw 'Skipped: sh not executable!' ++ endif ++ " The following used to crash Vim ++ let opts = #{cmd: 'sh'} ++ let vim = GetVimProg() ++ ++ let buf = RunVimInTerminal('sh', opts) ++ ++ let file = 'crash/poc_tagfunc.vim' ++ let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'" ++ let args = printf(cmn_args, vim, file) ++ call term_sendkeys(buf, args .. ++ \ ' || echo "crash 5: [OK]" >> X_crash1_result.txt' .. "\") ++ ++ call TermWait(buf, 100) ++ ++ " clean up ++ exe buf .. "bw!" ++ ++ sp X_crash1_result.txt ++ ++ let expected = [ ++ \ 'crash 5: [OK]', ++ \ ] ++ ++ call assert_equal(expected, getline(1, '$')) ++ bw! ++ ++ call delete('X_crash1_result.txt') ++endfunc ++ ++" vim: shiftwidth=2 sts=2 expandtab +diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim +index 613e952..24232d8 100644 +--- a/src/testdir/test_ins_complete.vim ++++ b/src/testdir/test_ins_complete.vim +@@ -496,9 +496,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\\") +- call TermWait(buf, 100) ++ call TermWait(buf, 200) + call term_sendkeys(buf, "\") + call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {}) + +@@ -724,7 +723,23 @@ func Test_z1_complete_no_history() + exe "normal owh\\" + exe "normal owh\" + call assert_equal(currmess, execute('messages')) +- close! ++ 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 diff --git a/vim.spec b/vim.spec index c7125b99..982fba3e 100644 --- a/vim.spec +++ b/vim.spec @@ -27,7 +27,7 @@ Summary: The VIM editor URL: http://www.vim.org/ Name: vim Version: %{baseversion}.%{patchlevel} -Release: 21%{?dist} +Release: 22%{?dist} License: Vim and MIT Source0: ftp://ftp.vim.org/pub/vim/unix/vim-%{baseversion}-%{patchlevel}.tar.bz2 Source1: virc @@ -138,6 +138,15 @@ Patch3052: 0001-patch-8.2.5037-cursor-position-may-be-invalid-after-.patch Patch3053:0001-patch-9.0.0339-no-check-if-the-return-value-of-XChan.patch # RHEL-40602 CVE-2021-3903 vim: heap-based buffer overflow vulnerability Patch3054: 0001-patch-8.2.3564-invalid-memory-access-when-scrolling-.patch +# RHEL-2159 vim: Heap Use After Free in function ins_compl_get_exp in vim/vim +# combined patch of some parts of the following commits: +# https://github.com/vim/vim/commit/e1dc9a6275 +# https://github.com/vim/vim/commit/ee9166eb3b +# https://github.com/vim/vim/commit/0ff01835a4 +# https://github.com/vim/vim/commit/d4566c14e7 +# https://github.com/vim/vim/commit/d979d64fa2 +# https://github.com/vim/vim/commit/e2528ae111 +Patch3055: vim-CVE-2023-4752.patch # gcc is no longer in buildroot by default BuildRequires: gcc @@ -379,6 +388,7 @@ perl -pi -e "s,bin/nawk,bin/awk,g" runtime/tools/mve.awk %patch3052 -p1 -b .cve1927 %patch3053 -p1 -b .cve47024 %patch -P 3054 -p1 -b .cve2021-3903 +%patch -P 3055 -p1 -b .CVE-2023-4752 %build cd src @@ -936,6 +946,9 @@ touch %{buildroot}/%{_datadir}/%{name}/vimfiles/doc/tags %endif %changelog +* Tue Feb 25 2025 Zdenek Dohnal - 2:8.2.2637-22 +- RHEL-2159 vim: Heap Use After Free in function ins_compl_get_exp in vim/vim + * Mon Aug 05 2024 Zdenek Dohnal - 2:8.2.2637-21 - RHEL-40602 CVE-2021-3903 vim: heap-based buffer overflow vulnerability