patch 9.2.0077: [security]: Crash when recovering a corrupted swap file

Problem:  memline: a crafted swap files with bogus pe_page_count/pe_bnum
          values could cause a multi-GB allocation via mf_get(), and
          invalid pe_old_lnum/pe_line_count values could cause a SEGV
          when passed to readfile() (ehdgks0627, un3xploitable)
Solution: Add bounds checks on pe_page_count and pe_bnum against
          mf_blocknr_max before descending into the block tree, and
          validate pe_old_lnum >= 1 and pe_line_count > 0 before calling
          readfile().

Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-r2gw-2x48-jj5p

Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Christian Brabandt
2026-02-23 21:42:39 +00:00
parent bb6de2105b
commit 65c1a143c3
7 changed files with 71 additions and 3 deletions
+1
View File
@@ -213,6 +213,7 @@ SRC_ALL = \
src/testdir/ru_RU/LC_MESSAGES/__PACKAGE__.po \
src/testdir/runtest.vim \
src/testdir/samples/*.html \
src/testdir/samples/*.swp \
src/testdir/samples/*.txt \
src/testdir/samples/*.vim \
src/testdir/samples/evil.zip \
+27 -2
View File
@@ -1595,8 +1595,12 @@ ml_recover(int checkext)
if (!cannot_open)
{
line_count = pp->pb_pointer[idx].pe_line_count;
if (readfile(curbuf->b_ffname, NULL, lnum,
pp->pb_pointer[idx].pe_old_lnum - 1,
linenr_T pe_old_lnum = pp->pb_pointer[idx].pe_old_lnum;
// Validate pe_line_count and pe_old_lnum from the
// untrusted swap file before passing to readfile().
if (line_count <= 0 || pe_old_lnum < 1 ||
readfile(curbuf->b_ffname, NULL, lnum,
pe_old_lnum - 1,
line_count, NULL, 0) != OK)
cannot_open = TRUE;
else
@@ -1627,6 +1631,27 @@ ml_recover(int checkext)
bnum = pp->pb_pointer[idx].pe_bnum;
line_count = pp->pb_pointer[idx].pe_line_count;
page_count = pp->pb_pointer[idx].pe_page_count;
// Validate pe_bnum and pe_page_count from the untrusted
// swap file before passing to mf_get(), which uses
// page_count to calculate allocation size. A bogus value
// (e.g. 0x40000000) would cause a multi-GB allocation.
// pe_page_count must be >= 1 and bnum + page_count must
// not exceed the number of pages in the swap file.
if (page_count < 1
|| bnum + page_count > mfp->mf_blocknr_max + 1)
{
++error;
ml_append(lnum++,
(char_u *)_("???ILLEGAL BLOCK NUMBER"),
(colnr_T)0, TRUE);
// Skip this entry and pop back up the stack to keep
// recovering whatever else we can.
idx = ip->ip_index + 1;
bnum = ip->ip_bnum;
page_count = 1;
--buf->b_ml.ml_stack_top;
continue;
}
idx = 0;
continue;
}
+4 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim\n"
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
"POT-Creation-Date: 2026-02-19 17:55+0000\n"
"POT-Creation-Date: 2026-02-27 21:04+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1960,6 +1960,9 @@ msgstr ""
msgid "???LINES MISSING"
msgstr ""
msgid "???ILLEGAL BLOCK NUMBER"
msgstr ""
msgid "???BLOCK MISSING"
msgstr ""
Binary file not shown.
Binary file not shown.
+37
View File
@@ -471,4 +471,41 @@ func Test_noname_buffer()
call assert_equal(['one', 'two'], getline(1, '$'))
endfunc
" Test for recovering a corrupted swap file, those caused a crash
func Test_recover_corrupted_swap_file1()
CheckUnix
" only works correctly on 64bit Unix systems:
if v:sizeoflong != 8 || !has('unix')
throw 'Skipped: Corrupt Swap file sample requires a 64bit Unix build'
endif
" Test 1: Heap buffer-overflow
new
let sample = 'samples/recover-crash1.swp'
let target = 'Xpoc1.swp'
call filecopy(sample, target)
try
sil recover! Xpoc1
catch /^Vim\%((\S\+)\)\=:E1364:/
endtry
let content = getline(1, '$')->join()
call assert_match('???ILLEGAL BLOCK NUMBER', content)
call delete(target)
bw!
"
" " Test 2: Segfault
new
let sample = 'samples/recover-crash2.swp'
let target = 'Xpoc2.swp'
call filecopy(sample, target)
try
sil recover! Xpoc2
catch /^Vim\%((\S\+)\)\=:E1364:/
endtry
let content = getline(1, '$')->join()
call assert_match('???ILLEGAL BLOCK NUMBER', content)
call assert_match('???LINES MISSING', content)
call delete(target)
bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
77,
/**/
76,
/**/