aspell expand can overflow the heap when a word is expanded with a cross-product prefix followed by a cross-product suffix whose strip length is longer than the word left after the prefix.
SfxEntry::add (modules/speller/default/affix.cpp) checks the suffix conditions against orig_word but computes the copy length from word:
if ((orig_word.size > stripl) && (orig_word.size >= conds->num)) {
...
if (cond < 0) {
int alen = word.size - stripl; // word, not orig_word
if (alen >= limit) return EMPTY;
char * newword = (char *)buf.alloc(alen + appndl + 1);
memcpy(newword, word, alen);
...
When a cross-product prefix has already shortened the word, word can be shorter than orig_word. If word.size < stripl, the subtraction wraps (SimpleString::size is unsigned), alen becomes negative, the alen >= limit guard doesn't catch it, buf.alloc gets a tiny size, and memcpy(newword, word, alen) runs with a huge size_t.
Test case
Make a directory repro/ containing a copy of iso-8859-1.cset (from data/) plus these two files:
repro/xx.dat:
name xx
charset iso8859-1
special
soundslike none
affix xx
repro/xx_affix.dat:
SET iso8859-1
PFX A Y 1
PFX A aaaaa b aaaaa
SFX B Y 1
SFX B aaac x aaac
Then run:
echo 'aaaaac/AB' | ./prog/aspell --data-dir=repro --dict-dir=repro --lang=xx expand
With an ASan build this aborts:
AddressSanitizer: negative-size-param: (size=-2) in __asan_memcpy
#0 __asan_memcpy
#1 aspeller::SfxEntry::add affix.cpp:1203
#2 aspeller::AffixMgr::expand_suffix affix.cpp:939
#3 aspeller::AffixMgr::expand affix.cpp:917
Prefix A turns aaaaac into bc (size 2); suffix B has strip length 4; the conditions pass against orig_word aaaaac, so alen = 2 - 4 wraps to a large value.
PR #687 fixes it by bailing out with an empty result when word.size <= stripl, matching the guard PfxEntry::add and SfxEntry::applicable already use. After the patch the same command prints aaaaac bc aax and exits cleanly, and normal expansion (aaaaac/B -> aaaaac aax) is unaffected.
aspell expandcan overflow the heap when a word is expanded with a cross-product prefix followed by a cross-product suffix whose strip length is longer than the word left after the prefix.SfxEntry::add(modules/speller/default/affix.cpp) checks the suffix conditions againstorig_wordbut computes the copy length fromword:When a cross-product prefix has already shortened the word,
wordcan be shorter thanorig_word. Ifword.size < stripl, the subtraction wraps (SimpleString::sizeis unsigned),alenbecomes negative, thealen >= limitguard doesn't catch it,buf.allocgets a tiny size, andmemcpy(newword, word, alen)runs with a hugesize_t.Test case
Make a directory
repro/containing a copy ofiso-8859-1.cset(fromdata/) plus these two files:repro/xx.dat:repro/xx_affix.dat:Then run:
With an ASan build this aborts:
Prefix
Aturnsaaaaacintobc(size 2); suffixBhas strip length 4; the conditions pass againstorig_wordaaaaac, soalen = 2 - 4wraps to a large value.PR #687 fixes it by bailing out with an empty result when
word.size <= stripl, matching the guardPfxEntry::addandSfxEntry::applicablealready use. After the patch the same command printsaaaaac bc aaxand exits cleanly, and normal expansion (aaaaac/B->aaaaac aax) is unaffected.