Package: release.debian.org
Severity: normal
Tags: bookworm
User: [email protected]
Usertags: pu
X-Debbugs-Cc: [email protected]
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.
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 <[email protected]>  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 <[email protected]>
+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 <[email protected]>
+Date:   Thu Jun 30 00:50:18 2022 +0200
+
+    Fix missing checks for EOF
+
+commit c4c05820c4a47369d5a81e4a506fe7abb2fa7ed6
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 19:24:51 2023 +0300
+
+    tests: add CVE crash vectors
+
+commit e79405772cecf47053116aeaad10e64606292b14
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 23:55:03 2023 +0300
+
+    voices: disallow dummy voice when not compiling
+
+commit 7d4ad3c2ae063cb08bfd606021bc323dfbadaba9
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 21:50:07 2023 +0300
+
+    synthdata: fix empty file load
+
+commit b99f332c576eb49839613a55cfd3e0e1b5487191
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 22:45:15 2023 +0300
+
+    dictionary: limit word length
+
+commit 1a7ecfc2f202438b17e742368f910e6099ce02b7
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 22:50:01 2023 +0300
+
+    readclause: limit embedded punctlist length
+
+commit a5eb246debb51ba328ef399350dfcd5d87782245
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 23:03:16 2023 +0300
+
+    wavegen: fix unitialized pitch
+
+commit 5f7db763e2eff1d8174d2b65a4bbe4b2a85c8a0c
+Author: Yury Popov <[email protected]>
+Date:   Sat Dec 16 23:17:45 2023 +0300
+
+    translate: fix number_buf initialization
+
+commit e7bcd3cc1599ebb531bb62fc3007d3ce1dade167
+Author: Yury Popov <[email protected]>
+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

Reply via email to