diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 0000000..a2763c8 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,5 @@ +FROM gcr.io/oss-fuzz-base/base-builder:v1 +RUN apt-get update && apt-get install -y make autoconf automake libtool pkg-config +COPY . $SRC/urlparse +WORKDIR urlparse +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..6f33867 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash -eu + +autoreconf -i +./configure --disable-dependency-tracking +make -j$(nproc) check TESTS="" + +$CXX $CXXFLAGS -std=c++17 -I. \ + fuzz/parser.cc -o $OUT/parser \ + $LIB_FUZZING_ENGINE .libs/liburlparse.a .libs/libhttp-parser.a + +zip -j $OUT/parser_seed_corpus.zip fuzz/corpus/parser/* diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 0000000..b478801 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c++ diff --git a/.github/workflows/cflite_batch.yaml b/.github/workflows/cflite_batch.yaml new file mode 100644 index 0000000..697a5a3 --- /dev/null +++ b/.github/workflows/cflite_batch.yaml @@ -0,0 +1,36 @@ +name: ClusterFuzzLite batch fuzzing +on: + schedule: + - cron: '0 0/6 * * *' +permissions: read-all +jobs: + BatchFuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: + - address + - undefined + - memory + steps: + - name: LLVM workaround + run: | + # https://github.com/actions/runner-images/issues/9491#issuecomment-1989718917 + # Asan in llvm 14 provided in ubuntu 22.04 is incompatible with + # high-entropy ASLR in much newer kernels that GitHub runners are + # using leading to random crashes: https://reviews.llvm.org/D148280 + sudo sysctl vm.mmap_rnd_bits=28 + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 3600 + mode: 'batch' + sanitizer: ${{ matrix.sanitizer }} diff --git a/.github/workflows/cflite_cron.yaml b/.github/workflows/cflite_cron.yaml new file mode 100644 index 0000000..d46a4da --- /dev/null +++ b/.github/workflows/cflite_cron.yaml @@ -0,0 +1,19 @@ +name: ClusterFuzzLite cron tasks +on: + schedule: + - cron: '0 0 * * *' +permissions: read-all +jobs: + Pruning: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + - name: Run Fuzzers + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 600 + mode: 'prune' diff --git a/fuzz/corpus/parser/connect b/fuzz/corpus/parser/connect new file mode 100644 index 0000000..cc56d45 --- /dev/null +++ b/fuzz/corpus/parser/connect @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-:12345 \ No newline at end of file diff --git a/fuzz/corpus/parser/connect-ipv6 b/fuzz/corpus/parser/connect-ipv6 new file mode 100644 index 0000000..f5cd1c4 --- /dev/null +++ b/fuzz/corpus/parser/connect-ipv6 @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@[abcdefABCDEF0123456789:.%abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%.-_~]:12345 \ No newline at end of file diff --git a/fuzz/corpus/parser/example b/fuzz/corpus/parser/example new file mode 100644 index 0000000..a364175 --- /dev/null +++ b/fuzz/corpus/parser/example @@ -0,0 +1 @@ +https://example.com/ \ No newline at end of file diff --git a/fuzz/corpus/parser/ipv6 b/fuzz/corpus/parser/ipv6 new file mode 100644 index 0000000..4d03e88 --- /dev/null +++ b/fuzz/corpus/parser/ipv6 @@ -0,0 +1 @@ +https://abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@[abcdefABCDEF0123456789:.%abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%.-_~]:12345/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/"<>[\]^`{|}?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/?"<>[\]^`{|}#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/?"<>[\]^`{|} \ No newline at end of file diff --git a/fuzz/corpus/parser/long b/fuzz/corpus/parser/long new file mode 100644 index 0000000..a9026ed --- /dev/null +++ b/fuzz/corpus/parser/long @@ -0,0 +1 @@ +https://abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-:12345/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/"<>[\]^`{|}?abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/?"<>[\]^`{|}#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()*+,;=:@/?"<>[\]^`{|} \ No newline at end of file diff --git a/fuzz/parser.cc b/fuzz/parser.cc new file mode 100644 index 0000000..57e5256 --- /dev/null +++ b/fuzz/parser.cc @@ -0,0 +1,72 @@ +#include +#include +#include + +#include "urlparse.h" +#include "http-parser/http_parser.h" + +namespace { +/* Copied from https://github.com/nodejs/http-parser */ +void dump_url(const uint8_t *url, const urlparse_url *u) { + size_t i; + + fprintf(stderr, "\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); + for (i = 0; i < URLPARSE_MAX; i++) { + if ((u->field_set & (1 << i)) == 0) { + fprintf(stderr, "\tfield_data[%zu]: unset\n", i); + continue; + } + + fprintf(stderr, "\tfield_data[%zu]: off: %u len: %u part: \"%.*s\"\n", i, + u->field_data[i].off, u->field_data[i].len, u->field_data[i].len, + url + u->field_data[i].off); + } +} +} // namespace + +namespace { +int fuzz(const uint8_t *data, size_t size, int is_connect) { + urlparse_url u; + struct http_parser_url hu; + int rv1, rv2; + + rv1 = urlparse_parse_url(reinterpret_cast(data), size, + is_connect, &u); + memset(&hu, 0, sizeof(hu)); + rv2 = http_parser_parse_url(reinterpret_cast(data), size, + is_connect, &hu); + if (rv2 != 0) { + rv2 = URLPARSE_ERR_PARSE; + } + + if (rv1 != rv2) { + fprintf( + stderr, + "urlparse_parse_url(%d) and http_parser_parse_url(%d) disagree with " + "is_connect = %d\n", + rv1, rv2, is_connect); + return -1; + } + + if (rv1 == 0 && memcmp(&hu, &u, sizeof(u)) != 0) { + fprintf(stderr, "is_connect = %d\n", is_connect); + fprintf(stderr, "target http_parser_url:\n"); + dump_url(data, reinterpret_cast(&hu)); + fprintf(stderr, "result urlparse_url:\n"); + dump_url(data, &u); + + return -1; + } + + return 0; +} +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (fuzz(data, size, /* is_connect = */ 0) != 0 || + fuzz(data, size, /* is_connect = */ 1) != 0) { + abort(); + } + + return 0; +}