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 0000000000..5fa9a8169c Binary files /dev/null and b/test/old/testdir/samples/recover-crash1.swp differ diff --git a/test/old/testdir/samples/recover-crash2.swp b/test/old/testdir/samples/recover-crash2.swp new file mode 100644 index 0000000000..01ab0e7cc3 Binary files /dev/null and b/test/old/testdir/samples/recover-crash2.swp differ diff --git a/test/old/testdir/test_recover.vim b/test/old/testdir/test_recover.vim index ca1ee11b44..e319a81975 100644 --- a/test/old/testdir/test_recover.vim +++ b/test/old/testdir/test_recover.vim @@ -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