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
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, anducl_hash_search()/ucl_hash_func()dereference the freed key/value memory, leading to invalid reads and a SEGV.Environment
valgrind --tool=memcheck --track-origins=yesucl_add_string_fuzzerv0.9.2(a6b5cac1121103984ee2035081b7467725d68ed7)Reproducer
Artifacts:
ucl_add_string_fuzzer)Repro with OSS-Fuzz helpers:
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:
Likely root cause (from symptoms):
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.kh_resize_ucl_hash_node()rehash, the code hashes/compares a freed key pointer.Impact
Credit: Aldo Ristori
archive.zip