]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Prevent some buffer overruns in spell.c's parsing of affix files.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 22 Apr 2026 16:02:15 +0000 (12:02 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 22 Apr 2026 16:02:15 +0000 (12:02 -0400)
parse_affentry() and addCompoundAffixFlagValue() each collect fields
from an affix file into working buffers of size BUFSIZ.  They failed
to defend against overlength fields, so that a malicious affix file
could cause a stack smash.  BUFSIZ (typically 8K) is certainly way
longer than any reasonable affix field, but let's fix this while
we're closing holes in this area.

I chose to do this by silently truncating the input before it can
overrun the buffer, using logic comparable to the existing logic in
get_nextfield().  Certainly there's at least as good an argument for
raising an error, but for now let's follow the existing precedent.

Reported-by: Igor Stepansky <igor.stepansky@orca.security>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Discussion: https://postgr.es/m/864123.1776810909@sss.pgh.pa.us
Backpatch-through: 14

src/backend/tsearch/spell.c

index abaa18c21c4e609aacb45b80504c76a61cb9161f..15dccb47bf5f20aa6fbddc80c2f150b13786c2a3 100644 (file)
@@ -909,14 +909,20 @@ parse_ooaffentry(char *str, char *type, char *flag, char *find,
  *
  * An .affix file entry has the following format:
  * <mask>  >  [-<find>,]<replace>
+ *
+ * Output buffers mask, find, repl must be of length BUFSIZ;
+ * we truncate the input to fit.
  */
 static bool
-parse_affentry(char *str, char *mask, char *find, char *repl)
+parse_affentry(const char *str, char *mask, char *find, char *repl)
 {
        int                     state = PAE_WAIT_MASK;
        char       *pmask = mask,
                           *pfind = find,
                           *prepl = repl;
+       char       *emask = mask + BUFSIZ;
+       char       *efind = find + BUFSIZ;
+       char       *erepl = repl + BUFSIZ;
 
        *mask = *find = *repl = '\0';
 
@@ -930,7 +936,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                                return false;
                        else if (!isspace((unsigned char) *str))
                        {
-                               pmask += ts_copychar_with_len(pmask, str, clen);
+                               if (pmask < emask - clen)
+                                       pmask += ts_copychar_with_len(pmask, str, clen);
                                state = PAE_INMASK;
                        }
                }
@@ -943,7 +950,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                        }
                        else if (!isspace((unsigned char) *str))
                        {
-                               pmask += ts_copychar_with_len(pmask, str, clen);
+                               if (pmask < emask - clen)
+                                       pmask += ts_copychar_with_len(pmask, str, clen);
                        }
                }
                else if (state == PAE_WAIT_FIND)
@@ -954,7 +962,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                        }
                        else if (t_isalpha_cstr(str) || t_iseq(str, '\'') /* english 's */ )
                        {
-                               prepl += ts_copychar_with_len(prepl, str, clen);
+                               if (prepl < erepl - clen)
+                                       prepl += ts_copychar_with_len(prepl, str, clen);
                                state = PAE_INREPL;
                        }
                        else if (!isspace((unsigned char) *str))
@@ -971,7 +980,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                        }
                        else if (t_isalpha_cstr(str))
                        {
-                               pfind += ts_copychar_with_len(pfind, str, clen);
+                               if (pfind < efind - clen)
+                                       pfind += ts_copychar_with_len(pfind, str, clen);
                        }
                        else if (!isspace((unsigned char) *str))
                                ereport(ERROR,
@@ -986,7 +996,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                        }
                        else if (t_isalpha_cstr(str))
                        {
-                               prepl += ts_copychar_with_len(prepl, str, clen);
+                               if (prepl < erepl - clen)
+                                       prepl += ts_copychar_with_len(prepl, str, clen);
                                state = PAE_INREPL;
                        }
                        else if (!isspace((unsigned char) *str))
@@ -1003,7 +1014,8 @@ parse_affentry(char *str, char *mask, char *find, char *repl)
                        }
                        else if (t_isalpha_cstr(str))
                        {
-                               prepl += ts_copychar_with_len(prepl, str, clen);
+                               if (prepl < erepl - clen)
+                                       prepl += ts_copychar_with_len(prepl, str, clen);
                        }
                        else if (!isspace((unsigned char) *str))
                                ereport(ERROR,
@@ -1061,7 +1073,7 @@ setCompoundAffixFlagValue(IspellDict *Conf, CompoundAffixFlag *entry,
  * val: affix parameter.
  */
 static void
-addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val)
+addCompoundAffixFlagValue(IspellDict *Conf, const char *s, uint32 val)
 {
        CompoundAffixFlag *newValue;
        char            sbuf[BUFSIZ];
@@ -1079,9 +1091,11 @@ addCompoundAffixFlagValue(IspellDict *Conf, char *s, uint32 val)
        sflag = sbuf;
        while (*s && !isspace((unsigned char) *s) && *s != '\n')
        {
-               int                     clen = ts_copychar_cstr(sflag, s);
+               int                     clen = pg_mblen_cstr(s);
 
-               sflag += clen;
+               /* Truncate the input to fit in BUFSIZ */
+               if (sflag < sbuf + BUFSIZ - clen)
+                       sflag += ts_copychar_with_len(sflag, s, clen);
                s += clen;
        }
        *sflag = '\0';