Skip to content

Stack-buffer over-read when logging 4-byte WAV chunk IDs in ps_config_wavfile #431

@hgarrereyn

Description

@hgarrereyn

Hi, there's a minor issue with how chunk IDs are logged in ps_config_wavfile.

Specifically, it uses a %s format specifier for a non-null-terminated char[4] containing the ID.

With a specially crafted WAV file, it's possible to hit this path.

A safer alternative would be %c%c%c%c, passing in each of the 4 chars individually.

(found via automated fuzzing)

See the following testcase which reproduces the bug (and associated ASAN report of the overflow):

testcase.cpp

#include <cstdio>
#include <cstdint>
#include <cstring>
extern "C" {
#include "/fuzz/install/include/pocketsphinx.h"
}
// Helper to write a 32-bit little-endian value
static void w32le(FILE* f, uint32_t v){
    unsigned char b[4];
    b[0]=v&0xff;
    b[1]=(v>>8)&0xff;
    b[2]=(v>>16)&0xff;
    b[3]=(v>>24)&0xff;
    fwrite(b,1,4,f);
}

int main(){
    // Initialize config
    ps_config_t *cfg = ps_config_init(NULL);
    if (!cfg) return 0;

    // Create a deliberately malformed WAV-like file that will
    // cause ps_config_wavfile() to log a 4-byte chunk ID.
    const char *path = "/tmp/repro.wav";
    FILE *f = fopen(path, "wb");

    // RIFF header
    fwrite("RIFF",1,4,f);
    w32le(f, 2000);             // arbitrary size
    fwrite("WAVE",1,4,f);

    // fmt chunk (minimal plausible PCM fmt)
    fwrite("fmt ",1,4,f);
    w32le(f, 16);
    unsigned char fmt[16] = {
        1,0,    // PCM
        1,0,    // mono
        0x80,0x3E,0,0,  // sample rate 16000
        0,0x7D,0,0,     // byte rate (deliberately inconsistent values are okay)
        2,0,    // block align
        16,0    // bits per sample
    };
    fwrite(fmt,1,16,f);

    // An unknown chunk with an 8-byte size but no payload to force an error
    // in which the 4-byte ID is logged as a string without NUL termination.
    // This triggers the stack-buffer-overflow in the logging path.
    fwrite("JUNK",1,4,f);  // 4-byte ID with no terminating NUL
    w32le(f, 8);           // claims 8 bytes follow, but we don't write them
    // EOF here
    fclose(f);

    // Now invoke ps_config_wavfile
    FILE *infh = fopen(path, "rb");
    if (!infh) return 0;
    ps_config_wavfile(cfg, infh, path);
    fclose(infh);
    return 0;
}

crash report

==12==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fd7bef010a4 at pc 0x55715d3165e2 bp 0x7ffdb7784340 sp 0x7ffdb7783ac8
READ of size 5 at 0x7fd7bef010a4 thread T0
    #0 0x55715d3165e1 in printf_common(void*, char const*, __va_list_tag*) asan_interceptors.cpp.o
    #1 0x55715d316979 in vsnprintf (/fuzz/workspace/test+0x59979) (BuildId: 79b35302799e787e859ed5ba0c888fa363c5eeb8)
    #2 0x55715d3eee47 in err_msg_system /fuzz/src/src/util/err.c:196:5
    #3 0x55715d3f2c7f in ps_config_wavfile /fuzz/src/src/util/soundfiles.c:209:17
    #4 0x55715d3cd695 in main /fuzz/workspace/test.cpp:56:5
    #5 0x7fd7c0c83d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #6 0x7fd7c0c83e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #7 0x55715d2f2314 in _start (/fuzz/workspace/test+0x35314) (BuildId: 79b35302799e787e859ed5ba0c888fa363c5eeb8)

Address 0x7fd7bef010a4 is located in stack of thread T0 at offset 36 in frame
    #0 0x55715d3f21ff in ps_config_wavfile /fuzz/src/src/util/soundfiles.c:101

  This frame has 3 object(s):
    [32, 36) 'id' (line 102) <== Memory access at offset 36 overflows this variable
    [48, 52) 'intval' (line 103)
    [64, 66) 'shortval' (line 104)
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)
SUMMARY: AddressSanitizer: stack-buffer-overflow asan_interceptors.cpp.o in printf_common(void*, char const*, __va_list_tag*)
Shadow bytes around the buggy address:
  0x7fd7bef00e00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd7bef00e80: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd7bef00f00: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd7bef00f80: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd7bef01000: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
=>0x7fd7bef01080: f1 f1 f1 f1[04]f2 04 f2 02 f3 f3 f3 00 00 00 00
  0x7fd7bef01100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd7bef01180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd7bef01200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd7bef01280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd7bef01300: 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions