From: Christian Brabandt Date: Mon, 23 Feb 2026 21:42:39 +0000 (+0000) Subject: patch 9.2.0077: [security]: Crash when recovering a corrupted swap file X-Git-Tag: v9.2.0077^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=65c1a143c331c886dc28888dd632708f953b4eb3;p=thirdparty%2Fvim.git 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 --- diff --git a/Filelist b/Filelist index b6414223f7..4b71b2aeef 100644 --- a/Filelist +++ b/Filelist @@ -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 \ diff --git a/src/memline.c b/src/memline.c index 604982a901..2d92aadb6d 100644 --- a/src/memline.c +++ b/src/memline.c @@ -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; } diff --git a/src/po/vim.pot b/src/po/vim.pot index 88bca4ef82..1ea35bab2d 100644 --- a/src/po/vim.pot +++ b/src/po/vim.pot @@ -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 \n" "Language-Team: LANGUAGE \n" @@ -1960,6 +1960,9 @@ msgstr "" msgid "???LINES MISSING" msgstr "" +msgid "???ILLEGAL BLOCK NUMBER" +msgstr "" + msgid "???BLOCK MISSING" msgstr "" diff --git a/src/testdir/samples/recover-crash1.swp b/src/testdir/samples/recover-crash1.swp new file mode 100644 index 0000000000..5fa9a8169c Binary files /dev/null and b/src/testdir/samples/recover-crash1.swp differ diff --git a/src/testdir/samples/recover-crash2.swp b/src/testdir/samples/recover-crash2.swp new file mode 100644 index 0000000000..01ab0e7cc3 Binary files /dev/null and b/src/testdir/samples/recover-crash2.swp differ diff --git a/src/testdir/test_recover.vim b/src/testdir/test_recover.vim index db59223c29..93425f1fb3 100644 --- a/src/testdir/test_recover.vim +++ b/src/testdir/test_recover.vim @@ -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 diff --git a/src/version.c b/src/version.c index 21a1f3207c..360e31edfb 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 77, /**/ 76, /**/