RHEL-2159 vim: Heap Use After Free in function ins_compl_get_exp in vim/vim

Resolves: RHEL-2159
This commit is contained in:
Zdenek Dohnal 2025-02-25 14:17:22 +01:00
parent 2bba9e8166
commit 99204e95ee
2 changed files with 276 additions and 1 deletions

262
vim-CVE-2023-4752.patch Normal file
View File

@ -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' .. "\<cr>")
+
+ 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\<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', {})
@@ -724,7 +723,23 @@ func Test_z1_complete_no_history()
exe "normal owh\<C-X>\<C-K>"
exe "normal owh\<C-N>"
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

View File

@ -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 <zdohnal@redhat.com> - 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 <zdohnal@redhat.com> - 2:8.2.2637-21
- RHEL-40602 CVE-2021-3903 vim: heap-based buffer overflow vulnerability