From: Christian Brabandt Date: Wed, 6 May 2026 18:50:00 +0000 (+0200) Subject: patch 9.2.0450: [security]: heap buffer overflow in spellfile.c read_compound() X-Git-Tag: v9.2.0450^0 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=92993329178cb1f72d700fff45ca86e1c2d369f8;p=thirdparty%2Fvim.git 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) Signed-off-by: Christian Brabandt --- diff --git a/src/spellfile.c b/src/spellfile.c index a9a347a89a..5102dad5b6 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, "^\(\)$" 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 f46a25d99e..8f3ef4907d 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 3cbce87d87..ff0960c227 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, /**/