vim-patch:9.2.0077: [security]: Crash when recovering a corrupted swap file (#38104)

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

65c1a143c3

Co-authored-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
zeertzjq
2026-02-28 12:33:47 +08:00
committed by GitHub
parent a416494e64
commit 7e8bdd348c
4 changed files with 60 additions and 3 deletions

View File

@@ -1041,9 +1041,12 @@ void ml_recover(bool checkext)
// This is slow, but it works.
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, line_count,
NULL, 0, false) != OK) {
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, false) != OK) {
cannot_open = true;
} else {
lnum += line_count;
@@ -1066,6 +1069,23 @@ void ml_recover(bool checkext)
bnum = pp->pb_pointer[idx].pe_bnum;
line_count = pp->pb_pointer[idx].pe_line_count;
page_count = (unsigned)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++, _("???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;
}

Binary file not shown.

Binary file not shown.

View File

@@ -478,4 +478,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 !has('nvim') && 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' " Xpoc1.swp (non-hidden) doesn't work in Nvim
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' " Xpoc1.swp (non-hidden) doesn't work in Nvim
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