Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:aspell User: [email protected] Usertags: pu
Hi, aspell-0.60.8.3 includes fixes for some non-dsa CVE as well as other fixes upstream considers equally important. #1150460 aspell: CVE-2026-75818 CVE-2026-75819 CVE-2026-75820 #1150801 aspell: CVE-2026-103658 [ Reason ] Quoting upstream words in #1152490 -- 8< This release fixed a number of other memory problems beyond ones that a CVE were issued for. It also included a more correct fix for CVE-2026-103658. ... Also even though the commits that mentioned the CVE did in fact fix them, please note the following: 0b52f16f997958a7d973cbda15f106114a2b3749 is a followup fix for CVE-2026-103658. I belive the exact fix might still lead to a crash. The fix for CVE-2026-75819 left open a similar vulnability, 7f9bda27d68b34c2abfbf82ad7bdbab45c1fb903 fixes that. Also, the memory issues I mentioned are likely just as serious as the three CVE with low severity. It is just that no CVE were created. -- >8 Upstream proposes just uploading 0.60.8.3 to stable (there is an intermediate 0.60.8.2 upstream release). I have prepared an upload with the minimal changes borrowed from 0.60.8.3 (nothing from 0.60.8.2), including complete fixes for the CVE as well as fixes for other memory problems added by upstream. Let me know if you prefer me to just adapt 0.60.8.3 to trixie. [ Impact ] Fixed CVE are non-dsa, but is desirable to have them fixed. [ Tests ] Built package has been tested manually. Also, autopkg suite added in 0.60.8.2-2 was cherry-picked and adapted in a separate branch, with tests succeeding. This autopkg suite is not included in this release. [ Risks ] Changes look miminal and problems are not expected. [ Checklist ] [*] *all* changes are documented in the d/changelog [*] I reviewed all changes and I approve them [*] attach debdiff against the package in (old)stable [*] the issue is verified as fixed in unstable [ Changes ] * common/string.hpp: fix integer overflow in String::reserve (CVE-2026-103658). * speller/readonly_ws.cpp: check return status of file reads (CVE-2026-103658). * prezip: correctly detect bad input (CVE-2026-75818). * Limit length of words added to the personal dictionary to 250 (CVE-2026-75820). * speller/readonly_ws.cpp: check that offsets are valid in dictionary header (CVE-2026-75819). * speller/readonly_ws.cpp: check first_word_offset without integer overflow (CVE-2026-75819). * common/convert.hpp fix out-of-bounds read in DecodeNormLookup::decode. * common/convert.hpp: fix stack overflow in create_norm_table on crafted .cmap. * speller/phonet.cpp: check target bounds in phonet '^^' rule handling. * speller/affix.cpp: fix out-of-bounds read in normalize_cond_str. * speller/affix.cpp: fix heap overflow in SfxEntry::add. * speller/suggest.cpp: fix off-by-one error in Working::try_word_c. * speller/affix.cpp: reject over-long strip/affix fields in affix parse_file. * speller/writable.cpp: limit replacement word length to 250. [ Other info ] Attached debdiff as well as individual patches for easier inspection. Built package is available at https://people.debian.org/~agmartin/debian-store/experimental/ Thanks for looking into this. Regards, -- Agustin
diff -Nru aspell-0.60.8.1/debian/changelog aspell-0.60.8.1/debian/changelog --- aspell-0.60.8.1/debian/changelog 2025-02-18 00:45:46.000000000 +0100 +++ aspell-0.60.8.1/debian/changelog 2026-11-05 23:54:43.000000000 +0100 @@ -1,3 +1,43 @@ +aspell (0.60.8.1-4+deb13u1) trixie; urgency=medium + + * Patches from upstream 0.60.8.3 to fix different vulnerabilities + (#1150460, #1150801). + - CVE-2026-103658: + . common/string.hpp: fix integer overflow in String::reserve + (0007_common-string.hpp-fix-integer-overflow-in-String-res...). + . speller/readonly_ws.cpp: check return status of file reads + (0008-speller-readonly_ws.cpp-check-return-status-of-file...). + - CVE-2026-75818: + . prezip: correctly detect bad input (0010-prezip-correctly...). + - CVE-2026-75820: + . Limit length of words added to the personal dictionary to 250 + characters. (0011-Limit-length-of-words-added-to-the-personal...). + - CVE-2026-75819: + . speller/readonly_ws.cpp: check that offsets are valid in + dictionary header (0012-speller-readonly_ws.cpp-check-that...). + . speller/readonly_ws.cpp: check first_word_offset without integer + overflow (0014-speller-readonly_ws.cpp-check-first_word_off...) + * Other problems upstream considers with similar severity as above, + although no CVE were issued: + - common/convert.hpp fix out-of-bounds read in + DecodeNormLookup::decode. (0002-common-convert.hpp-fix-out...). + - common/convert.hpp: fix stack overflow in create_norm_table on + crafted .cmap. (0003-common-convert.hpp-fix-stack-overflow...). + - speller/phonet.cpp: check target bounds in phonet '^^' rule + handling. (0004-speller-phonet.cpp-check-target-bounds...). + - speller/affix.cpp: fix out-of-bounds read in normalize_cond_str. + (0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz...). + - speller/affix.cpp: fix heap overflow in SfxEntry::add. + (0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch). + - speller/suggest.cpp: fix off-by-one error in Working::try_word_c. + (0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch). + - speller/affix.cpp: reject over-long strip/affix fields in affix + parse_file (0016-speller-affix.cpp-reject-over-long-strip-...). + - speller/writable.cpp: limit replacement word length to 250 + (0017-speller-writable.cpp-limit-replacement-word-length-t.patch). + + -- Agustin Martin Domingo <[email protected]> Thu, 05 Nov 2026 23:54:43 +0100 + aspell (0.60.8.1-4) unstable; urgency=medium * Borrow patch from upstream to deal with gcc15. Thanks Sergei diff -Nru aspell-0.60.8.1/debian/patches/0002-common-convert.hpp-fix-out-of-bounds-read-in-DecodeN.patch aspell-0.60.8.1/debian/patches/0002-common-convert.hpp-fix-out-of-bounds-read-in-DecodeN.patch --- aspell-0.60.8.1/debian/patches/0002-common-convert.hpp-fix-out-of-bounds-read-in-DecodeN.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0002-common-convert.hpp-fix-out-of-bounds-read-in-DecodeN.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,29 @@ +From 272076328b23a680beadda85d260cd12ab4384fc Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 26 Sep 2026 13:06:23 -0400 +Subject: [PATCH 02/17] common/convert.hpp fix out-of-bounds read in + DecodeNormLookup::decode + +Closes #686. + +Co-authored-by: arshiya tabasum <[email protected]> +--- + common/convert.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/convert.cpp b/common/convert.cpp +index 6a12645..5759b72 100644 +--- a/common/convert.cpp ++++ b/common/convert.cpp +@@ -668,7 +668,7 @@ namespace acommon { + ++in; + } else { + NormLookupRet<E,const char> ret = norm_lookup<E>(data, in, stop, 0, in); +- for (unsigned i = 0; ret.to[i] && i < E::max_to; ++i) ++ for (unsigned i = 0; i < E::max_to && ret.to[i]; ++i) + out.append(ret.to[i]); + in = ret.last + 1; + } +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0003-common-convert.hpp-fix-stack-overflow-in-create_norm.patch aspell-0.60.8.1/debian/patches/0003-common-convert.hpp-fix-stack-overflow-in-create_norm.patch --- aspell-0.60.8.1/debian/patches/0003-common-convert.hpp-fix-stack-overflow-in-create_norm.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0003-common-convert.hpp-fix-stack-overflow-in-create_norm.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,28 @@ +From 8ad84fd67d81a978d96f3388b2924dce7c1ba953 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 26 Sep 2026 13:13:18 -0400 +Subject: [PATCH 03/17] common/convert.hpp: fix stack overflow in + create_norm_table on crafted .cmap + +Closes #689. + +Co-authored-by: arshiya tabasum <[email protected]> +--- + common/convert.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/common/convert.cpp b/common/convert.cpp +index 5759b72..f0c108f 100644 +--- a/common/convert.cpp ++++ b/common/convert.cpp +@@ -376,6 +376,7 @@ namespace acommon { + VARARRAY(int, tally2_d, sz*4); Tally tally2(sz*4, tally2_d); + T * cur = d; + while (p = get_nb_line(in, buf), *p != '.') { ++ sanity(cur - d < size); + Uni32 f = strtoul(p, (char **)&p, 16); + cur->from = static_cast<typename T::From>(f); + sanity(f == cur->from); +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0004-speller-phonet.cpp-check-target-bounds-in-phonet-rul.patch aspell-0.60.8.1/debian/patches/0004-speller-phonet.cpp-check-target-bounds-in-phonet-rul.patch --- aspell-0.60.8.1/debian/patches/0004-speller-phonet.cpp-check-target-bounds-in-phonet-rul.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0004-speller-phonet.cpp-check-target-bounds-in-phonet-rul.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,29 @@ +From 9d2b905b600763cf04cfe2e67a4a773b082f9698 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 26 Sep 2026 13:24:04 -0400 +Subject: [PATCH 04/17] speller/phonet.cpp: check target bounds in phonet '^^' + rule handling + +Closes #690. + +Co-authored-by: arshiya tabasum <[email protected]> +--- + modules/speller/default/phonet.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/speller/default/phonet.cpp b/modules/speller/default/phonet.cpp +index 281aed4..a401f1a 100644 +--- a/modules/speller/default/phonet.cpp ++++ b/modules/speller/default/phonet.cpp +@@ -408,7 +408,7 @@ namespace aspeller { + c = *s; + if (parms.rules[n][0] != '\0' + && strstr (parms.rules[n]+1, "^^") != NULL) { +- if (c != '\0') { ++ if (c != '\0' && j < len) { + target[j] = c; + j++; + } +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz.patch aspell-0.60.8.1/debian/patches/0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz.patch --- aspell-0.60.8.1/debian/patches/0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,28 @@ +From f9630fe73fecd74953945114afdb8defdc168c92 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 26 Sep 2026 13:28:55 -0400 +Subject: [PATCH 05/17] speller/affix.cpp: fix out-of-bounds read in + normalize_cond_str + +Closes #691. + +Co-authored-by: arshiya tabasum <[email protected]> +--- + modules/speller/default/affix.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp +index f8ef287..1a7ea9e 100644 +--- a/modules/speller/default/affix.cpp ++++ b/modules/speller/default/affix.cpp +@@ -222,6 +222,7 @@ static int normalize_cond_str(char * str) + if (*s == '\0' || *s == '[') return -1; + char * min = s; + for (char * i = s + 1; *i != ']'; ++i) { ++ if (*i == '\0') return -1; + if ((byte)*i < (byte)*min) min = i;} + char c = *s; + *d++ = *min; +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch aspell-0.60.8.1/debian/patches/0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch --- aspell-0.60.8.1/debian/patches/0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,29 @@ +From 6f5cb59a198c449b301d7a8e7dff8f2580a0e103 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 26 Sep 2026 19:18:44 -0400 +Subject: [PATCH 06/17] speller/affix.cpp: fix heap overflow in SfxEntry::add + +In particular fix a heap overflow in SfxEntry::add when a +cross-product prefix shortens the word below the suffix strip length. + +Closes #688. +--- + modules/speller/default/affix.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp +index 1a7ea9e..b2d45d9 100644 +--- a/modules/speller/default/affix.cpp ++++ b/modules/speller/default/affix.cpp +@@ -1196,7 +1196,7 @@ SimpleString SfxEntry::add(SimpleString word, ObjStack & buf, + if ((conds->get(*--cp) & (1 << cond)) == 0) + break; + } +- if (cond < 0) { ++ if (cond < 0 && word.size > stripl) { + int alen = word.size - stripl; + if (alen >= limit) return EMPTY; + /* we have a match so add suffix */ +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0007-common-string.hpp-fix-integer-overflow-in-String-res.patch aspell-0.60.8.1/debian/patches/0007-common-string.hpp-fix-integer-overflow-in-String-res.patch --- aspell-0.60.8.1/debian/patches/0007-common-string.hpp-fix-integer-overflow-in-String-res.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0007-common-string.hpp-fix-integer-overflow-in-String-res.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,27 @@ +From b830b6e836aea9373fe5dd361b58e2d19d511958 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sun, 20 Sep 2026 23:34:24 -0400 +Subject: [PATCH 07/17] common/string.hpp: fix integer overflow in + String::reserve + +Fixes CVE-2026-103658 found by Anthropic and validated by Ada Logics. +--- + common/string.hpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/string.hpp b/common/string.hpp +index 18f67df..ee30b45 100644 +--- a/common/string.hpp ++++ b/common/string.hpp +@@ -75,7 +75,7 @@ namespace acommon { + public: + void reserve(size_t s) + { +- if (storage_end_ - begin_ >= (int)s + 1) return; ++ if ((size_t)(storage_end_ - begin_) >= s + 1) return; + reserve_i(s); + } + +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0008-speller-readonly_ws.cpp-check-return-status-of-file-.patch aspell-0.60.8.1/debian/patches/0008-speller-readonly_ws.cpp-check-return-status-of-file-.patch --- aspell-0.60.8.1/debian/patches/0008-speller-readonly_ws.cpp-check-return-status-of-file-.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0008-speller-readonly_ws.cpp-check-return-status-of-file-.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,75 @@ +From 0b52f16f997958a7d973cbda15f106114a2b3749 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Mon, 21 Sep 2026 20:33:13 -0400 +Subject: [PATCH 08/17] speller/readonly_ws.cpp: check return status of file + reads + +Return an error on EOF as this should not happen if the header of the +compiled dictionary is valid. +--- + modules/speller/default/readonly_ws.cpp | 21 ++++++++++++++++----- + 1 file changed, 16 insertions(+), 5 deletions(-) + +diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp +index c38cde9..d30ed30 100644 +--- a/modules/speller/default/readonly_ws.cpp ++++ b/modules/speller/default/readonly_ws.cpp +@@ -178,6 +178,17 @@ static inline bool duplicate_flag(const char * d) { + return get_flags(d) & DUPLICATE_FLAG; + } + ++PosibErr<void> read_w_error(FStream & f, const char * file_name, void * str, unsigned int n) ++{ ++ FILE * fh = f.c_stream(); ++ fread(str,1,n,fh); ++ if (feof(fh)) ++ return make_err(bad_file_format, file_name, _("Unexpected EOF.")); ++ if (ferror(fh)) ++ return make_err(file_error, file_name, _("I/O Error.")); ++ return no_err; ++} ++ + namespace { + + using namespace aspeller; +@@ -442,10 +453,10 @@ namespace { + CharVector word; + + word.resize(data_head.dict_name_size); +- f.read(word.data(), data_head.dict_name_size); ++ RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.dict_name_size)); + + word.resize(data_head.lang_name_size); +- f.read(word.data(), data_head.lang_name_size); ++ RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.lang_name_size)); + + PosibErr<void> pe = set_check_lang(word.data(),config); + if (pe.has_err()) { +@@ -457,13 +468,13 @@ namespace { + + if (data_head.soundslike_name_size != 0) { + word.resize(data_head.soundslike_name_size); +- f.read(word.data(), data_head.soundslike_name_size); ++ RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.soundslike_name_size)); + + if (strcmp(word.data(), lang()->soundslike_name()) != 0) + return make_err(bad_file_format, fn, _("Wrong soundslike.")); + + word.resize(data_head.soundslike_version_size); +- f.read(word.data(), data_head.soundslike_version_size); ++ RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.soundslike_version_size)); + + if (strcmp(word.data(), lang()->soundslike_version()) != 0) + return make_err(bad_file_format, fn, _("Wrong soundslike version.")); +@@ -484,7 +495,7 @@ namespace { + mmaped_block = 0; + block = (char *)malloc(block_size); + f.seek(data_head.head_size); +- f.read(block, block_size); ++ RET_ON_ERR(read_w_error(f, fn, block, block_size)); + } + + if (data_head.jump2_offset) { +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0010-prezip-correctly-detect-bad-input.patch aspell-0.60.8.1/debian/patches/0010-prezip-correctly-detect-bad-input.patch --- aspell-0.60.8.1/debian/patches/0010-prezip-correctly-detect-bad-input.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0010-prezip-correctly-detect-bad-input.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,54 @@ +From 15b188437f9e0192d4ac4472ad66a4e2f62a782f Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sun, 20 Sep 2026 23:09:03 -0400 +Subject: [PATCH 10/17] prezip: correctly detect bad input +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fixes CVE-2026-75818 identified by Michał Majchrowicz and Marcin +Wyczechowski, members of the AFINE Team. +--- + prog/prezip.c | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/prog/prezip.c b/prog/prezip.c +index 3835114..bbd0772 100644 +--- a/prog/prezip.c ++++ b/prog/prezip.c +@@ -1,5 +1,5 @@ + /* +- * Copyright (c) 2004 ++ * Copyright (c) 2004,2026 + * Kevin Atkinson + * + * Permission to use, copy, modify, distribute and sell this software +@@ -47,7 +47,7 @@ + + #endif + +-#define HEAD "prezip, a prefix delta compressor. Version 0.1.1, 2004-11-06" ++#define HEAD "prezip, a prefix delta compressor. Version 0.1.2, 2026-09-20" + + typedef struct Word { + char * str; +@@ -65,7 +65,7 @@ typedef struct Word { + + #define ADV(w, c) do {char * s = w + c;\ + while(w != s) {\ +- if (*w == 0) ret = 3;\ ++ if (*w == 0) {ret = 3; goto error;}\ + ++w;}} while (0) + + int main (int argc, const char *argv[]) { +@@ -227,6 +227,7 @@ int main (int argc, const char *argv[]) { + assert(ret >= 0); + if (ret > 0 && argc > 2) + fputs(argv[2], stderr); ++ error: + if (ret == 2) + fputs("unknown format\n", stderr); + else if (ret == 3) +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0011-Limit-length-of-words-added-to-the-personal-dictiona.patch aspell-0.60.8.1/debian/patches/0011-Limit-length-of-words-added-to-the-personal-dictiona.patch --- aspell-0.60.8.1/debian/patches/0011-Limit-length-of-words-added-to-the-personal-dictiona.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0011-Limit-length-of-words-added-to-the-personal-dictiona.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,47 @@ +From 782ce94e4dc71eaec4ee1bd945eb3b9c47c5387d Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Mon, 21 Sep 2026 01:05:16 -0400 +Subject: [PATCH 11/17] Limit length of words added to the personal dictionary + to 250 characters. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fixes CVE-2026-75820 identified by Michał Majchrowicz and Marcin +Wyczechowski, members of the AFINE Team. +--- + modules/speller/default/writable.cpp | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/modules/speller/default/writable.cpp b/modules/speller/default/writable.cpp +index 63172a4..b3b2990 100644 +--- a/modules/speller/default/writable.cpp ++++ b/modules/speller/default/writable.cpp +@@ -16,6 +16,8 @@ + #include "language.hpp" + #include "getdata.hpp" + ++#include "gettext.h" ++ + namespace { + + ////////////////////////////////////////////////////////////////////// +@@ -511,10 +513,14 @@ PosibErr<void> WritableDict::add(ParmString w, ParmString s) { + SensitiveCompare c(lang()); + WordEntry we; + if (WritableDict::lookup(w,&c,we)) return no_err; ++ if (w.size() > 250) ++ return make_err(invalid_word, ++ MsgConv(*lang())(w), ++ _("Word larger than 250 characters.")); + byte * w2; + w2 = (byte *)buffer.alloc(w.size() + 3); + *w2++ = lang()->get_word_info(w); +- *w2++ = w.size(); ++ *w2++ = (byte)w.size(); + memcpy(w2, w.str(), w.size() + 1); + word_lookup->insert((char *)w2); + if (use_soundslike) { +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0012-speller-readonly_ws.cpp-check-that-offsets-are-valid.patch aspell-0.60.8.1/debian/patches/0012-speller-readonly_ws.cpp-check-that-offsets-are-valid.patch --- aspell-0.60.8.1/debian/patches/0012-speller-readonly_ws.cpp-check-that-offsets-are-valid.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0012-speller-readonly_ws.cpp-check-that-offsets-are-valid.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,38 @@ +From 941953b25031bc9104e83f58e138a664b8dedc3f Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Mon, 21 Sep 2026 22:53:56 -0400 +Subject: [PATCH 12/17] speller/readonly_ws.cpp: check that offsets are valid + in dictionary header +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fixes CVE-2026-75819 identified by Michał Majchrowicz and Marcin +Wyczechowski, members of the AFINE Team. +--- + modules/speller/default/readonly_ws.cpp | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp +index d30ed30..0b99bd0 100644 +--- a/modules/speller/default/readonly_ws.cpp ++++ b/modules/speller/default/readonly_ws.cpp +@@ -487,6 +487,15 @@ namespace { + + block_size = data_head.block_size; + int offset = data_head.head_size; ++ ++ if (data_head.word_offset > block_size ++ || data_head.hash_offset > block_size ++ || data_head.jump1_offset > block_size ++ || data_head.jump2_offset > block_size ++ || data_head.word_offset + data_head.first_word_offset > block_size) ++ return make_err(bad_file_format, fn, ++ _("Invalid offset in dictionary header.")); ++ + mmaped_block = mmap_open(block_size + offset, f, 0); + if( mmaped_block != (char *)MAP_FAILED) { + block = mmaped_block + offset; +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0014-speller-readonly_ws.cpp-check-first_word_offset-with.patch aspell-0.60.8.1/debian/patches/0014-speller-readonly_ws.cpp-check-first_word_offset-with.patch --- aspell-0.60.8.1/debian/patches/0014-speller-readonly_ws.cpp-check-first_word_offset-with.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0014-speller-readonly_ws.cpp-check-first_word_offset-with.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,29 @@ +From 7f9bda27d68b34c2abfbf82ad7bdbab45c1fb903 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Sat, 10 Oct 2026 15:45:39 -0400 +Subject: [PATCH 14/17] speller/readonly_ws.cpp: check first_word_offset + without integer overflow + +Co-authored-by: arshiya tabasum <[email protected]> + +Closes #694. +--- + modules/speller/default/readonly_ws.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp +index 895f836..a02716a 100644 +--- a/modules/speller/default/readonly_ws.cpp ++++ b/modules/speller/default/readonly_ws.cpp +@@ -492,7 +492,7 @@ namespace { + || data_head.hash_offset > block_size + || data_head.jump1_offset > block_size + || data_head.jump2_offset > block_size +- || data_head.word_offset + data_head.first_word_offset > block_size) ++ || data_head.first_word_offset > block_size - data_head.word_offset) + return make_err(bad_file_format, fn, + _("Invalid offset in dictionary header.")); + +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch aspell-0.60.8.1/debian/patches/0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch --- aspell-0.60.8.1/debian/patches/0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,26 @@ +From fcaa6f9d5659a12449ecabef696f44d8e6ecac9a Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Tue, 13 Oct 2026 17:03:32 -0400 +Subject: [PATCH 15/17] speller/suggest.cpp: fix off-by-one error in + Working::try_word_c + +--- + modules/speller/default/suggest.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/speller/default/suggest.cpp b/modules/speller/default/suggest.cpp +index 52b25b7..dd76e06 100644 +--- a/modules/speller/default/suggest.cpp ++++ b/modules/speller/default/suggest.cpp +@@ -230,7 +230,7 @@ namespace { + static const bool do_count = true; + static const bool dont_count = false; + +- CheckInfo check_info[8]; ++ CheckInfo check_info[9]; + + void commit_temp(const char * b) { + if (temp_end) { +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0016-speller-affix.cpp-reject-over-long-strip-affix-field.patch aspell-0.60.8.1/debian/patches/0016-speller-affix.cpp-reject-over-long-strip-affix-field.patch --- aspell-0.60.8.1/debian/patches/0016-speller-affix.cpp-reject-over-long-strip-affix-field.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0016-speller-affix.cpp-reject-over-long-strip-affix-field.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,43 @@ +From 011ef4d48f6467129a40516c4ba4bb837657b64c Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Mon, 19 Oct 2026 13:39:06 -0400 +Subject: [PATCH 16/17] speller/affix.cpp: reject over-long strip/affix fields + in affix parse_file + +Co-authored-by: arshiya tabasum <[email protected]> + +Closes #696. +--- + modules/speller/default/affix.cpp | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp +index ab89d57..6e180ef 100644 +--- a/modules/speller/default/affix.cpp ++++ b/modules/speller/default/affix.cpp +@@ -372,6 +372,10 @@ PosibErr<void> AffixMgr::parse_file(const char * affpath, Conv & iconv) + // key is strip + if (dp.key != "0") { + ParmString s0(iconv(dp.key)); ++ // stripl is a byte, so a longer strip would be truncated while ++ // nptr->strip keeps the full string; the two then disagree and ++ // PfxEntry/SfxEntry::check overflow their tmpword buffer. ++ if (s0.size() > MAXWORDLEN) goto error; + max_(max_strip_, s0.size()); + max_(max_strip_f[(byte)achar], s0.size()); + nptr->strip = data_buf.dup(s0); +@@ -386,7 +390,10 @@ PosibErr<void> AffixMgr::parse_file(const char * affpath, Conv & iconv) + // key is affix string or 0 for null + if (dp.key != "0") { + nptr->appnd = data_buf.dup(iconv(dp.key)); +- nptr->appndl = strlen(nptr->appnd); ++ size_t appndl = strlen(nptr->appnd); ++ // appndl is a byte; reject anything that would not fit ++ if (appndl > MAXWORDLEN) goto error; ++ nptr->appndl = appndl; + } else { + nptr->appnd = ""; + nptr->appndl = 0; +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/0017-speller-writable.cpp-limit-replacement-word-length-t.patch aspell-0.60.8.1/debian/patches/0017-speller-writable.cpp-limit-replacement-word-length-t.patch --- aspell-0.60.8.1/debian/patches/0017-speller-writable.cpp-limit-replacement-word-length-t.patch 1970-01-01 01:00:00.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/0017-speller-writable.cpp-limit-replacement-word-length-t.patch 2026-11-05 23:54:43.000000000 +0100 @@ -0,0 +1,78 @@ +From 56dfb10f84caaea8a33607b8e5346362bf17f9c4 Mon Sep 17 00:00:00 2001 +From: Kevin Atkinson <[email protected]> +Date: Wed, 21 Oct 2026 02:16:00 -0400 +Subject: [PATCH 17/17] speller/writable.cpp: limit replacement word length to + 250 + +Co-authored-by: arshiya tabasum <[email protected]> + +Closes #697. +--- + modules/speller/default/speller_impl.cpp | 2 +- + modules/speller/default/writable.cpp | 16 ++++++++++++---- + 2 files changed, 13 insertions(+), 5 deletions(-) + +diff --git a/modules/speller/default/speller_impl.cpp b/modules/speller/default/speller_impl.cpp +index 155fbb5..4567e5d 100644 +--- a/modules/speller/default/speller_impl.cpp ++++ b/modules/speller/default/speller_impl.cpp +@@ -108,7 +108,7 @@ namespace aspeller { + return no_err; + if (first_word == 0 || cor != first_word) { + lang().to_lower(buf, mis.str()); +- repl_->add_repl(buf, cor_orignal_casing); ++ repl_->add_repl(buf, cor_orignal_casing).ignore_err(); + } + + if (memory && prev_cor_repl_ == mis) +diff --git a/modules/speller/default/writable.cpp b/modules/speller/default/writable.cpp +index b3b2990..b6b088e 100644 +--- a/modules/speller/default/writable.cpp ++++ b/modules/speller/default/writable.cpp +@@ -515,8 +515,8 @@ PosibErr<void> WritableDict::add(ParmString w, ParmString s) { + if (WritableDict::lookup(w,&c,we)) return no_err; + if (w.size() > 250) + return make_err(invalid_word, +- MsgConv(*lang())(w), +- _("Word larger than 250 characters.")); ++ MsgConv(*lang())(w), ++ _("Word larger than 250 characters.")); + byte * w2; + w2 = (byte *)buffer.alloc(w.size() + 3); + *w2++ = lang()->get_word_info(w); +@@ -796,6 +796,10 @@ bool WritableReplDict::repl_lookup(ParmString word, WordEntry & o) const + + PosibErr<void> WritableReplDict::add_repl(ParmString mis, ParmString cor, ParmString sl) + { ++ if (mis.size() > 250 || cor.size() > 250) ++ return make_err(invalid_word, ++ MsgConv(*lang())(mis.size() > 250 ? mis : cor), ++ _("Word larger than 250 characters.")); + Str m; + SensitiveCompare cmp(lang()); // FIXME: I don't think this is completely correct + WordEntry we; +@@ -926,7 +930,11 @@ PosibErr<void> WritableReplDict::merge(FStream & in, + *repl = '\0'; // split string + ++repl; + if (!repl[0]) continue; // empty repl, ignore +- WritableReplDict::add_repl(conv1(mis), conv2(repl)); ++ Ret pe = WritableReplDict::add_repl(conv1(mis), conv2(repl)); ++ if (pe.has_err()) { ++ clear(); ++ return pe.with_file(file_name); ++ } + } + + } else { +@@ -940,7 +948,7 @@ PosibErr<void> WritableReplDict::merge(FStream & in, + in.ignore(); // ignore space + for (j = 0; j != num_repls; ++j) { + in.getline(repl, ','); +- WritableReplDict::add_repl(mis, repl); ++ WritableReplDict::add_repl(mis, repl).ignore_err(); + } + } + } +-- +2.55.0 + diff -Nru aspell-0.60.8.1/debian/patches/series aspell-0.60.8.1/debian/patches/series --- aspell-0.60.8.1/debian/patches/series 2025-02-18 00:43:43.000000000 +0100 +++ aspell-0.60.8.1/debian/patches/series 2026-11-05 23:54:43.000000000 +0100 @@ -8,3 +8,19 @@ 1000_aspell.1.diff 1000_aspell-import.1.diff 1001-modules-speller-default-vector_hash-t.hpp-fix-gcc-15.patch + +# ------------- Borrowed from 0.60.8.3 ------------------------ +0002-common-convert.hpp-fix-out-of-bounds-read-in-DecodeN.patch +0003-common-convert.hpp-fix-stack-overflow-in-create_norm.patch +0004-speller-phonet.cpp-check-target-bounds-in-phonet-rul.patch +0005-speller-affix.cpp-fix-out-of-bounds-read-in-normaliz.patch +0006-speller-affix.cpp-fix-heap-overflow-in-SfxEntry-add.patch +0007-common-string.hpp-fix-integer-overflow-in-String-res.patch +0008-speller-readonly_ws.cpp-check-return-status-of-file-.patch +0010-prezip-correctly-detect-bad-input.patch +0011-Limit-length-of-words-added-to-the-personal-dictiona.patch +0012-speller-readonly_ws.cpp-check-that-offsets-are-valid.patch +0014-speller-readonly_ws.cpp-check-first_word_offset-with.patch +0015-speller-suggest.cpp-fix-off-by-one-error-in-Working-.patch +0016-speller-affix.cpp-reject-over-long-strip-affix-field.patch +0017-speller-writable.cpp-limit-replacement-word-length-t.patch
>From 272076328b23a680beadda85d260cd12ab4384fc Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 26 Sep 2026 13:06:23 -0400 Subject: [PATCH 02/17] common/convert.hpp fix out-of-bounds read in DecodeNormLookup::decode Closes #686. Co-authored-by: arshiya tabasum <[email protected]> --- common/convert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/convert.cpp b/common/convert.cpp index 6a12645..5759b72 100644 --- a/common/convert.cpp +++ b/common/convert.cpp @@ -668,7 +668,7 @@ namespace acommon { ++in; } else { NormLookupRet<E,const char> ret = norm_lookup<E>(data, in, stop, 0, in); - for (unsigned i = 0; ret.to[i] && i < E::max_to; ++i) + for (unsigned i = 0; i < E::max_to && ret.to[i]; ++i) out.append(ret.to[i]); in = ret.last + 1; } -- 2.55.0
>From 8ad84fd67d81a978d96f3388b2924dce7c1ba953 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 26 Sep 2026 13:13:18 -0400 Subject: [PATCH 03/17] common/convert.hpp: fix stack overflow in create_norm_table on crafted .cmap Closes #689. Co-authored-by: arshiya tabasum <[email protected]> --- common/convert.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/common/convert.cpp b/common/convert.cpp index 5759b72..f0c108f 100644 --- a/common/convert.cpp +++ b/common/convert.cpp @@ -376,6 +376,7 @@ namespace acommon { VARARRAY(int, tally2_d, sz*4); Tally tally2(sz*4, tally2_d); T * cur = d; while (p = get_nb_line(in, buf), *p != '.') { + sanity(cur - d < size); Uni32 f = strtoul(p, (char **)&p, 16); cur->from = static_cast<typename T::From>(f); sanity(f == cur->from); -- 2.55.0
>From 9d2b905b600763cf04cfe2e67a4a773b082f9698 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 26 Sep 2026 13:24:04 -0400 Subject: [PATCH 04/17] speller/phonet.cpp: check target bounds in phonet '^^' rule handling Closes #690. Co-authored-by: arshiya tabasum <[email protected]> --- modules/speller/default/phonet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/speller/default/phonet.cpp b/modules/speller/default/phonet.cpp index 281aed4..a401f1a 100644 --- a/modules/speller/default/phonet.cpp +++ b/modules/speller/default/phonet.cpp @@ -408,7 +408,7 @@ namespace aspeller { c = *s; if (parms.rules[n][0] != '\0' && strstr (parms.rules[n]+1, "^^") != NULL) { - if (c != '\0') { + if (c != '\0' && j < len) { target[j] = c; j++; } -- 2.55.0
>From f9630fe73fecd74953945114afdb8defdc168c92 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 26 Sep 2026 13:28:55 -0400 Subject: [PATCH 05/17] speller/affix.cpp: fix out-of-bounds read in normalize_cond_str Closes #691. Co-authored-by: arshiya tabasum <[email protected]> --- modules/speller/default/affix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp index f8ef287..1a7ea9e 100644 --- a/modules/speller/default/affix.cpp +++ b/modules/speller/default/affix.cpp @@ -222,6 +222,7 @@ static int normalize_cond_str(char * str) if (*s == '\0' || *s == '[') return -1; char * min = s; for (char * i = s + 1; *i != ']'; ++i) { + if (*i == '\0') return -1; if ((byte)*i < (byte)*min) min = i;} char c = *s; *d++ = *min; -- 2.55.0
>From 6f5cb59a198c449b301d7a8e7dff8f2580a0e103 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 26 Sep 2026 19:18:44 -0400 Subject: [PATCH 06/17] speller/affix.cpp: fix heap overflow in SfxEntry::add In particular fix a heap overflow in SfxEntry::add when a cross-product prefix shortens the word below the suffix strip length. Closes #688. --- modules/speller/default/affix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp index 1a7ea9e..b2d45d9 100644 --- a/modules/speller/default/affix.cpp +++ b/modules/speller/default/affix.cpp @@ -1196,7 +1196,7 @@ SimpleString SfxEntry::add(SimpleString word, ObjStack & buf, if ((conds->get(*--cp) & (1 << cond)) == 0) break; } - if (cond < 0) { + if (cond < 0 && word.size > stripl) { int alen = word.size - stripl; if (alen >= limit) return EMPTY; /* we have a match so add suffix */ -- 2.55.0
>From b830b6e836aea9373fe5dd361b58e2d19d511958 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sun, 20 Sep 2026 23:34:24 -0400 Subject: [PATCH 07/17] common/string.hpp: fix integer overflow in String::reserve Fixes CVE-2026-103658 found by Anthropic and validated by Ada Logics. --- common/string.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/string.hpp b/common/string.hpp index 18f67df..ee30b45 100644 --- a/common/string.hpp +++ b/common/string.hpp @@ -75,7 +75,7 @@ namespace acommon { public: void reserve(size_t s) { - if (storage_end_ - begin_ >= (int)s + 1) return; + if ((size_t)(storage_end_ - begin_) >= s + 1) return; reserve_i(s); } -- 2.55.0
>From 0b52f16f997958a7d973cbda15f106114a2b3749 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Mon, 21 Sep 2026 20:33:13 -0400 Subject: [PATCH 08/17] speller/readonly_ws.cpp: check return status of file reads Return an error on EOF as this should not happen if the header of the compiled dictionary is valid. --- modules/speller/default/readonly_ws.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp index c38cde9..d30ed30 100644 --- a/modules/speller/default/readonly_ws.cpp +++ b/modules/speller/default/readonly_ws.cpp @@ -178,6 +178,17 @@ static inline bool duplicate_flag(const char * d) { return get_flags(d) & DUPLICATE_FLAG; } +PosibErr<void> read_w_error(FStream & f, const char * file_name, void * str, unsigned int n) +{ + FILE * fh = f.c_stream(); + fread(str,1,n,fh); + if (feof(fh)) + return make_err(bad_file_format, file_name, _("Unexpected EOF.")); + if (ferror(fh)) + return make_err(file_error, file_name, _("I/O Error.")); + return no_err; +} + namespace { using namespace aspeller; @@ -442,10 +453,10 @@ namespace { CharVector word; word.resize(data_head.dict_name_size); - f.read(word.data(), data_head.dict_name_size); + RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.dict_name_size)); word.resize(data_head.lang_name_size); - f.read(word.data(), data_head.lang_name_size); + RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.lang_name_size)); PosibErr<void> pe = set_check_lang(word.data(),config); if (pe.has_err()) { @@ -457,13 +468,13 @@ namespace { if (data_head.soundslike_name_size != 0) { word.resize(data_head.soundslike_name_size); - f.read(word.data(), data_head.soundslike_name_size); + RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.soundslike_name_size)); if (strcmp(word.data(), lang()->soundslike_name()) != 0) return make_err(bad_file_format, fn, _("Wrong soundslike.")); word.resize(data_head.soundslike_version_size); - f.read(word.data(), data_head.soundslike_version_size); + RET_ON_ERR(read_w_error(f, fn, word.data(), data_head.soundslike_version_size)); if (strcmp(word.data(), lang()->soundslike_version()) != 0) return make_err(bad_file_format, fn, _("Wrong soundslike version.")); @@ -484,7 +495,7 @@ namespace { mmaped_block = 0; block = (char *)malloc(block_size); f.seek(data_head.head_size); - f.read(block, block_size); + RET_ON_ERR(read_w_error(f, fn, block, block_size)); } if (data_head.jump2_offset) { -- 2.55.0
>From 15b188437f9e0192d4ac4472ad66a4e2f62a782f Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sun, 20 Sep 2026 23:09:03 -0400 Subject: [PATCH 10/17] prezip: correctly detect bad input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes CVE-2026-75818 identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team. --- prog/prezip.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prog/prezip.c b/prog/prezip.c index 3835114..bbd0772 100644 --- a/prog/prezip.c +++ b/prog/prezip.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004 + * Copyright (c) 2004,2026 * Kevin Atkinson * * Permission to use, copy, modify, distribute and sell this software @@ -47,7 +47,7 @@ #endif -#define HEAD "prezip, a prefix delta compressor. Version 0.1.1, 2004-11-06" +#define HEAD "prezip, a prefix delta compressor. Version 0.1.2, 2026-09-20" typedef struct Word { char * str; @@ -65,7 +65,7 @@ typedef struct Word { #define ADV(w, c) do {char * s = w + c;\ while(w != s) {\ - if (*w == 0) ret = 3;\ + if (*w == 0) {ret = 3; goto error;}\ ++w;}} while (0) int main (int argc, const char *argv[]) { @@ -227,6 +227,7 @@ int main (int argc, const char *argv[]) { assert(ret >= 0); if (ret > 0 && argc > 2) fputs(argv[2], stderr); + error: if (ret == 2) fputs("unknown format\n", stderr); else if (ret == 3) -- 2.55.0
>From 782ce94e4dc71eaec4ee1bd945eb3b9c47c5387d Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Mon, 21 Sep 2026 01:05:16 -0400 Subject: [PATCH 11/17] Limit length of words added to the personal dictionary to 250 characters. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes CVE-2026-75820 identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team. --- modules/speller/default/writable.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/speller/default/writable.cpp b/modules/speller/default/writable.cpp index 63172a4..b3b2990 100644 --- a/modules/speller/default/writable.cpp +++ b/modules/speller/default/writable.cpp @@ -16,6 +16,8 @@ #include "language.hpp" #include "getdata.hpp" +#include "gettext.h" + namespace { ////////////////////////////////////////////////////////////////////// @@ -511,10 +513,14 @@ PosibErr<void> WritableDict::add(ParmString w, ParmString s) { SensitiveCompare c(lang()); WordEntry we; if (WritableDict::lookup(w,&c,we)) return no_err; + if (w.size() > 250) + return make_err(invalid_word, + MsgConv(*lang())(w), + _("Word larger than 250 characters.")); byte * w2; w2 = (byte *)buffer.alloc(w.size() + 3); *w2++ = lang()->get_word_info(w); - *w2++ = w.size(); + *w2++ = (byte)w.size(); memcpy(w2, w.str(), w.size() + 1); word_lookup->insert((char *)w2); if (use_soundslike) { -- 2.55.0
>From 941953b25031bc9104e83f58e138a664b8dedc3f Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Mon, 21 Sep 2026 22:53:56 -0400 Subject: [PATCH 12/17] speller/readonly_ws.cpp: check that offsets are valid in dictionary header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes CVE-2026-75819 identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team. --- modules/speller/default/readonly_ws.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp index d30ed30..0b99bd0 100644 --- a/modules/speller/default/readonly_ws.cpp +++ b/modules/speller/default/readonly_ws.cpp @@ -487,6 +487,15 @@ namespace { block_size = data_head.block_size; int offset = data_head.head_size; + + if (data_head.word_offset > block_size + || data_head.hash_offset > block_size + || data_head.jump1_offset > block_size + || data_head.jump2_offset > block_size + || data_head.word_offset + data_head.first_word_offset > block_size) + return make_err(bad_file_format, fn, + _("Invalid offset in dictionary header.")); + mmaped_block = mmap_open(block_size + offset, f, 0); if( mmaped_block != (char *)MAP_FAILED) { block = mmaped_block + offset; -- 2.55.0
>From 7f9bda27d68b34c2abfbf82ad7bdbab45c1fb903 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Sat, 10 Oct 2026 15:45:39 -0400 Subject: [PATCH 14/17] speller/readonly_ws.cpp: check first_word_offset without integer overflow Co-authored-by: arshiya tabasum <[email protected]> Closes #694. --- modules/speller/default/readonly_ws.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/speller/default/readonly_ws.cpp b/modules/speller/default/readonly_ws.cpp index 895f836..a02716a 100644 --- a/modules/speller/default/readonly_ws.cpp +++ b/modules/speller/default/readonly_ws.cpp @@ -492,7 +492,7 @@ namespace { || data_head.hash_offset > block_size || data_head.jump1_offset > block_size || data_head.jump2_offset > block_size - || data_head.word_offset + data_head.first_word_offset > block_size) + || data_head.first_word_offset > block_size - data_head.word_offset) return make_err(bad_file_format, fn, _("Invalid offset in dictionary header.")); -- 2.55.0
>From fcaa6f9d5659a12449ecabef696f44d8e6ecac9a Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Tue, 13 Oct 2026 17:03:32 -0400 Subject: [PATCH 15/17] speller/suggest.cpp: fix off-by-one error in Working::try_word_c --- modules/speller/default/suggest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/speller/default/suggest.cpp b/modules/speller/default/suggest.cpp index 52b25b7..dd76e06 100644 --- a/modules/speller/default/suggest.cpp +++ b/modules/speller/default/suggest.cpp @@ -230,7 +230,7 @@ namespace { static const bool do_count = true; static const bool dont_count = false; - CheckInfo check_info[8]; + CheckInfo check_info[9]; void commit_temp(const char * b) { if (temp_end) { -- 2.55.0
>From 011ef4d48f6467129a40516c4ba4bb837657b64c Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Mon, 19 Oct 2026 13:39:06 -0400 Subject: [PATCH 16/17] speller/affix.cpp: reject over-long strip/affix fields in affix parse_file Co-authored-by: arshiya tabasum <[email protected]> Closes #696. --- modules/speller/default/affix.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/speller/default/affix.cpp b/modules/speller/default/affix.cpp index ab89d57..6e180ef 100644 --- a/modules/speller/default/affix.cpp +++ b/modules/speller/default/affix.cpp @@ -372,6 +372,10 @@ PosibErr<void> AffixMgr::parse_file(const char * affpath, Conv & iconv) // key is strip if (dp.key != "0") { ParmString s0(iconv(dp.key)); + // stripl is a byte, so a longer strip would be truncated while + // nptr->strip keeps the full string; the two then disagree and + // PfxEntry/SfxEntry::check overflow their tmpword buffer. + if (s0.size() > MAXWORDLEN) goto error; max_(max_strip_, s0.size()); max_(max_strip_f[(byte)achar], s0.size()); nptr->strip = data_buf.dup(s0); @@ -386,7 +390,10 @@ PosibErr<void> AffixMgr::parse_file(const char * affpath, Conv & iconv) // key is affix string or 0 for null if (dp.key != "0") { nptr->appnd = data_buf.dup(iconv(dp.key)); - nptr->appndl = strlen(nptr->appnd); + size_t appndl = strlen(nptr->appnd); + // appndl is a byte; reject anything that would not fit + if (appndl > MAXWORDLEN) goto error; + nptr->appndl = appndl; } else { nptr->appnd = ""; nptr->appndl = 0; -- 2.55.0
>From 56dfb10f84caaea8a33607b8e5346362bf17f9c4 Mon Sep 17 00:00:00 2001 From: Kevin Atkinson <[email protected]> Date: Wed, 21 Oct 2026 02:16:00 -0400 Subject: [PATCH 17/17] speller/writable.cpp: limit replacement word length to 250 Co-authored-by: arshiya tabasum <[email protected]> Closes #697. --- modules/speller/default/speller_impl.cpp | 2 +- modules/speller/default/writable.cpp | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/modules/speller/default/speller_impl.cpp b/modules/speller/default/speller_impl.cpp index 155fbb5..4567e5d 100644 --- a/modules/speller/default/speller_impl.cpp +++ b/modules/speller/default/speller_impl.cpp @@ -108,7 +108,7 @@ namespace aspeller { return no_err; if (first_word == 0 || cor != first_word) { lang().to_lower(buf, mis.str()); - repl_->add_repl(buf, cor_orignal_casing); + repl_->add_repl(buf, cor_orignal_casing).ignore_err(); } if (memory && prev_cor_repl_ == mis) diff --git a/modules/speller/default/writable.cpp b/modules/speller/default/writable.cpp index b3b2990..b6b088e 100644 --- a/modules/speller/default/writable.cpp +++ b/modules/speller/default/writable.cpp @@ -515,8 +515,8 @@ PosibErr<void> WritableDict::add(ParmString w, ParmString s) { if (WritableDict::lookup(w,&c,we)) return no_err; if (w.size() > 250) return make_err(invalid_word, - MsgConv(*lang())(w), - _("Word larger than 250 characters.")); + MsgConv(*lang())(w), + _("Word larger than 250 characters.")); byte * w2; w2 = (byte *)buffer.alloc(w.size() + 3); *w2++ = lang()->get_word_info(w); @@ -796,6 +796,10 @@ bool WritableReplDict::repl_lookup(ParmString word, WordEntry & o) const PosibErr<void> WritableReplDict::add_repl(ParmString mis, ParmString cor, ParmString sl) { + if (mis.size() > 250 || cor.size() > 250) + return make_err(invalid_word, + MsgConv(*lang())(mis.size() > 250 ? mis : cor), + _("Word larger than 250 characters.")); Str m; SensitiveCompare cmp(lang()); // FIXME: I don't think this is completely correct WordEntry we; @@ -926,7 +930,11 @@ PosibErr<void> WritableReplDict::merge(FStream & in, *repl = '\0'; // split string ++repl; if (!repl[0]) continue; // empty repl, ignore - WritableReplDict::add_repl(conv1(mis), conv2(repl)); + Ret pe = WritableReplDict::add_repl(conv1(mis), conv2(repl)); + if (pe.has_err()) { + clear(); + return pe.with_file(file_name); + } } } else { @@ -940,7 +948,7 @@ PosibErr<void> WritableReplDict::merge(FStream & in, in.ignore(); // ignore space for (j = 0; j != num_repls; ++j) { in.getline(repl, ','); - WritableReplDict::add_repl(mis, repl); + WritableReplDict::add_repl(mis, repl).ignore_err(); } } } -- 2.55.0

