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 <aarnaarn2@gmail.com>
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 <samuel.thibault@ens-lyon.org>
Date:   Thu Jun 30 00:50:18 2022 +0200

    Fix missing checks for EOF

commit c4c05820c4a47369d5a81e4a506fe7abb2fa7ed6
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 19:24:51 2023 +0300

    tests: add CVE crash vectors

commit e79405772cecf47053116aeaad10e64606292b14
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 23:55:03 2023 +0300

    voices: disallow dummy voice when not compiling

commit 7d4ad3c2ae063cb08bfd606021bc323dfbadaba9
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 21:50:07 2023 +0300

    synthdata: fix empty file load

commit b99f332c576eb49839613a55cfd3e0e1b5487191
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 22:45:15 2023 +0300

    dictionary: limit word length

commit 1a7ecfc2f202438b17e742368f910e6099ce02b7
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 22:50:01 2023 +0300

    readclause: limit embedded punctlist length

commit a5eb246debb51ba328ef399350dfcd5d87782245
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 23:03:16 2023 +0300

    wavegen: fix unitialized pitch

commit 5f7db763e2eff1d8174d2b65a4bbe4b2a85c8a0c
Author: Yury Popov <git@phoenix.dj>
Date:   Sat Dec 16 23:17:45 2023 +0300

    translate: fix number_buf initialization

commit e7bcd3cc1599ebb531bb62fc3007d3ce1dade167
Author: Yury Popov <git@phoenix.dj>
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);
