From 7e8bdd348c3bb6074ab795b97addb818705991bf Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 28 Feb 2026 12:33:47 +0800 Subject: [PATCH] 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 https://github.com/vim/vim/commit/65c1a143c331c886dc28888dd632708f953b4eb3 Co-authored-by: Christian Brabandt --- src/nvim/memline.c | 26 ++++++++++++-- test/old/testdir/samples/recover-crash1.swp | Bin 0 -> 9765 bytes test/old/testdir/samples/recover-crash2.swp | Bin 0 -> 9682 bytes test/old/testdir/test_recover.vim | 37 ++++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 test/old/testdir/samples/recover-crash1.swp create mode 100644 test/old/testdir/samples/recover-crash2.swp diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 519aa6cc59..f16514e169 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -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; } diff --git a/test/old/testdir/samples/recover-crash1.swp b/test/old/testdir/samples/recover-crash1.swp new file mode 100644 index 0000000000000000000000000000000000000000..5fa9a8169c3a75302500c6a755f9971ac06f9f31 GIT binary patch literal 9765 zcmYc?2=nw+u+%eRU|?Vn01*hBR$5hs;NfQFTQWaJl@;8p^b(J#p@&<7csmYI{P zS6p5I79Ax@~ literal 0 HcmV?d00001 diff --git a/test/old/testdir/samples/recover-crash2.swp b/test/old/testdir/samples/recover-crash2.swp new file mode 100644 index 0000000000000000000000000000000000000000..01ab0e7cc37aebbf14a3712604c4e804747f78d1 GIT binary patch literal 9682 zcmYc?2=nw+u+%eRU|?Vn01*hBR$5hs;NfQFTQWaJl@;8p^b(J#p@&<7csmYI{P zS6p5I79Ayqeh5SleJ_j%AA~X(8yOmaysE6Es30s93Sy4p(GVC7fx#LAEQ8fyqwX3F zfzc2c4S~@R7!85Z5E!Zjoin() + 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