patch 9.2.0738: ml_recover() may write beyond block buffer
Commit:
https://github.com/vim/vim/commit/43939cf9ebf5302bb51c85d00ac83de7e01452d5
Author: Christian Brabandt <
c...@256bit.org>
Date: Sat Jun 27 08:39:43 2026 +0000
patch 9.2.0738: ml_recover() may write beyond block buffer
Problem: A crafted swap file can cause an out-of-bounds write during
recovery when the same block is referenced twice with
different pe_page_count values (cipher-creator)
Solution: Check hp->bh_page_count against page_count after mf_get() and
clamp page_count to the actual block size.
closes: #20645
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/memline.c b/src/memline.c
index 4ad09f43a..2e0403d41 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -1680,6 +1680,20 @@ ml_recover(int checkext)
*/
has_error = FALSE;
+ // Verify the cached block's actual size matches the
+ // pointer entry's pe_page_count. mf_get() cache hits
+ // return the original block without resizing, so a
+ // crafted swap file referencing the same block twice
+ // with different pe_page_count values would cause an
+ // OOB write below.
+ if (hp->bh_page_count != page_count)
+ {
+ ++error;
+ ml_append(lnum++, (char_u *)_("??? BLOCK PAGE COUNT MISMATCH"),
+ (colnr_T)0, TRUE);
+ page_count = hp->bh_page_count;
+ }
+
// Check the length of the block.
// If wrong, use the length given in the pointer block.
if (page_count * mfp->mf_page_size != dp->db_txt_end)
diff --git a/src/po/vim.pot b/src/po/vim.pot
index 131e52457..4996b205f 100644
--- a/src/po/vim.pot
+++ b/src/po/vim.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim
"
"Report-Msgid-Bugs-To:
vim...@vim.org
"
-"POT-Creation-Date: 2026-06-24 17:52+0000
"
+"POT-Creation-Date: 2026-06-27 08:41+0000
"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
"Language-Team: LANGUAGE <
L...@li.org>
"
@@ -1978,6 +1978,9 @@ msgstr ""
msgid "???BLOCK MISSING"
msgstr ""
+msgid "??? BLOCK PAGE COUNT MISMATCH"
+msgstr ""
+
msgid "??? from here until ???END lines may be messed up"
msgstr ""
diff --git a/src/testdir/samples/recover-mismatch-pc.swp b/src/testdir/samples/recover-mismatch-pc.swp
new file mode 100644
index 0000000000000000000000000000000000000000..dd5a1fa083e9817bd8b926c28b65a02b120be911
GIT binary patch
literal 28672
zcmeI%u?~VT5CG6h;;exm0NoAf!Y?>D$}aH(9Nhg~Kf{(d!cLlZLx;OGxqD5g8{6S}
ztNT+MV(il)2oT6s;BxM!uYNo<byePT9n)L{2oNAZfB*pk1PIIsykAB5#C$PROR3F2
zzf|8Y+><6ifB*pk1PBlyK!5-N0tD70@OXys-Nk;=?%)1@JrAQ30t5&UAV7cs0RjXF
z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk
c1PBlyK!5-N0t5&UAV7cs0RjXF5Xeg42S98GRsaA1
literal 0
HcmV?d00001
diff --git a/src/testdir/test_recover.vim b/src/testdir/test_recover.vim
index bb61e1995..0c8e28196 100644
--- a/src/testdir/test_recover.vim
+++ b/src/testdir/test_recover.vim
@@ -514,8 +514,8 @@ func Test_recover_corrupted_swap_file1()
call assert_match('???ILLEGAL BLOCK NUMBER', content)
call delete(target)
bw!
-"
-" " Test 2: Segfault
+
+ " Test 2: Segfault
new
let sample = 'samples/recover-crash2.swp'
let target = 'Xpoc2.swp'
@@ -531,6 +531,19 @@ func Test_recover_corrupted_swap_file1()
call assert_match('???LINES MISSING', content)
call delete(target)
bw!
+
+ " Test 3: wrong page_count header
+ new
+ let sample = 'samples/recover-mismatch-pc.swp'
+ let target = 'Xmismatch-pc.swp'
+ call writefile(readblob(sample), target, 'bD')
+ try
+ sil noa recover! Xmismatch-pc.swp
+ catch
+ endtry
+ " Verifies no crash occurs. The OOB write is only reliably triggered
+ " interactively due to memory pressure evicting blocks in the test runner.
+ bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index fa30c5e70..7cfc8de77 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 738,
/**/
737,
/**/