From e6047960e96e2a3bdd5d8b6ca1308203d9ea5a5f Mon Sep 17 00:00:00 2001 From: Andrey Rachitskiy Date: Sun, 2 Aug 2026 12:07:36 +0500 Subject: [PATCH] Fix memory-safety bugs in the ispell/hunspell dictionary loader. Allocate CompoundAffix with room for its terminator, initialize the old-format flag buffer before NIAddAffix(), and reject incomplete or NULL Hunspell AF alias slots. Add regression tests for the AF cases. Author: Andrey Rachitskiy Reported-by: Michael Malis Discussion: https://www.postgresql.org/message-id/19595-7dc18b4e212c4757%40postgresql.org --- src/backend/tsearch/Makefile | 4 +++- .../tsearch/dicts/hunspell_test_afshort.affix | 5 +++++ .../tsearch/dicts/hunspell_test_afshort.dict | 1 + .../tsearch/dicts/hunspell_test_aftrunc.affix | 5 +++++ .../tsearch/dicts/hunspell_test_aftrunc.dict | 1 + src/backend/tsearch/spell.c | 18 +++++++++++++++++- src/test/regress/expected/tsdicts.out | 16 ++++++++++++++++ src/test/regress/sql/tsdicts.sql | 16 ++++++++++++++++ 8 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/backend/tsearch/dicts/hunspell_test_afshort.affix create mode 100644 src/backend/tsearch/dicts/hunspell_test_afshort.dict create mode 100644 src/backend/tsearch/dicts/hunspell_test_aftrunc.affix create mode 100644 src/backend/tsearch/dicts/hunspell_test_aftrunc.dict diff --git a/src/backend/tsearch/Makefile b/src/backend/tsearch/Makefile index 4a436150109..9430d906122 100644 --- a/src/backend/tsearch/Makefile +++ b/src/backend/tsearch/Makefile @@ -18,7 +18,9 @@ DICTFILES=synonym_sample.syn thesaurus_sample.ths \ hunspell_sample.affix \ ispell_sample.affix ispell_sample.dict \ hunspell_sample_long.affix hunspell_sample_long.dict \ - hunspell_sample_num.affix hunspell_sample_num.dict + hunspell_sample_num.affix hunspell_sample_num.dict \ + hunspell_test_afshort.affix hunspell_test_afshort.dict \ + hunspell_test_aftrunc.affix hunspell_test_aftrunc.dict # Local paths to dictionaries files DICTFILES_PATH=$(addprefix dicts/,$(DICTFILES)) diff --git a/src/backend/tsearch/dicts/hunspell_test_afshort.affix b/src/backend/tsearch/dicts/hunspell_test_afshort.affix new file mode 100644 index 00000000000..df0656b7b50 --- /dev/null +++ b/src/backend/tsearch/dicts/hunspell_test_afshort.affix @@ -0,0 +1,5 @@ +COMPOUNDFLAG Z +AF 2 +AF x +SFX A Y 1 +SFX A 0 0/2 . diff --git a/src/backend/tsearch/dicts/hunspell_test_afshort.dict b/src/backend/tsearch/dicts/hunspell_test_afshort.dict new file mode 100644 index 00000000000..8c5c4c3f000 --- /dev/null +++ b/src/backend/tsearch/dicts/hunspell_test_afshort.dict @@ -0,0 +1 @@ +foo/2 diff --git a/src/backend/tsearch/dicts/hunspell_test_aftrunc.affix b/src/backend/tsearch/dicts/hunspell_test_aftrunc.affix new file mode 100644 index 00000000000..f1b6e5b9513 --- /dev/null +++ b/src/backend/tsearch/dicts/hunspell_test_aftrunc.affix @@ -0,0 +1,5 @@ +AF 2 +AF x + +SFX A Y 1 +SFX A 0 s . diff --git a/src/backend/tsearch/dicts/hunspell_test_aftrunc.dict b/src/backend/tsearch/dicts/hunspell_test_aftrunc.dict new file mode 100644 index 00000000000..257cc5642cb --- /dev/null +++ b/src/backend/tsearch/dicts/hunspell_test_aftrunc.dict @@ -0,0 +1 @@ +foo diff --git a/src/backend/tsearch/spell.c b/src/backend/tsearch/spell.c index 3ded3cf7d5f..7323a5e15f2 100644 --- a/src/backend/tsearch/spell.c +++ b/src/backend/tsearch/spell.c @@ -1182,12 +1182,18 @@ getAffixFlagSet(IspellDict *Conf, char *s) errmsg("invalid affix alias \"%s\"", s))); if (curaffix > 0 && curaffix < Conf->nAffixData) + { + if (Conf->AffixData[curaffix] == NULL) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid affix alias \"%s\"", s))); /* * Do not subtract 1 from curaffix because empty string was added * in NIImportOOAffixes */ return Conf->AffixData[curaffix]; + } else if (curaffix > Conf->nAffixData) ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -1422,6 +1428,13 @@ nextline: tsearch_readline_end(&trst); if (ptype) pfree(ptype); + + /* Reject incomplete AF alias table. */ + if (Conf->useFlagAliases && curaffix != naffix) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("too few flag vector aliases (expected %d)", + naffix - 1))); } /* @@ -1449,6 +1462,8 @@ NIImportAffixes(IspellDict *Conf, const char *filename) bool oldformat = false; char *recoded = NULL; + flag[0] = '\0'; /* no flag seen yet */ + if (!tsearch_readline_begin(&trst, filename)) ereport(ERROR, (errcode(ERRCODE_CONFIG_FILE_ERROR), @@ -1997,7 +2012,8 @@ NISortAffixes(IspellDict *Conf) /* Store compound affixes in the Conf->CompoundAffix array */ if (Conf->naffixes > 1) qsort(Conf->Affix, Conf->naffixes, sizeof(AFFIX), cmpaffix); - Conf->CompoundAffix = ptr = palloc_array(CMPDAffix, Conf->naffixes); + /* +1 for terminator */ + Conf->CompoundAffix = ptr = palloc_array(CMPDAffix, Conf->naffixes + 1); ptr->affix = NULL; for (int i = 0; i < Conf->naffixes; i++) diff --git a/src/test/regress/expected/tsdicts.out b/src/test/regress/expected/tsdicts.out index 0bbf2ff4ca2..fc56de6cc50 100644 --- a/src/test/regress/expected/tsdicts.out +++ b/src/test/regress/expected/tsdicts.out @@ -447,6 +447,22 @@ CREATE TEXT SEARCH DICTIONARY hunspell_err ( AffFile=hunspell_sample_long ); ERROR: invalid affix alias "302,301,202,303" +-- incomplete AF table (alias references an unfilled slot) +\set VERBOSITY terse +CREATE TEXT SEARCH DICTIONARY hunspell_test_afshort ( + Template=ispell, + DictFile=hunspell_test_afshort, + AffFile=hunspell_test_afshort +); +ERROR: invalid affix alias "2" +\set VERBOSITY default +-- incomplete AF table (no reference to the missing slot) +CREATE TEXT SEARCH DICTIONARY hunspell_test_aftrunc ( + Template=ispell, + DictFile=hunspell_test_aftrunc, + AffFile=hunspell_test_aftrunc +); +ERROR: too few flag vector aliases (expected 2) -- Synonym dictionary CREATE TEXT SEARCH DICTIONARY synonym ( Template=synonym, diff --git a/src/test/regress/sql/tsdicts.sql b/src/test/regress/sql/tsdicts.sql index cf08410bb2d..9bb2cd1a848 100644 --- a/src/test/regress/sql/tsdicts.sql +++ b/src/test/regress/sql/tsdicts.sql @@ -138,6 +138,22 @@ CREATE TEXT SEARCH DICTIONARY hunspell_err ( AffFile=hunspell_sample_long ); +-- incomplete AF table (alias references an unfilled slot) +\set VERBOSITY terse +CREATE TEXT SEARCH DICTIONARY hunspell_test_afshort ( + Template=ispell, + DictFile=hunspell_test_afshort, + AffFile=hunspell_test_afshort +); +\set VERBOSITY default + +-- incomplete AF table (no reference to the missing slot) +CREATE TEXT SEARCH DICTIONARY hunspell_test_aftrunc ( + Template=ispell, + DictFile=hunspell_test_aftrunc, + AffFile=hunspell_test_aftrunc +); + -- Synonym dictionary CREATE TEXT SEARCH DICTIONARY synonym ( Template=synonym, -- 2.53.0