Skip to content

heap-overflow (read) via invalid url #1126

@hgarrereyn

Description

@hgarrereyn

Hi, there is a potential bug in query_string reachable when parsing malformed urls.

This bug was reproduced on b3ed7b1.

Description

Currently there's an assumption in qs_parse/qs_decode etc... that the urls have well-formed url-encoded components, i.e. % is always followed by at least two bytes.

If this condition is violated (which seems possible if an attacker directly sends a malformed URL via a HTTP request), it can lead to a small heap overflow (read) when bytes are unconditionally read beyond the % marker.

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <string>
#include "/fuzz/install/include/crow/query_string.h"

int main(){
    // url ending with %
    std::string raw_url = "https://example.com/asdf?%";
    crow::query_string qs(raw_url);
    auto keys = qs.keys();
    if (!keys.empty()) {
        (void)qs.get(keys[0]); // -> qs_k2v -> qs_strncmp -> heap OOB read
    }
    return 0;
}

stdout


stderr

=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50300000008b at pc 0x556520368a00 bp 0x7ffe8bf68560 sp 0x7ffe8bf68558
READ of size 1 at 0x50300000008b thread T0
    #0 0x5565203689ff in crow::qs_strncmp(char const*, char const*, unsigned long) /fuzz/install/include/crow/query_string.h:79:47
    #1 0x5565203682f5 in crow::qs_k2v(char const*, char* const*, unsigned long, int) /fuzz/install/include/crow/query_string.h:186:14
    #2 0x55652035e354 in crow::query_string::get(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&) const /fuzz/install/include/crow/query_string.h:371:25
    #3 0x55652035d80c in main /fuzz/testcase.cpp:10:18
    #4 0x7fd2df151d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7fd2df151e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #6 0x5565202824f4 in _start (/fuzz/test+0x2d4f4) (BuildId: a80a3f911d45bd8ace72d4518f0f1e468297e699)

0x50300000008b is located 0 bytes after 27-byte region [0x503000000070,0x50300000008b)
allocated by thread T0 here:
    #0 0x55652035b30d in operator new(unsigned long) (/fuzz/test+0x10630d) (BuildId: a80a3f911d45bd8ace72d4518f0f1e468297e699)
    #1 0x7fd2df5bb109 in void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>::_M_construct<char*>(char*, char*, std::forward_iterator_tag) (/lib/x86_64-linux-gnu/libstdc++.so.6+0x14f109) (BuildId: e72c155b714bc42a767ec9c0dd94589110e5b42f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /fuzz/install/include/crow/query_string.h:79:47 in crow::qs_strncmp(char const*, char const*, unsigned long)
Shadow bytes around the buggy address:
  0x502ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x503000000000: fa fa 00 00 00 fa fa fa 00 00 00 03 fa fa 00 00
=>0x503000000080: 00[03]fa fa 00 00 00 00 fa fa fa fa fa fa fa fa
  0x503000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x503000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==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/CrowCpp/Crow /fuzz/src && \
    cd /fuzz/src && \
    git checkout b3ed7b1742c8e7eee8979106f96f643c106ba8a5 && \
    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 dependencies and build tools
RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        libasio-dev \
        cmake \
        ninja-build \
        make \
        pkg-config \
    && rm -rf /var/lib/apt/lists/*

# Configure and install Crow (header-only)
WORKDIR /fuzz/src

RUN cmake -S . -B /fuzz/build \
    -G Ninja \
    -DCMAKE_C_COMPILER=clang_wrapper \
    -DCMAKE_CXX_COMPILER=clang_wrapper++ \
    -DCMAKE_INSTALL_PREFIX=/fuzz/install \
    -DCROW_BUILD_EXAMPLES=OFF \
    -DCROW_BUILD_TESTS=OFF \
    -DCROW_INSTALL=ON

RUN cmake --build /fuzz/build --target install --config Release

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -pthread && /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 -pthread && /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

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions