Hi, there is a potential bug in the way ZEROCOPY works with ucl_parser_add_file_full.
This bug was reproduced on 31aa36f.
Description
The ZEROCOPY flag requires that input buffers are persisted for the lifetime of the parsed objects:
If you specify #UCL_PARSER_ZEROCOPY you must ensure that the input memory is not freed if an object is in use.
However the existing implementations of ucl_parser_add_file and ucl_parser_add_fd immediately violate this rule by unmapping the memory buffer that was used to read the data.
As a result, trying to use ZEROCOPY with these functions leaves the parser in a precarious state, which can lead to crashes (uaf, segv) later on in random places when it tries to go use those now deallocated pointers.
The testcase below demonstrates how an innocuous looking testcase can hit a random crash later on.
The easiest solution here I think is to just disable ZEROCOPY when inside one of the file parsing functions, since the input buffer is something managed by the library and not exposed to the user.
POC
The following testcase demonstrates the bug:
testcase.cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
extern "C" {
#include "/fuzz/install/include/ucl.h"
}
int main() {
// REQUIRED: UCL_PARSER_ZEROCOPY flag
struct ucl_parser *parser = ucl_parser_new(UCL_PARSER_ZEROCOPY);
// Msgpack with invalid 0xc1 byte
unsigned char msgpack_data[] = {
133, 162, 107, 48, 144, 162, 107, 49, 203, 65, 29, 8, 135, 157, 147, 71,
164, 162, 107, 50, 147, 53, 75, 7, 162, 107, 51, 166, 115, 105, 109, 112,
108, 101, 162, 107, 52, 193 // 193 = 0xc1 (invalid)
};
FILE *f = fopen("/tmp/ucl_crash.bin", "wb");
fwrite(msgpack_data, 1, sizeof(msgpack_data), f);
fclose(f);
// This fails but leaves parser in bad state
ucl_parser_add_file_full(parser, "/tmp/ucl_crash.bin",
0, UCL_DUPLICATE_APPEND, UCL_PARSE_MSGPACK);
// This CRASHES in hash function
const char *json = "{\"a\": [1,2], \"A\": 1, \"alpha\": 1, \"key.with.dots\": true}";
ucl_parser_add_chunk(parser, (const unsigned char *)json, strlen(json));
ucl_parser_free(parser);
return 0;
}
stdout
stderr
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x7f0d80300002 (pc 0x7f0d80c6e95c bp 0x000000000000 sp 0x7ffd66453220 T0)
==1==The signal is caused by a READ memory access.
#0 0x7f0d80c6e95c in _mum_hash_aligned /fuzz/src/src/./mum.h:283:9
#1 0x7f0d80c6e95c in _mum_hash_default /fuzz/src/src/./mum.h:347:12
#2 0x7f0d80c6e95c in mum_hash /fuzz/src/src/./mum.h:437:9
#3 0x7f0d80c6e95c in ucl_hash_func /fuzz/src/src/ucl_hash.c:104:9
#4 0x7f0d80c6c4e7 in kh_resize_ucl_hash_node /fuzz/src/src/ucl_hash.c:116:1
#5 0x7f0d80c68c5f in kh_put_ucl_hash_node /fuzz/src/src/ucl_hash.c:116:1
#6 0x7f0d80c67885 in ucl_hash_insert /fuzz/src/src/ucl_hash.c:332:7
#7 0x7f0d80c7037d in ucl_hash_insert_object /fuzz/src/src/./ucl_internal.h:488:7
#8 0x7f0d80c7037d in ucl_parser_process_object_element /fuzz/src/src/ucl_parser.c:1272:15
#9 0x7f0d80c7b264 in ucl_parse_key /fuzz/src/src/ucl_parser.c:1575:7
#10 0x7f0d80c7b264 in ucl_state_machine /fuzz/src/src/ucl_parser.c:2548:9
#11 0x7f0d80c7427f in ucl_parser_add_chunk_full /fuzz/src/src/ucl_parser.c:3071:12
#12 0x55921bf955b4 in main /fuzz/testcase.cpp:30:5
#13 0x7f0d80703d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#14 0x7f0d80703e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#15 0x55921beba324 in _start (/fuzz/test+0x2c324) (BuildId: e8202fd8774fa5bb11cea0210ce5a96a13d30595)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /fuzz/src/src/./mum.h:283:9 in _mum_hash_aligned
==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/vstakhov/libucl.git /fuzz/src && \
cd /fuzz/src && \
git checkout 31aa36f && \
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++
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool && rm -rf /var/lib/apt/lists/*
RUN cd /fuzz/src && \
./autogen.sh && \
CC=clang_wrapper CXX=clang_wrapper++ ./configure --prefix=/fuzz/install && \
make -j$(nproc) && \
make install
Build Command
clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lucl -lm && /fuzz/test
Reproduce
- Copy
Dockerfile and testcase.cpp into a local folder.
- Build the repro image:
docker build . -t repro --platform=linux/amd64
- 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 -lucl -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.
Hi, there is a potential bug in the way
ZEROCOPYworks withucl_parser_add_file_full.This bug was reproduced on 31aa36f.
Description
The
ZEROCOPYflag requires that input buffers are persisted for the lifetime of the parsed objects:However the existing implementations of
ucl_parser_add_fileanducl_parser_add_fdimmediately violate this rule by unmapping the memory buffer that was used to read the data.As a result, trying to use
ZEROCOPYwith these functions leaves the parser in a precarious state, which can lead to crashes (uaf, segv) later on in random places when it tries to go use those now deallocated pointers.The testcase below demonstrates how an innocuous looking testcase can hit a random crash later on.
The easiest solution here I think is to just disable ZEROCOPY when inside one of the file parsing functions, since the input buffer is something managed by the library and not exposed to the user.
POC
The following testcase demonstrates the bug:
testcase.cpp
stdout
stderr
Steps to Reproduce
The crash was triaged with the following Dockerfile:
Dockerfile
Build Command
clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lucl -lm && /fuzz/testReproduce
Dockerfileandtestcase.cppinto a local folder.docker build . -t repro --platform=linux/amd64docker 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 -lucl -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.