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 <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yasuhiro Matsumoto
2026-05-23 19:56:10 +00:00
committed by Christian Brabandt
parent 5e3056ee83
commit 9a920e8254
7 changed files with 150 additions and 30 deletions
+6 -1
View File
@@ -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.
+1
View File
@@ -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*
+4
View File
@@ -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
+4 -1
View File
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Vim\n"
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
"POT-Creation-Date: 2026-05-21 19:38+0000\n"
"POT-Creation-Date: 2026-05-23 19:49+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -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
+101 -28
View File
@@ -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
+32
View File
@@ -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
+2
View File
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
524,
/**/
523,
/**/