Skip to content

config_read_string aborts process via yy_fatal_error → exit() on malformed input #292

@tzh21

Description

@tzh21

Summary

When config_read_string() is given malformed input that the flex-generated scanner cannot recover from, libconfig terminates the entire calling process via yy_fatal_error()exit(YY_EXIT_FAILURE). The library should return an error code instead so the application can handle it.

This was previously reported in #56 (2016), where the failure was attributed to "an I/O error on the file or stream you're trying to read". The 12-byte string-only reproducer below shows the failure has nothing to do with I/O — it is a parser-state failure on adversarial input.

Environment

  • libconfig: v1.8.2 (latest stable)
  • Compiler: clang 22.1.6 (Homebrew LLVM)
  • Platform: macOS 14 / Apple Silicon
  • Build: cmake -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTS=OFF -DBUILD_EXAMPLES=OFF with -fsanitize=fuzzer-no-link,address

Minimal reproducer (12 bytes)

#include <libconfig.h>
int main(void) {
    config_t cfg;
    config_init(&cfg);
    config_read_string(&cfg, "@include \".\"");   /* 12 bytes */
    /* never reached */
    config_destroy(&cfg);
    return 0;
}

Build & run:

$ clang repro.c -lconfig -o repro && ./repro
input in flex scanner failed
$ echo $?
1

The process is terminated before config_read_string() returns. The caller has no way to detect or recover from this — config_init() succeeded, the input is a pure in-memory string (no FILE*, no fd, no path), and yet the process is gone.

Stack trace (under ASan)

SUMMARY: libFuzzer: fuzz target exited
    #5 yy_fatal_error                scanner.c:2463
    #6 yy_get_next_buffer             scanner.c:1903
    #7 libconfig_yylex                scanner.c:1742
    #8 libconfig_yyparse               grammar.c:1231
    #9 __config_read                  libconfig.c:582

Impact

Any application that feeds libconfig untrusted (or even partially attacker-influenced) configuration data — for example, services that accept user-supplied config snippets, IPC handlers, file watchers — can be silently DoS'd by a 12-byte input. The application gets no opportunity to log the failure or fall back; the exit() is unconditional.

Suggested fix

yy_fatal_error() is auto-generated by flex but its body can be overridden with %option yyfatalerror=... or by defining YY_FATAL_ERROR(msg) in the scanner prologue. A library-grade behavior would set a state flag, longjmp back to the parse-entry frame, or store the message in config_error_text / config_error_line so the existing config_read* API can return CONFIG_FALSE instead of crashing.

Discovered by 12-hour libFuzzer (fork mode) coverage-guided fuzzing of config_read_string. After dedup, 226 distinct fuzzer-saved crashes all reduced to this one root cause.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions