]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0077: [security]: Crash when recovering a corrupted swap file v9.2.0077
authorChristian Brabandt <cb@256bit.org>
Mon, 23 Feb 2026 21:42:39 +0000 (21:42 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 27 Feb 2026 21:04:55 +0000 (21:04 +0000)
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>
Filelist
src/memline.c
src/po/vim.pot
src/testdir/samples/recover-crash1.swp [new file with mode: 0644]
src/testdir/samples/recover-crash2.swp [new file with mode: 0644]
src/testdir/test_recover.vim
src/version.c

index b6414223f7b1ad939adf9bda1631a3fcae43cf5d..4b71b2aeefbaae6d41a98574f9b93e766870ce8a 100644 (file)
--- 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 \
index 604982a901b2d3e4c97982d2ced0a3193e2c7d30..2d92aadb6d49416063cd73f6f223bf245e97acbd 100644 (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;
                }
index 88bca4ef827f7635219dce9fc10377a5e541eaaa..1ea35bab2d64839a0c999de5ac59dff03b944798 100644 (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 ""
 
diff --git a/src/testdir/samples/recover-crash1.swp b/src/testdir/samples/recover-crash1.swp
new file mode 100644 (file)
index 0000000..5fa9a81
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 (file)
index 0000000..01ab0e7
Binary files /dev/null and b/src/testdir/samples/recover-crash2.swp differ
index db59223c295f7c884945b53f7d2bc8e9ad32ba10..93425f1fb344da9999ebbce3da5102ee1bb6df35 100644 (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
index 21a1f3207c80b2b938b9aca0d7b30229c617bf25..360e31edfb1721dbdb1154b1fd2b9c7adefb0c4b 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    77,
 /**/
     76,
 /**/