vim-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: vim/vim#20286

9a920e8254

Co-authored-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
This commit is contained in:
zeertzjq
2026-05-24 08:04:49 +08:00
parent 61eddb3fe7
commit 1bf38c302d
4 changed files with 112 additions and 29 deletions

View File

@@ -542,6 +542,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.

View File

@@ -224,6 +224,7 @@ EXTERN const char e_failed_to_find_all_diff_anchors[] INIT( = N_("E1550: Failed
EXTERN const char e_diff_anchors_with_hidden_windows[] INIT( = N_("E1562: Diff anchors cannot be used with hidden diff windows"));
EXTERN const char e_leadtab_requires_tab[] INIT( = N_("E1572: 'listchars' field \"leadtab\" requires \"tab\" to be specified"));
EXTERN const char e_invalid_format_string_single_percent_s[] INIT( = N_("E1577: Invalid format string, only one \"%s\" is allowed"));
EXTERN const char e_too_many_postponed_prefixes_spell[] INIT(= N_("E1578: Too many postponed prefixes and/or compound flags"));
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
EXTERN const char e_cannot_read_from_str_2[] INIT(= N_("E282: Cannot read from \"%s\""));

View File

@@ -2929,6 +2929,19 @@ static void 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 *store_afflist, int *cntp, int id)
{
if (*cntp >= MAXWLEN - 1) {
emsg(_(e_too_many_postponed_prefixes_spell));
return FAIL;
}
store_afflist[(*cntp)++] = (char)(uint8_t)id;
store_afflist[*cntp] = NUL;
return OK;
}
// Returns true if flag "flag" appears in affix list "afflist".
static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
{
@@ -3180,6 +3193,7 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
int flags = 0;
store_afflist[0] = NUL;
int pfxlen = 0;
int totlen = 0;
bool need_affix = false;
if (afflist != NULL) {
// Extract flags from the affix list.
@@ -3193,13 +3207,20 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
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;
}
}
}
@@ -3279,13 +3300,12 @@ static int get_affix_flags(afffile_T *affile, char *afflist)
return flags;
}
// 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.
static int get_pfxlist(afffile_T *affile, char *afflist, char *store_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.
/// Returns FAIL when the fixed-size buffer would overflow.
static int get_pfxlist(afffile_T *affile, char *afflist, char *store_afflist, int *cntp)
{
int cnt = 0;
char key[AH_KEY_LEN];
for (char *p = afflist; *p != NUL;) {
@@ -3297,8 +3317,8 @@ static int get_pfxlist(afffile_T *affile, char *afflist, char *store_afflist)
hashitem_T *hi = hash_find(&affile->af_pref, key);
if (!HASHITEM_EMPTY(hi)) {
int id = HI2AH(hi)->ah_newID;
if (id != 0) {
store_afflist[cnt++] = (char)(uint8_t)id;
if (id != 0 && store_afflist_add(store_afflist, cntp, id) == FAIL) {
return FAIL;
}
}
}
@@ -3307,16 +3327,15 @@ static int get_pfxlist(afffile_T *affile, char *afflist, char *store_afflist)
}
}
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[]".
static void get_compflags(afffile_T *affile, char *afflist, char *store_afflist)
/// 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 int get_compflags(afffile_T *affile, char *afflist, char *store_afflist, int *cntp)
{
int cnt = 0;
char key[AH_KEY_LEN];
for (char *p = afflist; *p != NUL;) {
@@ -3325,8 +3344,9 @@ static void get_compflags(afffile_T *affile, char *afflist, char *store_afflist)
// A flag is a compound flag if it appears in "af_comp".
xmemcpyz(key, prevp, (size_t)(p - prevp));
hashitem_T *hi = hash_find(&affile->af_comp, key);
if (!HASHITEM_EMPTY(hi)) {
store_afflist[cnt++] = (char)(uint8_t)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 == ',') {
@@ -3334,7 +3354,7 @@ static void get_compflags(afffile_T *affile, char *afflist, char *store_afflist)
}
}
store_afflist[cnt] = NUL;
return OK;
}
/// Apply affixes to a word and store the resulting words.
@@ -3466,9 +3486,16 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
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;
}
@@ -3482,18 +3509,30 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
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;
}
} else {
use_pfxlist[use_pfxlen] = NUL;
}
if (retval == FAIL) {
break;
}
// Combine the list of compound flags.
// Concatenate them to the prefix IDs list.
@@ -3504,11 +3543,15 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
break;
}
}
if (use_pfxlist[j] == NUL) {
use_pfxlist[j++] = pfxlist[i];
use_pfxlist[j] = NUL;
if (use_pfxlist[j] == NUL
&& store_afflist_add(use_pfxlist, &listlen, pfxlist[i]) == FAIL) {
retval = FAIL;
break;
}
}
if (retval == FAIL) {
break;
}
}
}
@@ -5497,7 +5540,9 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
if (fpos_next < 0) {
break; // should never happen
}
if (strncmp(word, line, (size_t)len) == 0
size_t linelen = strlen(line);
if (linelen >= (size_t)len
&& strncmp(word, line, (size_t)len) == 0
&& (line[len] == '/' || (uint8_t)line[len] < ' ')) {
// Found duplicate word. Remove it by writing a '#' at
// the start of the line. Mixing reading and writing

View File

@@ -1219,5 +1219,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