Skip to content

Security Issue: Use-After-Free (and secondary UUM) in libucl hash lookup/insert #347

@Kaldreic

Description

@Kaldreic

Summary

A crafted input triggers a use-after-free in libucl’s hash table during parsing. After freeing an object (or its key string) in ucl_object_free_internal(), the parser proceeds to perform a lookup/insert, and ucl_hash_search() / ucl_hash_func() dereference the freed key/value memory, leading to invalid reads and a SEGV.

Environment

  • Tooling: valgrind --tool=memcheck --track-origins=yes
  • Target: ucl_add_string_fuzzer
  • OS: Ubuntu 20.04.6 LTS focal x86_64
  • libucl version: v0.9.2 (a6b5cac1121103984ee2035081b7467725d68ed7)

Reproducer

Artifacts:

  • Fuzzer binary (ucl_add_string_fuzzer)
  • Single testcase

Repro with OSS-Fuzz helpers:

git clone https://github.com/google/oss-fuzz.git
cd oss-fuzz

python3 infra/helper.py build_image libucl
python3 infra/helper.py build_fuzzers --sanitizer=none libucl
python3 infra/helper.py shell libucl

apt update && apt install -y valgrind
ulimit -n 65535
valgrind --tool=memcheck --track-origins=yes /out/ucl_add_string_fuzzer /path/to/poc

Valgrind Trace (top frames):

==398== Use of uninitialised value of size 8
==398==    at 0x2172BD: ucl_hash_search (in /out/ucl_add_string_fuzzer)
==398==    by 0x1FAFD0: ucl_parser_process_object_element (in /out/ucl_add_string_fuzzer)
==398==    by 0x1FDA9B: ucl_state_machine (in /out/ucl_add_string_fuzzer)
==398==    by 0x1FCCE9: ucl_parser_add_chunk_full (in /out/ucl_add_string_fuzzer)

==409== Invalid read of size 8
==409==    at 0x2171DB: ucl_hash_search (in /out/ucl_add_string_fuzzer)
==409==    by 0x1FAFD0: ucl_parser_process_object_element (in /out/ucl_add_string_fuzzer)
==409==    by 0x1FDA9B: ucl_state_machine (in /out/ucl_add_string_fuzzer)
==409==    by 0x1FCCE9: ucl_parser_add_chunk_full (in /out/ucl_add_string_fuzzer)

UBSan Trace:

==398==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0000002172bd bp 0x001ffefff150 sp 0x001ffefff0b0 T398)
==398==The signal is caused by a READ memory access.
==398==Hint: address points to the zero page.
==400== Warning: invalid file descriptor 65523 in syscall close()
    #0 0x0000002172bd in ucl_hash_search /src/libucl/src/ucl_hash.c
    #1 0x0000001fafd0 in ucl_hash_search_obj /src/libucl/src/./ucl_internal.h:465:32
    #2 0x0000001fafd0 in ucl_parser_process_object_element /src/libucl/src/ucl_parser.c:1253:11
    #3 0x0000001fda9b in ucl_parse_key /src/libucl/src/ucl_parser.c:1566:7
    #4 0x0000001fda9b in ucl_state_machine /src/libucl/src/ucl_parser.c:2539:9
    #5 0x0000001fcce9 in ucl_parser_add_chunk_full /src/libucl/src/ucl_parser.c:3062:12

Likely root cause (from symptoms):

  • Lifetime / order-of-operations bug on duplicate-key handling or error paths
  • An existing ucl_object_t (or its key string) is freed while still referenced by the table, and a lookup/insert immediately follows, using the stale pointer.
  • During kh_resize_ucl_hash_node() rehash, the code hashes/compares a freed key pointer.

Impact

  • Denial of service (process crash) when parsing untrusted UCL input.
  • Potential for information disclosure (reading freed memory) if the freed region is reused before comparison; broader corruption not demonstrated in this trace but lifecycle makes it risky.

Credit: Aldo Ristori

archive.zip

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