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.

Raspunde prin e-mail lui