- 
                Notifications
    
You must be signed in to change notification settings  - Fork 316
 
Description
Hi, there is a potential bug in ly_realloc reachable by providing size=0.
This bug was reproduced on efe43e3.
Description
ly_realloc is a wrapper around realloc which tries to reallocate, and then frees the original pointer if it failed:
    new_mem = realloc(ptr, size);
    if (!new_mem) {
        free(ptr);
    }On some platforms/allocators, realloc(ptr, 0) frees ptr and returns NULL (C/POSIX-permitted behavior). In that case, ly_realloc calls free(ptr) again, causing a double free. The provided testcase sets new_size=0, triggering this exact path. AddressSanitizer confirms the first free happens inside realloc and the second in ly_realloc.
Internally, it seems like ly_realloc is used in quite a few places, often with sizes derived from user controlled data. It seems possible that one of those paths could hit it with new_size=0, thus triggering this double free.
Since the double-free happens back-to-back, it seems likely that modern libc assertions would catch this and abort the process, thus impact is likely fairly low.
POC
The following testcase demonstrates the bug:
testcase.cpp
#include <cstdlib>
#include <cstddef>
extern "C" void *ly_realloc(void *ptr, size_t size);
int main() {
    void *p = std::malloc(1024);
    if (!p) return 0;
    // POSIX permits realloc(p,0) to free p and return NULL.
    // ly_realloc frees again on NULL, causing double-free.
    (void)ly_realloc(p, 0);
    return 0;
}
stdout
=================================================================
==1==ERROR: AddressSanitizer: attempting double-free on 0x519000000080 in thread T0:
    #0 0x558a56a35616 in free (/fuzz/test+0x118616) (BuildId: 3cdbcee87f4049a203614f4880bbaf914dd8e139)
    #1 0x558a56a74754 in ly_realloc /fuzz/src/src/ly_common.c:98:9
    #2 0x558a56a74657 in main /fuzz/testcase.cpp:11:11
    #3 0x7fd8f7ff9d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #4 0x7fd8f7ff9e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #5 0x558a56999514 in _start (/fuzz/test+0x7c514) (BuildId: 3cdbcee87f4049a203614f4880bbaf914dd8e139)
0x519000000080 is located 0 bytes inside of 1024-byte region [0x519000000080,0x519000000480)
freed by thread T0 here:
    #0 0x558a56a35ce5 in realloc (/fuzz/test+0x118ce5) (BuildId: 3cdbcee87f4049a203614f4880bbaf914dd8e139)
    #1 0x558a56a7473c in ly_realloc /fuzz/src/src/ly_common.c:96:15
    #2 0x558a56a74657 in main /fuzz/testcase.cpp:11:11
    #3 0x7fd8f7ff9d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
    #0 0x558a56a358be in malloc (/fuzz/test+0x1188be) (BuildId: 3cdbcee87f4049a203614f4880bbaf914dd8e139)
    #1 0x558a56a74628 in main /fuzz/testcase.cpp:7:15
    #2 0x7fd8f7ff9d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: double-free (/fuzz/test+0x118616) (BuildId: 3cdbcee87f4049a203614f4880bbaf914dd8e139) in free
==1==ABORTING
stderr
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/CESNET/libyang /fuzz/src && \
    cd /fuzz/src && \
    git checkout efe43e3790822a3dc64d7d28db935d03fff8b81f && \
    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++
# Install build dependencies
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    build-essential \
    pkg-config \
    cmake \
    ninja-build \
    libpcre2-dev \
    libxxhash-dev \
 && rm -rf /var/lib/apt/lists/*
# Configure, build, and install libyang (static library preferred)
WORKDIR /work/build
ENV CC=clang_wrapper CXX=clang_wrapper++
RUN cmake -G Ninja /fuzz/src \
    -DCMAKE_C_COMPILER=clang_wrapper \
    -DCMAKE_CXX_COMPILER=clang_wrapper++ \
    -DCMAKE_INSTALL_PREFIX=/fuzz/install \
    -DBUILD_SHARED_LIBS=OFF \
    -DENABLE_TOOLS=OFF \
    -DENABLE_TESTS=OFF \
    -DENABLE_YANGLINT_INTERACTIVE=OFF \
 && ninja -j"$(nproc)" && ninja installBuild Command
clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lyang -lpcre2-8 -lxxhash -lpthread -ldl -lm && /fuzz/testReproduce
- Copy 
Dockerfileandtestcase.cppinto 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 -lyang -lpcre2-8 -lxxhash -lpthread -ldl -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.