diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index f53fe1de2b..e1dde76aca 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -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. diff --git a/src/nvim/errors.h b/src/nvim/errors.h index 5bc8dace62..1ad98f2aeb 100644 --- a/src/nvim/errors.h +++ b/src/nvim/errors.h @@ -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\"")); diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index eacb07e9ae..af4b6b5413 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -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 diff --git a/test/old/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim index aaea8e5b84..4b6539932d 100644 --- a/test/old/testdir/test_spellfile.vim +++ b/test/old/testdir/test_spellfile.vim @@ -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