From 0976ce255bebb5d44d07bbda51233d7002588ae1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 8 May 2026 07:03:20 +0800 Subject: [PATCH] vim-patch:9.2.0450: [security]: heap buffer overflow in spellfile.c read_compound() (#39660) 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 https://github.com/vim/vim/commit/92993329178cb1f72d700fff45ca86e1c2d369f8 Co-authored-by: Christian Brabandt Co-authored-by: Claude Opus 4.7 (1M context) --- src/nvim/spellfile.c | 19 +++++++++++++------ test/old/testdir/test_spellfile.vim | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 021a891eee..a2a06e451c 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -331,6 +331,9 @@ enum { CF_UPPER = 0x02, }; +// Max allowed length for COMPOUND section +#define COMPOUND_MAX_LEN 100000 + static const char *e_spell_trunc = N_("E758: Truncated spell file"); static const char e_error_while_reading_sug_file_str[] = N_("E782: Error while reading .sug file: %s"); @@ -1431,24 +1434,28 @@ static int 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; - c += todo * 2; - char *pat = xmalloc((size_t)c); + if ((size_t)todo > COMPOUND_MAX_LEN) { + return SP_FORMERROR; + } + size_t patsize = (size_t)todo * 2 + 7; + patsize += (size_t)todo * 2; + size_t flagsize = (size_t)todo + 1; + char *pat = xmalloc(patsize); // We also need a list of all flags that can appear at the start and one // for all flags. - uint8_t *cp = xmalloc((size_t)todo + 1); + uint8_t *cp = xmalloc(flagsize); slang->sl_compstartflags = cp; *cp = NUL; - uint8_t *ap = xmalloc((size_t)todo + 1); + uint8_t *ap = xmalloc(flagsize); slang->sl_compallflags = ap; *ap = NUL; // 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. - uint8_t *crp = xmalloc((size_t)todo + 1); + uint8_t *crp = xmalloc(flagsize); slang->sl_comprules = crp; char *pp = pat; diff --git a/test/old/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim index 02878877b1..510953f4c9 100644 --- a/test/old/testdir/test_spellfile.vim +++ b/test/old/testdir/test_spellfile.vim @@ -337,6 +337,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, '')