patch 9.2.0524: spell: buffer overflow with many affix or compound flags
Commit:
https://github.com/vim/vim/commit/9a920e82546dace49a0a88dced80d29a39efbdb0
Author: Yasuhiro Matsumoto <[email protected]>
Date: Sat May 23 19:56:10 2026 +0000
patch 9.2.0524: spell: buffer overflow with many affix or compound flags
Problem: spell: a word in a .dic file with many postponed prefix or
compound flags overflows the fixed-size store_afflist[MAXWLEN]
buffer in get_pfxlist() and get_compflags().
Solution: Add bounds checks (Yasuhiro Matsumoto).
closes: #20286
Signed-off-by: Yasuhiro Matsumoto <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt
index 27f97548b..774f0777c 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -1,4 +1,4 @@
-*spell.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*spell.txt* For Vim version 9.2. Last change: 2026 May 23
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -562,6 +562,11 @@ then Vim will try to guess.
avoid running out of memory compression will be done
now and then. This can be tuned with the 'mkspellmem'
option.
+ *E1578*
+ There is a limit on how many postponed prefix and
+ compound flags can be stored for one word. Reduce the
+ number of affix/compound flags on a word in the .dic
+ file that exceeds it.
After the spell file was written and it was being used
in a buffer it will be reloaded automatically.
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 9c0298aed..7cd0074a6 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -4776,6 +4776,7 @@ E1574 channel.txt /*E1574*
E1575 builtin.txt /*E1575*
E1576 tagsrch.txt /*E1576*
E1577 options.txt /*E1577*
+E1578 spell.txt /*E1578*
E158 sign.txt /*E158*
E159 sign.txt /*E159*
E16 cmdline.txt /*E16*
diff --git a/src/errors.h b/src/errors.h
index 0013a43ee..9dbd7ac4d 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3812,3 +3812,7 @@ EXTERN char e_tag_file_entry_must_not_be_url[]
INIT(= N_("E1576: Tag file entry must not be a URL"));
EXTERN char e_invalid_format_string_single_percent_s[]
INIT(= N_("E1577: Invalid format string, only one \"%s\" is allowed"));
+#ifdef FEAT_SPELL
+EXTERN char e_too_many_postponed_prefixes_spell[]
+ INIT(= N_("E1578: Too many postponed prefixes and/or compound flags"));
+#endif
diff --git a/src/po/vim.pot b/src/po/vim.pot
index 946ce0883..00c192a62 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: [email protected]
"
-"POT-Creation-Date: 2026-05-21 19:38+0000
"
+"POT-Creation-Date: 2026-05-23 19:49+0000
"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>
"
"Language-Team: LANGUAGE <[email protected]>
"
@@ -8880,6 +8880,9 @@ msgstr ""
msgid "E1577: Invalid format string, only one \"%s\" is allowed"
msgstr ""
+msgid "E1578: Too many postponed prefixes and/or compound flags"
+msgstr ""
+
#. type of cmdline window or 0
#. result of cmdline window or 0
#. buffer of cmdline window or NULL
diff --git a/src/spellfile.c b/src/spellfile.c
index 9ee7031e5..f1841ddf3 100644
--- a/src/spellfile.c
+++ b/src/spellfile.c
@@ -2013,8 +2013,8 @@ static int str_equal(char_u *s1, char_u *s2);
static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from,
char_u *to);
static int sal_to_bool(char_u *s);
static int get_affix_flags(afffile_T *affile, char_u *afflist);
-static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u
*store_afflist);
-static void get_compflags(afffile_T *affile, char_u *afflist, char_u
*store_afflist);
+static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u
*store_afflist, int *cntp);
+static int get_compflags(afffile_T *affile, char_u *afflist, char_u
*store_afflist, int *cntp);
static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist,
afffile_T *affile, hashtab_T *ht, hashtab_T *xht, int condit, int flags, char_u
*pfxlist, int pfxlen);
static void *getroom(spellinfo_T *spin, size_t len, int align);
static char_u *getroom_save(spellinfo_T *spin, char_u *s);
@@ -3357,6 +3357,26 @@ check_renumber(spellinfo_T *spin)
}
}
+/*
+ * Append one affix or compound ID to "store_afflist".
+ * Returns FAIL when this would overrun the fixed-size buffer.
+ */
+ static int
+store_afflist_add(
+ char_u *store_afflist,
+ int *cntp,
+ int id)
+{
+ if (*cntp >= MAXWLEN - 1)
+ {
+ emsg(_(e_too_many_postponed_prefixes_spell));
+ return FAIL;
+ }
+ store_afflist[(*cntp)++] = id;
+ store_afflist[*cntp] = NUL;
+ return OK;
+}
+
/*
* Return TRUE if flag "flag" appears in affix list "afflist".
*/
@@ -3516,6 +3536,7 @@ spell_read_dic(spellinfo_T *spin, char_u *fname,
afffile_T *affile)
char_u *afflist;
char_u store_afflist[MAXWLEN];
int pfxlen;
+ int totlen;
int need_affix;
char_u *dw;
char_u *pc;
@@ -3665,6 +3686,7 @@ spell_read_dic(spellinfo_T *spin, char_u *fname,
afffile_T *affile)
flags = 0;
store_afflist[0] = NUL;
pfxlen = 0;
+ totlen = 0;
need_affix = FALSE;
if (afflist != NULL)
{
@@ -3676,13 +3698,28 @@ spell_read_dic(spellinfo_T *spin, char_u *fname,
afffile_T *affile)
need_affix = TRUE;
if (affile->af_pfxpostpone)
+ {
// Need to store the list of prefix IDs with the word.
- pfxlen = get_pfxlist(affile, afflist, store_afflist);
+ if (get_pfxlist(affile, afflist, store_afflist, &totlen)
+ == FAIL)
+ {
+ retval = FAIL;
+ break;
+ }
+ pfxlen = totlen;
+ }
if (spin->si_compflags != NULL)
+ {
// Need to store the list of compound flags with the word.
// Concatenate them to the list of prefix IDs.
- get_compflags(affile, afflist, store_afflist + pfxlen);
+ if (get_compflags(affile, afflist, store_afflist, &totlen)
+ == FAIL)
+ {
+ retval = FAIL;
+ break;
+ }
+ }
}
// Add the word to the word tree(s).
@@ -3753,18 +3790,18 @@ get_affix_flags(afffile_T *affile, char_u *afflist)
/*
* Get the list of prefix IDs from the affix list "afflist".
* Used for PFXPOSTPONE.
- * Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL
- * and return the number of affixes.
+ * Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL.
+ * Returns FAIL when the fixed-size buffer would overflow.
*/
static int
get_pfxlist(
afffile_T *affile,
char_u *afflist,
- char_u *store_afflist)
+ char_u *store_afflist,
+ int *cntp)
{
char_u *p;
char_u *prevp;
- int cnt = 0;
int id;
char_u key[AH_KEY_LEN];
hashitem_T *hi;
@@ -3781,32 +3818,32 @@ get_pfxlist(
if (!HASHITEM_EMPTY(hi))
{
id = HI2AH(hi)->ah_newID;
- if (id != 0)
- store_afflist[cnt++] = id;
+ if (id != 0 && store_afflist_add(store_afflist, cntp, id) ==
FAIL)
+ return FAIL;
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',')
++p;
}
- store_afflist[cnt] = NUL;
- return cnt;
+ return OK;
}
/*
* Get the list of compound IDs from the affix list "afflist" that are used
* for compound words.
* Puts the flags in "store_afflist[]".
+ * Returns FAIL when the fixed-size buffer would overflow.
*/
- static void
+ static int
get_compflags(
afffile_T *affile,
char_u *afflist,
- char_u *store_afflist)
+ char_u *store_afflist,
+ int *cntp)
{
char_u *p;
char_u *prevp;
- int cnt = 0;
char_u key[AH_KEY_LEN];
hashitem_T *hi;
@@ -3818,14 +3855,16 @@ get_compflags(
// A flag is a compound flag if it appears in "af_comp".
vim_strncpy(key, prevp, p - prevp);
hi = hash_find(&affile->af_comp, key);
- if (!HASHITEM_EMPTY(hi))
- store_afflist[cnt++] = HI2CI(hi)->ci_newID;
+ if (!HASHITEM_EMPTY(hi)
+ && store_afflist_add(store_afflist, cntp,
+ HI2CI(hi)->ci_newID) == FAIL)
+ return FAIL;
}
if (affile->af_flagtype == AFT_NUM && *p == ',')
++p;
}
- store_afflist[cnt] = NUL;
+ return OK;
}
/*
@@ -3983,10 +4022,20 @@ store_aff_word(
if (affile->af_pfxpostpone
|| spin->si_compflags != NULL)
{
+ int listlen = 0;
+
if (affile->af_pfxpostpone)
+ {
// Get prefix IDS from the affix list.
- use_pfxlen = get_pfxlist(affile,
- ae->ae_flags, store_afflist);
+ if (get_pfxlist(affile, ae->ae_flags,
+ store_afflist, &listlen)
+ == FAIL)
+ {
+ retval = FAIL;
+ break;
+ }
+ use_pfxlen = listlen;
+ }
else
use_pfxlen = 0;
use_pfxlist = store_afflist;
@@ -3998,14 +4047,30 @@ store_aff_word(
for (j = 0; j < use_pfxlen; ++j)
if (pfxlist[i] == use_pfxlist[j])
break;
- if (j == use_pfxlen)
- use_pfxlist[use_pfxlen++] = pfxlist[i];
+ if (j == use_pfxlen
+ && store_afflist_add(use_pfxlist,
+ &listlen, pfxlist[i])
+ == FAIL)
+ {
+ retval = FAIL;
+ break;
+ }
+ use_pfxlen = listlen;
}
+ if (retval == FAIL)
+ break;
if (spin->si_compflags != NULL)
// Get compound IDS from the affix list.
- get_compflags(affile, ae->ae_flags,
- use_pfxlist + use_pfxlen);
+ if (get_compflags(affile, ae->ae_flags,
+ use_pfxlist, &listlen)
+ == FAIL)
+ {
+ retval = FAIL;
+ break;
+ }
+ if (retval == FAIL)
+ break;
// Combine the list of compound flags.
// Concatenate them to the prefix IDs list.
@@ -4016,12 +4081,17 @@ store_aff_word(
use_pfxlist[j] != NUL; ++j)
if (pfxlist[i] == use_pfxlist[j])
break;
- if (use_pfxlist[j] == NUL)
+ if (use_pfxlist[j] == NUL
+ && store_afflist_add(use_pfxlist,
+ &listlen, pfxlist[i])
+ == FAIL)
{
- use_pfxlist[j++] = pfxlist[i];
- use_pfxlist[j] = NUL;
+ retval = FAIL;
+ break;
}
}
+ if (retval == FAIL)
+ break;
}
}
@@ -6236,6 +6306,7 @@ spell_add_word(
char_u line[MAXWLEN * 2];
long fpos, fpos_next = 0;
int i;
+ size_t linelen;
char_u *spf;
if (!valid_spell_word(word, word + len))
@@ -6312,7 +6383,9 @@ spell_add_word(
fpos_next = ftell(fd);
if (fpos_next < 0)
break; // should never happen
- if (STRNCMP(word, line, len) == 0
+ linelen = STRLEN(line);
+ if (linelen >= (size_t)len
+ && STRNCMP(word, line, len) == 0
&& (line[len] == '/' || line[len] < ' '))
{
// Found duplicate word. Remove it by writing a '#' at
diff --git a/src/testdir/test_spellfile.vim b/src/testdir/test_spellfile.vim
index cf75eb4ef..07156818d 100644
--- a/src/testdir/test_spellfile.vim
+++ b/src/testdir/test_spellfile.vim
@@ -1214,5 +1214,37 @@ func Test_mkspell_no_buffer_overflow()
defer delete('Xbof2.spl')
endfunc
+func Test_mkspell_no_affixlist_overflow()
+ let aff_lines = [
+ \ 'SET ISO8859-1',
+ \ 'PFXPOSTPONE',
+ \ 'PFX A Y 1',
+ \ 'PFX A 0 pre .',
+ \ ]
+ call writefile(aff_lines, 'Xaffbof.aff', 'D')
+ call writefile(['1', 'word/' .. repeat('A', 300)], 'Xaffbof.dic', 'D')
+
+ call assert_fails('mkspell! Xaffbof.spl Xaffbof',
+ \ 'Too many postponed prefixes and/or compound flags')
+ call assert_false(filereadable('Xaffbof.spl'))
+endfunc
+
+func Test_mkspell_no_compflag_overflow()
+ " Overflow the compound-flag path in get_compflags(): a word whose
+ " affix list repeats a compound flag many times accumulates one ID per
+ " occurrence, overrunning store_afflist[MAXWLEN].
+ let aff_lines = [
+ \ 'SET ISO8859-1',
+ \ 'COMPOUNDFLAG c',
+ \ ]
+ call writefile(aff_lines, 'Xcompbof.aff', 'D')
+
+ " Repeat the compound flag 'c' far past MAXWLEN.
+ call writefile(['1', 'word/' .. repeat('c', 300)], 'Xcompbof.dic', 'D')
+
+ call assert_fails('mkspell! Xcompbof.spl Xcompbof',
+ \ 'Too many postponed prefixes and/or compound flags')
+ call assert_false(filereadable('Xcompbof.spl'))
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index af37e33da..f3cfeb60a 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 */
+/**/
+ 524,
/**/
523,
/**/
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/vim_dev/E1wQskS-002pYH-Ih%40256bit.org.