Skip to content

ZEROCOPY does not work with file parsing functions #352

@hgarrereyn

Description

@hgarrereyn

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

  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 -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.

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