Skip to content

Stack buffer overflow in parse_line via generate_constell_table when a line exceeds MAX_BUF_SIZE #91

@hgarrereyn

Description

@hgarrereyn

Hi, there is a potential bug in parse_line reachable by invoking generate_constell_table on an untrusted constellation file.

This bug was reproduced on 3614bbc.

Description

What crashes: In generate_constell_table the helper parse_line allocates a fixed-size stack buffer buffer[MAX_BUF_SIZE] and then copies the current input line into it via memcpy(buffer, &data[line_start], line_length); followed by buffer[line_length] = '\0'. When line_length exceeds MAX_BUF_SIZE, both the memcpy and the terminator write overflow the stack buffer.

For normal usage where users are only parsing trusted files like bsc5_constellations.txt, this wouldn't be an issue. However, if users ever try to use this to parse data that is untrusted or containing long lines for some reason, it results in a stack-overflow write.

A safe patch could be to just cap the memcpy to the expected max buf size and ignore trailing data on long lines (which shouldn't impact normal usage, but prevents any sort of bad behavior when processing untrusted data).

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <cstdio>
#include <cstdint>
#include <cstring>
#include <string>
extern "C" {
#include "/fuzz/install/include/core.h"
}
int main(){
    // Single overly long line; library copies line into a fixed-size stack buffer
    std::string s(3000, 'A');
    s.push_back('\n');
    struct Constell *table = nullptr;
    unsigned int n = 0;
    // Triggers stack-buffer-overflow in parse_line -> memcpy(buffer, ..., line_length)
    (void)generate_constell_table(reinterpret_cast<const uint8_t*>(s.data()), s.size(), &table, &n);
    if (table) printf("%u\n", n);
    return 0;
}

stdout


stderr

=================================================================
==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f6df8400820 at pc 0x5634e6529884 bp 0x7ffc25e1abf0 sp 0x7ffc25e1a3b0
WRITE of size 3001 at 0x7f6df8400820 thread T0
    #0 0x5634e6529883 in __asan_memcpy (/fuzz/test+0xc7883) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467)
    #1 0x5634e656c52b in parse_line (/fuzz/test+0x10a52b) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467)
    #2 0x5634e656ca64 in generate_constell_table (/fuzz/test+0x10aa64) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467)
    #3 0x5634e656a6a6 in main /fuzz/testcase.cpp:15:11
    #4 0x7f6df9cb2d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7f6df9cb2e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #6 0x5634e648f3a4 in _start (/fuzz/test+0x2d3a4) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467)

Address 0x7f6df8400820 is located in stack of thread T0 at offset 2080 in frame
    #0 0x5634e656c40f in parse_line (/fuzz/test+0x10a40f) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467)

  This frame has 1 object(s):
    [32, 2080) 'buffer' <== Memory access at offset 2080 overflows this variable
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 (/fuzz/test+0xc7883) (BuildId: 11f7c6b74eb0330fe5d1e9dbdb355a509e07f467) in __asan_memcpy
Shadow bytes around the buggy address:
  0x7f6df8400580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7f6df8400800: 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3
  0x7f6df8400880: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7f6df8400a80: 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
==1==ABORTING

Steps to Reproduce

The crash was triaged with the following Dockerfile:

Dockerfile

# Ubuntu 22.04 with some packages pre-installed
FROM hgarrereyn/stitch_repro_base@sha256:3ae94cdb7bf2660f4941dc523fe48cd2555049f6fb7d17577f5efd32a40fdd2c

RUN git clone https://github.com/da-luce/astroterm /fuzz/src && \
    cd /fuzz/src && \
    git checkout 3614bbcf06fa039b785d8d646f7427da8915d35a && \
    git submodule update --init --remote --recursive

ENV LD_LIBRARY_PATH=/fuzz/install/lib
ENV ASAN_OPTIONS=hard_rss_limit_mb=1024:detect_leaks=0

RUN echo '#!/bin/bash\nexec clang-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper && \
    chmod +x /usr/local/bin/clang_wrapper && \
    echo '#!/bin/bash\nexec clang++-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper++ && \
    chmod +x /usr/local/bin/clang_wrapper++

# Install build dependencies
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      libncurses-dev \
      make \
      ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Environment for compilers
ENV CC=clang_wrapper
ENV CXX=clang_wrapper++

# Build static library manually (avoid data embedding requirements)
WORKDIR /fuzz

RUN mkdir -p /fuzz/build /fuzz/install/lib /fuzz/install/include && \
    cd /fuzz/build && \
    INCS="-I/fuzz/src/include -I/fuzz/src/data" && \
    CFLAGS="-O2 -fPIC $INCS" && \
    $CC $CFLAGS -c /fuzz/src/src/astro.c -o astro.o && \
    $CC $CFLAGS -c /fuzz/src/src/bit.c -o bit.o && \
    $CC $CFLAGS -c /fuzz/src/src/coord.c -o coord.o && \
    $CC $CFLAGS -c /fuzz/src/src/core.c -o core.o && \
    $CC $CFLAGS -c /fuzz/src/src/core_position.c -o core_position.o && \
    $CC $CFLAGS -c /fuzz/src/src/core_render.c -o core_render.o && \
    $CC $CFLAGS -c /fuzz/src/src/drawing.c -o drawing.o && \
    $CC $CFLAGS -c /fuzz/src/src/parse_BSC5.c -o parse_BSC5.o && \
    $CC $CFLAGS -c /fuzz/src/src/stopwatch.c -o stopwatch.o && \
    $CC $CFLAGS -c /fuzz/src/src/term.c -o term.o && \
    # Provide portable strptime implementation as the project does
    $CC $CFLAGS -c /fuzz/src/src/strptime.c -o strptime.o && \
    ar rcs /fuzz/install/lib/libastroterm.a *.o && \
    cp -r /fuzz/src/include/* /fuzz/install/include/

# Done. The library and headers are in /fuzz/install

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lastroterm -lcurses -lm && /fuzz/test

Reproduce

  1. Copy Dockerfile and testcase.cpp into a local folder.
  2. Build the repro image:
docker build . -t repro --platform=linux/amd64
  1. Compile and run the testcase in the image:
docker run \
    -it --rm \
    --platform linux/amd64 \
    --mount type=bind,source="$(pwd)/testcase.cpp",target=/fuzz/testcase.cpp \
    repro \
    bash -c "clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lastroterm -lcurses -lm && /fuzz/test"


Additional Info

This testcase was discovered by STITCH, an autonomous fuzzing system. All reports are reviewed manually (by a human) before submission.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions