Origin: https://sources.debian.org/data/main/e/espeak-ng/1.51%2Bdfsg-12/debian/patches/CVE commit 58f1e0b6a4e6aa55621c6f01118994d01fd6f68c Merge: f983e445 e7bcd3cc Author: Alexander Epaneshnikov Date: Sun Dec 17 15:29:30 2023 +0300 tests: fix CVE crashes (#1846) Fixes: #1823, #1824, #1825, #1826, #1827 - Add crash test and vectors provided by @SEU-SSL - Disallow dummy/null voice load (that causes incorrect translator initialization) - Fix empty `phondata` file load (that causes unitialized memory access) - Limit max word length for RemoveEnding (causes buffer overflow) - Limit punctlist initialization from embedded commands (buffer overflow) - Fix unitialized pitch in wavegen (DBZ and indexing problems) - Properly zeroize stack variables before use in TranslateClause and SetWordStress TODO (in nextup PR): add & fix more vectors from fuzzer. commit 9decedb8c229e1a4219baceaab7a3d656e889e31 Author: Samuel Thibault Date: Thu Jun 30 00:50:18 2022 +0200 Fix missing checks for EOF commit c4c05820c4a47369d5a81e4a506fe7abb2fa7ed6 Author: Yury Popov Date: Sat Dec 16 19:24:51 2023 +0300 tests: add CVE crash vectors commit e79405772cecf47053116aeaad10e64606292b14 Author: Yury Popov Date: Sat Dec 16 23:55:03 2023 +0300 voices: disallow dummy voice when not compiling commit 7d4ad3c2ae063cb08bfd606021bc323dfbadaba9 Author: Yury Popov Date: Sat Dec 16 21:50:07 2023 +0300 synthdata: fix empty file load commit b99f332c576eb49839613a55cfd3e0e1b5487191 Author: Yury Popov Date: Sat Dec 16 22:45:15 2023 +0300 dictionary: limit word length commit 1a7ecfc2f202438b17e742368f910e6099ce02b7 Author: Yury Popov Date: Sat Dec 16 22:50:01 2023 +0300 readclause: limit embedded punctlist length commit a5eb246debb51ba328ef399350dfcd5d87782245 Author: Yury Popov Date: Sat Dec 16 23:03:16 2023 +0300 wavegen: fix unitialized pitch commit 5f7db763e2eff1d8174d2b65a4bbe4b2a85c8a0c Author: Yury Popov Date: Sat Dec 16 23:17:45 2023 +0300 translate: fix number_buf initialization commit e7bcd3cc1599ebb531bb62fc3007d3ce1dade167 Author: Yury Popov Date: Sat Dec 16 23:26:07 2023 +0300 dictionary: fix stack initialization --- src/libespeak-ng/dictionary.c | 4 ++++ src/libespeak-ng/readclause.c | 12 ++++++------ src/libespeak-ng/synthdata.c | 18 ++++++++++++++---- src/libespeak-ng/translate.c | 1 + src/libespeak-ng/voices.c | 20 ++++++++++++-------- src/libespeak-ng/wavegen.c | 9 ++++++--- tests/crash.test | 17 +++++++++++++++++ tests/crash_vectors/cve-2023-49990.txt | 1 + tests/crash_vectors/cve-2023-49991.txt | 1 + tests/crash_vectors/cve-2023-49994.txt | 1 + 10 files changed, 63 insertions(+), 21 deletions(-) --- a/src/libespeak-ng/readclause.c +++ b/src/libespeak-ng/readclause.c @@ -335,7 +335,7 @@ static int AnnouncePunctuation(Translato if ((*bufix == 0) || (end_clause == 0) || (tr->langopts.param[LOPT_ANNOUNCE_PUNCT] & 2)) { punct_count = 1; - while ((c2 == c1) && (c1 != '<')) { // don't eat extra '<', it can miss XML tags + while (!Eof() && (c2 == c1) && (c1 != '<')) { // don't eat extra '<', it can miss XML tags punct_count++; c2 = GetC(); } @@ -647,7 +647,7 @@ int ReadClause(Translator *tr, char *buf // an embedded command. If it's a voice change, end the clause if (c2 == 'V') { buf[ix++] = 0; // end the clause at this point - while (!iswspace(c1 = GetC()) && !Eof() && (ix < (n_buf-1))) + while (!Eof() && !iswspace(c1 = GetC()) && (ix < (n_buf-1))) buf[ix++] = c1; // add voice name to end of buffer, after the text buf[ix++] = 0; return CLAUSE_VOICE; @@ -657,7 +657,7 @@ int ReadClause(Translator *tr, char *buf strcpy(&buf[ix], " "); ix += 3; - if ((c2 = GetC()) == '0') + if (!Eof() && (c2 = GetC()) == '0') option_punctuation = 0; else { option_punctuation = 1; @@ -665,7 +665,7 @@ int ReadClause(Translator *tr, char *buf if (c2 != '1') { // a list of punctuation characters to be spoken, terminated by space j = 0; - while (!iswspace(c2) && !Eof()) { + while (!Eof() && !iswspace(c2) && (j < N_PUNCTLIST-1)) { option_punctlist[j++] = c2; c2 = GetC(); buf[ix++] = ' '; @@ -791,7 +791,7 @@ int ReadClause(Translator *tr, char *buf } if ((c1 == '.') && (c2 == '.')) { - while ((c_next = GetC()) == '.') { + while (!Eof() && (c_next = GetC()) == '.') { // 3 or more dots, replace by elipsis c1 = 0x2026; c2 = ' '; @@ -808,7 +808,7 @@ int ReadClause(Translator *tr, char *buf // Handling of sequences of ? and ! like ??!?, !!??!, ?!! etc // Use only first char as determinant if(punct_data & (CLAUSE_QUESTION | CLAUSE_EXCLAMATION)) { - while(clause_type_from_codepoint(c2) & (CLAUSE_QUESTION | CLAUSE_EXCLAMATION)) { + while(!Eof() && clause_type_from_codepoint(c2) & (CLAUSE_QUESTION | CLAUSE_EXCLAMATION)) { c_next = GetC(); c2 = c_next; } --- /dev/null +++ b/tests/crash.test @@ -0,0 +1,17 @@ +#!/bin/sh +# include common script +. "`dirname $0`/common" + +test_crash() { + TEST_NAME=$1 + + echo "testing CVE-${TEST_NAME}" + ESPEAK_DATA_PATH=`pwd` LD_LIBRARY_PATH=src:${LD_LIBRARY_PATH} \ + $VALGRIND src/espeak-ng -f "$(dirname $0)/crash_vectors/${TEST_NAME}.txt" -w /dev/null || exit 1 +} + +test_crash cve-2023-49990 +test_crash cve-2023-49991 +test_crash cve-2023-49992 +test_crash cve-2023-49993 +test_crash cve-2023-49994 --- /dev/null +++ b/tests/crash_vectors/cve-2023-49990.txt @@ -0,0 +1 @@ +ã¦à»Vñ€¦ñ €¦V €äVñ€ãÂà¦æsññâñþâññà¶æØØsññâñþâññeeeeeeeeseee€ññûñ \ No newline at end of file --- /dev/null +++ b/tests/crash_vectors/cve-2023-49991.txt @@ -0,0 +1 @@ +€¦Vñ €ñVðÕhñùâÿñVDíZ»»ÕöÖÖÖÖÖÖÖÖÖì»»º»Ö¾ÖÖÖÖÖÖ´ÖÖÖ»þþ÷ÜÖÖÖ»»º»Õª»»®îÿÿ€ê`v \ No newline at end of file --- /dev/null +++ b/tests/crash_vectors/cve-2023-49994.txt @@ -0,0 +1 @@ +"[[-#,- -1-2. r--ª#--O)C--!ÿE-1‹@5-!-V-1-- \ No newline at end of file --- a/src/libespeak-ng/voices.c +++ b/src/libespeak-ng/voices.c @@ -554,6 +554,10 @@ voice_t *LoadVoice(const char *vname, in static char voice_name[40]; // voice name for current_voice_selected static char voice_languages[100]; // list of languages and priorities for current_voice_selected + if ((vname == NULL || vname[0] == 0) && !(control & 8)) { + return NULL; + } + strncpy0(voicename, vname, sizeof(voicename)); if (control & 0x10) { strcpy(buf, vname); @@ -937,14 +941,14 @@ voice_t *LoadVoice(const char *vname, in if (!tone_only) { if (!!(control & 8/*compiling phonemes*/)) { - /* Set by espeak_ng_CompilePhonemeDataPath when it - * calls LoadVoice("", 8) to set up a dummy(?) voice. - * As phontab may not yet exist this avoids the spurious - * error message and guarantees consistent results by - * not actually reading a potentially bogus phontab... - */ - ix = 0; - } else if ((ix = SelectPhonemeTableName(phonemes_name)) < 0) { + /* Set by espeak_ng_CompilePhonemeDataPath when it + * calls LoadVoice("", 8) to set up a dummy(?) voice. + * As phontab may not yet exist this avoids the spurious + * error message and guarantees consistent results by + * not actually reading a potentially bogus phontab... + */ + ix = 0; + } else if ((ix = SelectPhonemeTableName(phonemes_name)) < 0) { fprintf(stderr, "Unknown phoneme table: '%s'\n", phonemes_name); ix = 0; } --- a/src/libespeak-ng/synthdata.c +++ b/src/libespeak-ng/synthdata.c @@ -75,8 +75,15 @@ static espeak_ng_STATUS ReadPhFile(void if ((f_in = fopen(buf, "rb")) == NULL) return create_file_error_context(context, errno, buf); - if (*ptr != NULL) + if (*ptr != NULL) { free(*ptr); + *ptr = NULL; + } + + if (length == 0) { + *ptr = NULL; + return 0; + } if ((*ptr = malloc(length)) == NULL) { fclose(f_in); @@ -86,6 +93,7 @@ static espeak_ng_STATUS ReadPhFile(void int error = errno; fclose(f_in); free(*ptr); + *ptr = NULL; return create_file_error_context(context, error, buf); } @@ -119,9 +127,11 @@ espeak_ng_STATUS LoadPhData(int *srate, // read the version number and sample rate from the first 8 bytes of phondata version = 0; // bytes 0-3, version number rate = 0; // bytes 4-7, sample rate - for (ix = 0; ix < 4; ix++) { - version += (wavefile_data[ix] << (ix*8)); - rate += (wavefile_data[ix+4] << (ix*8)); + if (wavefile_data) { + for (ix = 0; ix < 4; ix++) { + version += (wavefile_data[ix] << (ix*8)); + rate += (wavefile_data[ix+4] << (ix*8)); + } } if (version != version_phdata) --- a/src/libespeak-ng/dictionary.c +++ b/src/libespeak-ng/dictionary.c @@ -1062,6 +1062,9 @@ void SetWordStress(Translator *tr, char static char consonant_types[16] = { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 }; + memset(syllable_weight, 0, sizeof(syllable_weight)); + memset(vowel_length, 0, sizeof(vowel_length)); + stressflags = tr->langopts.stress_flags; if (dictionary_flags != NULL) @@ -3070,6 +3073,7 @@ int RemoveEnding(Translator *tr, char *w *word_end = 'e'; } i = word_end - word; + if (i >= N_WORD_BYTES) i = N_WORD_BYTES-1; if (word_copy != NULL) { memcpy(word_copy, word, i); --- a/src/libespeak-ng/wavegen.c +++ b/src/libespeak-ng/wavegen.c @@ -537,14 +537,14 @@ static void AdvanceParameters() if (wvoice == NULL) return; - int x; + int x = 0; int ix; static int Flutter_ix = 0; // advance the pitch wdata.pitch_ix += wdata.pitch_inc; if ((ix = wdata.pitch_ix>>8) > 127) ix = 127; - x = wdata.pitch_env[ix] * wdata.pitch_range; + if (wdata.pitch_env) x = wdata.pitch_env[ix] * wdata.pitch_range; wdata.pitch = (x>>8) + wdata.pitch_base; @@ -560,7 +560,7 @@ static void AdvanceParameters() if(const_f0) wdata.pitch = (const_f0<<12); - + if (wdata.pitch < 102400) wdata.pitch = 102400; // min pitch, 25 Hz (25 << 12) @@ -1268,6 +1268,9 @@ static int WavegenFill2() static bool resume = false; static int echo_complete = 0; + if (wdata.pitch < 102400) + wdata.pitch = 102400; // min pitch, 25 Hz (25 << 12) + while (out_ptr < out_end) { if (WcmdqUsed() <= 0) { if (echo_complete > 0) { --- a/src/libespeak-ng/translate.c +++ b/src/libespeak-ng/translate.c @@ -2630,6 +2630,7 @@ void TranslateClause(Translator *tr, int if (dict_flags & FLAG_SPELLWORD) { // redo the word, speaking single letters for (pw = word; *pw != ' ';) { + memset(number_buf, 0, sizeof(number_buf)); memset(number_buf, ' ', 9); nx = utf8_in(&c_temp, pw); memcpy(&number_buf[2], pw, nx);