Commit: patch 9.2.0450: [security]: heap buffer overflow in spellfile.c read_compound()

1 view
Skip to first unread message

Christian Brabandt

unread,
3:15 PM (5 hours ago) 3:15 PM
to vim...@googlegroups.com
patch 9.2.0450: [security]: heap buffer overflow in spellfile.c read_compound()

Commit: https://github.com/vim/vim/commit/92993329178cb1f72d700fff45ca86e1c2d369f8
Author: Christian Brabandt <c...@256bit.org>
Date: Wed May 6 20:50:00 2026 +0200

patch 9.2.0450: [security]: heap buffer overflow in spellfile.c read_compound()

Problem: read_compound() in spellfile.c computes the size of the regex
pattern buffer using signed-int arithmetic on the attacker
controlled SN_COMPOUND sectionlen. With sectionlen=0x40000008
and UTF-8 encoding active the multiplication wraps to 27 while
the per-byte loop writes up to ~1B bytes, overflowing the heap.
Reachable when loading a crafted .spl file (e.g. via 'set spell'
after a modeline sets 'spelllang'). The cp/ap/crp allocations
have the same int + 1 overflow class (Daniel Cervera)
Solution: Use type size_t as buffer size and reject values larger than
COMPOUND_MAX_LEN (100000). Apply the same size_t treatment to
the cp/ap/crp allocations.

Github Advisory:
https://github.com/vim/vim/security/advisories/GHSA-q4jv-r9gj-6cwv

Co-Authored-By: Claude Opus 4.7 (1M context) <nor...@anthropic.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/spellfile.c b/src/spellfile.c
index a9a347a89..5102dad5b 100644
--- a/src/spellfile.c
+++ b/src/spellfile.c
@@ -290,6 +290,9 @@
#define CF_WORD 0x01
#define CF_UPPER 0x02

+// Max allowed length for COMPOUND section
+#define COMPOUND_MAX_LEN 100000
+
/*
* Loop through all the siblings of a node (including the node)
*/
@@ -1219,6 +1222,8 @@ read_compound(FILE *fd, slang_T *slang, int len)
char_u *crp;
int cnt;
garray_T *gap;
+ size_t patsize;
+ size_t flagsize;

if (todo < 2)
return SP_FORMERROR; // need at least two bytes
@@ -1275,16 +1280,19 @@ read_compound(FILE *fd, slang_T *slang, int len)
// "a[bc]/a*b+" -> "^\(a[bc]\|a*b\+\)$".
// Inserting backslashes may double the length, "^\(\)$<Nul>" is 7 bytes.
// Conversion to utf-8 may double the size.
- c = todo * 2 + 7;
+ if ((size_t)todo > COMPOUND_MAX_LEN)
+ return SP_FORMERROR;
+ patsize = (size_t)todo * 2 + 7;
if (enc_utf8)
- c += todo * 2;
- pat = alloc(c);
+ patsize += (size_t)todo * 2;
+ flagsize = (size_t)todo + 1;
+ pat = alloc(patsize);
if (pat == NULL)
return SP_OTHERERROR;

// We also need a list of all flags that can appear at the start and one
// for all flags.
- cp = alloc(todo + 1);
+ cp = alloc(flagsize);
if (cp == NULL)
{
vim_free(pat);
@@ -1293,7 +1301,7 @@ read_compound(FILE *fd, slang_T *slang, int len)
slang->sl_compstartflags = cp;
*cp = NUL;

- ap = alloc(todo + 1);
+ ap = alloc(flagsize);
if (ap == NULL)
{
vim_free(pat);
@@ -1305,7 +1313,7 @@ read_compound(FILE *fd, slang_T *slang, int len)
// And a list of all patterns in their original form, for checking whether
// compounding may work in match_compoundrule(). This is freed when we
// encounter a wildcard, the check doesn't work then.
- crp = alloc(todo + 1);
+ crp = alloc(flagsize);
slang->sl_comprules = crp;

pp = pat;
diff --git a/src/testdir/test_spellfile.vim b/src/testdir/test_spellfile.vim
index f46a25d99..8f3ef4907 100644
--- a/src/testdir/test_spellfile.vim
+++ b/src/testdir/test_spellfile.vim
@@ -334,6 +334,10 @@ func Test_spellfile_format_error()
" SN_COMPOUND: incorrect comppatlen
call Spellfile_Test(0z080000000007040101000000020165, 'E758:')

+ " SN_COMPOUND: oversized sectionlen
+ let v = eval('0z08004000000803010161' .. repeat('61', 50) .. 'FF')
+ call Spellfile_Test(v, 'E759:')
+
" SN_INFO: missing info
call Spellfile_Test(0z0F0000000005040101, '')

diff --git a/src/version.c b/src/version.c
index 3cbce87d8..ff0960c22 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 450,
/**/
449,
/**/
Reply all
Reply to author
Forward
0 new messages