]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0524: spell: buffer overflow with many affix or compound flags master v9.2.0524
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Sat, 23 May 2026 19:56:10 +0000 (19:56 +0000)
committerChristian Brabandt <cb@256bit.org>
Sat, 23 May 2026 19:56:10 +0000 (19:56 +0000)
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>
runtime/doc/spell.txt
runtime/doc/tags
src/errors.h
src/po/vim.pot
src/spellfile.c
src/testdir/test_spellfile.vim
src/version.c

index 27f97548b335ba481db75c27d25c89a803b55448..774f0777c3d6fde3c7d5b82d0775900f5492034c 100644 (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.
index 9c0298aedd73717a435683b7db54de19f38834cc..7cd0074a6a9ec3d7d96ad89ce83e0feb4791b853 100644 (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*
index 0013a43ee87f88c34e232c12cfe4f40b370721fa..9dbd7ac4db1f74b5d9e6b50eedcace1e95e34bac 100644 (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
index 946ce08831008f1d981cae6b46fa3c6e7a4a53af..00c192a621e7e3d63fef3469b46f2648611e655c 100644 (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
index 9ee7031e551d56ced24b91036bfb5af2cf099e0a..f1841ddf373be709a4aff2939ed14398779ed643 100644 (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
index cf75eb4ef9aba4a90e2bf8daa5a35c7f5d05759f..07156818d9fe82b0803a5e57d593dc2b44e26836 100644 (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
index af37e33da164d48013edba49a631955d60cdae5e..f3cfeb60a84615a6b3b3cafaea48af662aebd552 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    524,
 /**/
     523,
 /**/