### Summary

While fuzzing `espeak-ng` it was found that the application suffers from stack-buffer-overflow via a crafted file under `RemoveEnding` function, which is located in the `dictionary.c`

### ASAN

```
==3874241==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff404115f at pc 0x000000434862 bp 0x7ffff403e2a0 sp 0x7ffff403da48
READ of size 5 at 0x7ffff404115f thread T1
    #0 0x434861 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) (/espeak-ng/build/src/espeak-ng+0x434861)
    #1 0x434c6a in bcmp (/espeak-ng/build/src/espeak-ng+0x434c6a)
    #2 0x505b29 in RemoveEnding /espeak-ng/src/libespeak-ng/dictionary.c:2963:10
    #3 0x564693 in TranslateWord3 /espeak-ng/src/libespeak-ng/translateword.c:444:17
    #4 0x553871 in TranslateWord /espeak-ng/src/libespeak-ng/translate.c:150:14
    #5 0x55cae2 in TranslateWord2 /espeak-ng/src/libespeak-ng/translate.c:404:11
    #6 0x559e00 in TranslateClause /espeak-ng/src/libespeak-ng/translate.c:1594:17
    #7 0x5405ca in SpeakNextClause /espeak-ng/src/libespeak-ng/synthesize.c:1560:2
    #8 0x57ea73 in Synthesize /espeak-ng/src/libespeak-ng/speech.c:455:2
    #9 0x588e9b in process_espeak_command /espeak-ng/src/libespeak-ng/espeak_command.c:316:3
    #10 0x586304 in say_thread /espeak-ng/src/libespeak-ng/fifo.c:355:6
    #11 0x7ffff7c6d608 in start_thread /build/glibc-e2p3jK/glibc-2.31/nptl/pthread_create.c:477:8
    #12 0x7ffff7b65352 in clone /build/glibc-e2p3jK/glibc-2.31/misc/../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Address 0x7ffff404115f is located in stack of thread T1 at offset 5663 in frame
    #0 0x5542df in TranslateClause /espeak-ng/src/libespeak-ng/translate.c:923

  This frame has 18 object(s):
    [32, 36) 'fc.i.i.i' (line 742)
    [48, 52) 'nc.i.i.i' (line 743)
    [64, 68) 'new_c.i.i' (line 786)
    [80, 84) 'c2.i.i' (line 786)
    [96, 100) 'next2.i' (line 837)
    [112, 116) 'cc' (line 926)
    [128, 132) 'prev_in' (line 929)
    [144, 148) 'prev_out' (line 930)
    [160, 164) 'next_in' (line 932)
    [176, 180) 'charix_top' (line 955)
    [192, 1800) 'charix' (line 957)
    [1936, 5536) 'words' (line 958)
    [5664, 6464) 'sbuf' (line 962) <== Memory access at offset 5663 partially underflows this variable
    [6592, 6596) 'tone' (line 965)
    [6608, 6612) 'next2_in' (line 1274)
    [6624, 6628) 'c_temp' (line 1489)
    [6640, 6790) 'number_buf' (line 1492)
    [6864, 7464) 'num_wtab' (line 1493)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
Thread T1 created by T0 here:
    #0 0x480b9a in pthread_create (/espeak-ng/build/src/espeak-ng+0x480b9a)
    #1 0x585bb6 in fifo_init /espeak-ng/src/libespeak-ng/fifo.c:91:9
    #2 0x57d979 in espeak_ng_InitializeOutput /espeak-ng/src/libespeak-ng/speech.c:277:50

SUMMARY: AddressSanitizer: stack-buffer-overflow (/espeak-ng/build/src/espeak-ng+0x434861) in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long)
Shadow bytes around the buggy address:
  0x10007e8001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e8001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e8001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800210: 00 00 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2
=>0x10007e800220: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2[f2]00 00 00 00
  0x10007e800230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==3874241==ABORTING
```

### Code Snippet

https://github.com/espeak-ng/espeak-ng/blob/master/src/libespeak-ng/dictionary.c#L2963

```C
				const char *p;
				for (i = 0; (p = add_e_additions[i]) != NULL; i++) {
					int len = strlen(p);
					if (memcmp(p, &word_end[1-len], len) == 0) {
						end_flags |= FLAG_SUFX_E_ADDED;
						break;
					}
				}
			}
```

The problem lies in the second argument which is passed to `memcmp`, which is `&word_end[1-len]`. If `len` is greater than or equal to the length of the `word_end` buffer, this will result in an out-of-bounds access successfully resulting to stack buffer overflow.

**Proof-of-concept:** [sof.zip](https://github.com/espeak-ng/espeak-ng/files/15212437/sof.zip)
[SECURITY] stack-buffer-overflow in dictionary.c
This is a GitHub Issue
repo:espeak-ng
owner:espeak-ng
Title : [SECURITY] stack-buffer-overflow in dictionary.c
Issue date:

--- start body ---
### Summary

While fuzzing `espeak-ng` it was found that the application suffers from stack-buffer-overflow via a crafted file under `RemoveEnding` function, which is located in the `dictionary.c`

### ASAN

```
==3874241==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffff404115f at pc 0x000000434862 bp 0x7ffff403e2a0 sp 0x7ffff403da48
READ of size 5 at 0x7ffff404115f thread T1
    #0 0x434861 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) (/espeak-ng/build/src/espeak-ng+0x434861)
    #1 0x434c6a in bcmp (/espeak-ng/build/src/espeak-ng+0x434c6a)
    #2 0x505b29 in RemoveEnding /espeak-ng/src/libespeak-ng/dictionary.c:2963:10
    #3 0x564693 in TranslateWord3 /espeak-ng/src/libespeak-ng/translateword.c:444:17
    #4 0x553871 in TranslateWord /espeak-ng/src/libespeak-ng/translate.c:150:14
    #5 0x55cae2 in TranslateWord2 /espeak-ng/src/libespeak-ng/translate.c:404:11
    #6 0x559e00 in TranslateClause /espeak-ng/src/libespeak-ng/translate.c:1594:17
    #7 0x5405ca in SpeakNextClause /espeak-ng/src/libespeak-ng/synthesize.c:1560:2
    #8 0x57ea73 in Synthesize /espeak-ng/src/libespeak-ng/speech.c:455:2
    #9 0x588e9b in process_espeak_command /espeak-ng/src/libespeak-ng/espeak_command.c:316:3
    #10 0x586304 in say_thread /espeak-ng/src/libespeak-ng/fifo.c:355:6
    #11 0x7ffff7c6d608 in start_thread /build/glibc-e2p3jK/glibc-2.31/nptl/pthread_create.c:477:8
    #12 0x7ffff7b65352 in clone /build/glibc-e2p3jK/glibc-2.31/misc/../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Address 0x7ffff404115f is located in stack of thread T1 at offset 5663 in frame
    #0 0x5542df in TranslateClause /espeak-ng/src/libespeak-ng/translate.c:923

  This frame has 18 object(s):
    [32, 36) 'fc.i.i.i' (line 742)
    [48, 52) 'nc.i.i.i' (line 743)
    [64, 68) 'new_c.i.i' (line 786)
    [80, 84) 'c2.i.i' (line 786)
    [96, 100) 'next2.i' (line 837)
    [112, 116) 'cc' (line 926)
    [128, 132) 'prev_in' (line 929)
    [144, 148) 'prev_out' (line 930)
    [160, 164) 'next_in' (line 932)
    [176, 180) 'charix_top' (line 955)
    [192, 1800) 'charix' (line 957)
    [1936, 5536) 'words' (line 958)
    [5664, 6464) 'sbuf' (line 962) <== Memory access at offset 5663 partially underflows this variable
    [6592, 6596) 'tone' (line 965)
    [6608, 6612) 'next2_in' (line 1274)
    [6624, 6628) 'c_temp' (line 1489)
    [6640, 6790) 'number_buf' (line 1492)
    [6864, 7464) 'num_wtab' (line 1493)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
Thread T1 created by T0 here:
    #0 0x480b9a in pthread_create (/espeak-ng/build/src/espeak-ng+0x480b9a)
    #1 0x585bb6 in fifo_init /espeak-ng/src/libespeak-ng/fifo.c:91:9
    #2 0x57d979 in espeak_ng_InitializeOutput /espeak-ng/src/libespeak-ng/speech.c:277:50

SUMMARY: AddressSanitizer: stack-buffer-overflow (/espeak-ng/build/src/espeak-ng+0x434861) in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long)
Shadow bytes around the buggy address:
  0x10007e8001d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e8001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e8001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800210: 00 00 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2
=>0x10007e800220: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2[f2]00 00 00 00
  0x10007e800230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x10007e800270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==3874241==ABORTING
```

### Code Snippet

https://github.com/espeak-ng/espeak-ng/blob/master/src/libespeak-ng/dictionary.c#L2963

```C
				const char *p;
				for (i = 0; (p = add_e_additions[i]) != NULL; i++) {
					int len = strlen(p);
					if (memcmp(p, &word_end[1-len], len) == 0) {
						end_flags |= FLAG_SUFX_E_ADDED;
						break;
					}
				}
			}
```

The problem lies in the second argument which is passed to `memcmp`, which is `&word_end[1-len]`. If `len` is greater than or equal to the length of the `word_end` buffer, this will result in an out-of-bounds access successfully resulting to stack buffer overflow.

**Proof-of-concept:** [sof.zip](https://github.com/espeak-ng/espeak-ng/files/15212437/sof.zip)
--- end body ---
