Bug#1059656: bookworm-pu: package espeak-ng/1.51+dfsg-10+deb12u1

Samuel Thibault sthibault at debian.org
Fri Dec 29 20:54:57 GMT 2023


Package: release.debian.org
Severity: normal
Tags: bookworm
User: release.debian.org at packages.debian.org
Usertags: pu
X-Debbugs-Cc: espeak-ng at packages.debian.org
Control: affects -1 + src:espeak-ng

[ Reason ]
This upload provides fixes for CVEs. They are not a regression over
oldstable.

[ Impact ]
Blind users using the espeak-ng speech synthesis might be at risk when
e.g. reading a webpage that contains the CVE triggers.

[ Tests ]
CVE tests are getting added in the patch.

[ Risks ]
The code is relatively simple, comes from upstream, and has been in
testing since December 24th.

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Change s]in *all* the changes)

The changes fix various use-after-free, unitialized buffers (which lead
to missing \0 terminators), and missing buffer bound checks.
-------------- next part --------------
diff -Nru espeak-ng-1.51+dfsg/debian/changelog espeak-ng-1.51+dfsg/debian/changelog
--- espeak-ng-1.51+dfsg/debian/changelog	2023-01-26 01:09:47.000000000 +0100
+++ espeak-ng-1.51+dfsg/debian/changelog	2023-12-21 01:26:02.000000000 +0100
@@ -1,3 +1,10 @@
+espeak-ng (1.51+dfsg-10+deb12u1) bookworm; urgency=medium
+
+  * patches/CVE: Fix CVE-2023-49990, CVE-2023-49991, CVE-2023-49992,
+    CVE-2023-49993, CVE-2023-49994 (Closes: Bug#1059060)
+
+ -- Samuel Thibault <sthibault at debian.org>  Thu, 21 Dec 2023 01:26:02 +0100
+
 espeak-ng (1.51+dfsg-10) unstable; urgency=medium
 
   * watch: Use API instead of releases page.
diff -Nru espeak-ng-1.51+dfsg/debian/patches/CVE espeak-ng-1.51+dfsg/debian/patches/CVE
--- espeak-ng-1.51+dfsg/debian/patches/CVE	1970-01-01 01:00:00.000000000 +0100
+++ espeak-ng-1.51+dfsg/debian/patches/CVE	2023-12-21 01:26:02.000000000 +0100
@@ -0,0 +1,326 @@
+commit 58f1e0b6a4e6aa55621c6f01118994d01fd6f68c
+Merge: f983e445 e7bcd3cc
+Author: Alexander Epaneshnikov <aarnaarn2 at 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 at ens-lyon.org>
+Date:   Thu Jun 30 00:50:18 2022 +0200
+
+    Fix missing checks for EOF
+
+commit c4c05820c4a47369d5a81e4a506fe7abb2fa7ed6
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 19:24:51 2023 +0300
+
+    tests: add CVE crash vectors
+
+commit e79405772cecf47053116aeaad10e64606292b14
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 23:55:03 2023 +0300
+
+    voices: disallow dummy voice when not compiling
+
+commit 7d4ad3c2ae063cb08bfd606021bc323dfbadaba9
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 21:50:07 2023 +0300
+
+    synthdata: fix empty file load
+
+commit b99f332c576eb49839613a55cfd3e0e1b5487191
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 22:45:15 2023 +0300
+
+    dictionary: limit word length
+
+commit 1a7ecfc2f202438b17e742368f910e6099ce02b7
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 22:50:01 2023 +0300
+
+    readclause: limit embedded punctlist length
+
+commit a5eb246debb51ba328ef399350dfcd5d87782245
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 23:03:16 2023 +0300
+
+    wavegen: fix unitialized pitch
+
+commit 5f7db763e2eff1d8174d2b65a4bbe4b2a85c8a0c
+Author: Yury Popov <git at phoenix.dj>
+Date:   Sat Dec 16 23:17:45 2023 +0300
+
+    translate: fix number_buf initialization
+
+commit e7bcd3cc1599ebb531bb62fc3007d3ce1dade167
+Author: Yury Popov <git at 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);
diff -Nru espeak-ng-1.51+dfsg/debian/patches/series espeak-ng-1.51+dfsg/debian/patches/series
--- espeak-ng-1.51+dfsg/debian/patches/series	2022-09-23 00:13:40.000000000 +0200
+++ espeak-ng-1.51+dfsg/debian/patches/series	2023-12-21 01:26:02.000000000 +0100
@@ -9,3 +9,4 @@
 lang
 long-build-path
 mb-fr
+CVE
diff -Nru espeak-ng-1.51+dfsg/debian/salsa-ci.yml espeak-ng-1.51+dfsg/debian/salsa-ci.yml
--- espeak-ng-1.51+dfsg/debian/salsa-ci.yml	2022-09-23 01:53:17.000000000 +0200
+++ espeak-ng-1.51+dfsg/debian/salsa-ci.yml	2023-12-21 01:26:02.000000000 +0100
@@ -5,6 +5,8 @@
 
 # needs building a host espeak-ng to generate the phoneme databases
 variables:
+  RELEASE: bookworm
+
   SALSA_CI_DISABLE_CROSSBUILD_ARM64: '1'
 
   SALSA_CI_REPROTEST_ENABLE_DIFFOSCOPE: 1


More information about the Pkg-a11y-devel mailing list