From 4ac894930a7be41999cf280042d1004243d564cc Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Sun, 14 Jan 2024 14:16:13 -0800 Subject: [PATCH 1/4] build: add ClusterFuzzLite setup The goal is to enable continuous fuzzing of libuv. There is not a lot of parsing or complex data handling in libuv so I think integrating with OSS-Fuzz is too much. However, ClusterFuzzLite can be used to run fuzzers for a small amount of seconds for each PR to ensure nothing breaks. This commit adds ClusterFuzzLite setup as well as a fuzzer targeting various operations. The fuzzer can be extended, e.g. it would be nice to have more complex FS fuzzing, but I thought I would keep it as is for now and see if you're interested in having fuzzing. Signed-off-by: David Korczynski --- .clusterfuzzlite/Dockerfile | 6 ++ .clusterfuzzlite/README.md | 16 ++++ .clusterfuzzlite/build.sh | 25 +++++ .clusterfuzzlite/libuv_fuzzer.c | 158 ++++++++++++++++++++++++++++++++ .clusterfuzzlite/project.yaml | 1 + .github/workflows/cflite_pr.yml | 30 ++++++ 6 files changed, 236 insertions(+) create mode 100644 .clusterfuzzlite/Dockerfile create mode 100644 .clusterfuzzlite/README.md create mode 100644 .clusterfuzzlite/build.sh create mode 100644 .clusterfuzzlite/libuv_fuzzer.c create mode 100644 .clusterfuzzlite/project.yaml create mode 100644 .github/workflows/cflite_pr.yml diff --git a/.clusterfuzzlite/Dockerfile b/.clusterfuzzlite/Dockerfile new file mode 100644 index 00000000..2f90a6e5 --- /dev/null +++ b/.clusterfuzzlite/Dockerfile @@ -0,0 +1,6 @@ +FROM gcr.io/oss-fuzz-base/base-builder +RUN apt-get update && apt-get install -y make autoconf automake libtool + +COPY . $SRC/libuv +COPY .clusterfuzzlite/build.sh $SRC/build.sh +WORKDIR $SRC/libuv diff --git a/.clusterfuzzlite/README.md b/.clusterfuzzlite/README.md new file mode 100644 index 00000000..a5092fbc --- /dev/null +++ b/.clusterfuzzlite/README.md @@ -0,0 +1,16 @@ +# ClusterFuzzLite set up +This folder contains a fuzzing set for [ClusterFuzzLite](https://google.github.io/clusterfuzzlite). + +To reproduce this set up the way ClusterFuzzLite does it (by way of [OSS-Fuzz](https://github.com/google/oss-fuzz)) you can do: + +```sh +git clone https://github.com/google/oss-fuzz +git clone https://github.com/libuv/libuv +cd libuv + +# Build the fuzzers in .clusterfuzzlite +python3 ../oss-fuzz/infra/helper.py build_fuzzers --external $PWD + +# Run the fuzzer for 10 seconds +python3 ../oss-fuzz/infra/helper.py run_fuzzer --external $PWD libuv_fuzzer -- -max_total_time=10 +``` diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100644 index 00000000..fd20e427 --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,25 @@ +#!/bin/bash -eu +# Supply build instructions +# Use the following environment variables to build the code +# $CXX: c++ compiler +# $CC: c compiler +# CFLAGS: compiler flags for C files +# CXXFLAGS: compiler flags for CPP files +# LIB_FUZZING_ENGINE: linker flag for fuzzing harnesses + +# When run multiple times locally leftover files may be present in shared +# OUT folder. Clear these in case. +rm -f $OUT/test_file* + +sh autogen.sh +./configure +make +make install +find . -name "*.a" + +# Copy all fuzzer executables to $OUT/ +$CC $CFLAGS $LIB_FUZZING_ENGINE \ + $SRC/libuv/.clusterfuzzlite/libuv_fuzzer.c \ + -o $OUT/libuv_fuzzer \ + -I$SRC/libuv/include \ + $SRC/libuv/.libs/libuv.a diff --git a/.clusterfuzzlite/libuv_fuzzer.c b/.clusterfuzzlite/libuv_fuzzer.c new file mode 100644 index 00000000..eb643670 --- /dev/null +++ b/.clusterfuzzlite/libuv_fuzzer.c @@ -0,0 +1,158 @@ +/* Copyright libuv contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "../src/idna.c" +#include "uv.h" + +static uv_loop_t *loop; + +static void dummy_cb(uv_fs_t *req) { (void)req; } + +void test_idna(const uint8_t *data, size_t size) { + char *new_str = (char *)malloc(size + 1); + if (new_str == NULL) { + return; + } + memcpy(new_str, data, size); + new_str[size] = '\0'; + + char de[256]; + uv__idna_toascii(new_str, new_str + size, de, de + 256); + + uv_wtf8_length_as_utf16(new_str); + + free(new_str); +} + +void test_file_ops_1(const uint8_t *data, size_t size) { + int r; + char read_buf[256]; + uv_fs_t open_req1; + uv_fs_t write_req; + uv_buf_t iov; + uv_fs_t close_req; + uv_fs_t read_req; + + unlink("test_file"); + r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, NULL); + uv_fs_req_cleanup(&open_req1); + + iov = uv_buf_init((char *)data, size); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + uv_fs_req_cleanup(&write_req); + + /* Close after writing */ + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + + /* Open again to read */ + r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, NULL); + uv_fs_req_cleanup(&open_req1); + + iov = uv_buf_init(read_buf, sizeof(read_buf)); + uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); + uv_fs_req_cleanup(&read_req); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_req_cleanup(&close_req); + unlink("test_file"); +} + +void test_file_ops_2(const uint8_t *data, size_t size) { + int r; + uv_buf_t iov; + uv_fs_t open_req1; + uv_fs_t write_req; + uv_fs_t copy_req; + uv_fs_t close_req; + + r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + S_IWUSR | S_IRUSR, NULL); + uv_fs_req_cleanup(&open_req1); + + iov = uv_buf_init((char *)data, size); + r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + uv_fs_req_cleanup(&write_req); + + uv_fs_copyfile(NULL, ©_req, "test_file", "test_file2", 0, NULL); + + r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_req_cleanup(&close_req); + + uv_fs_req_cleanup(©_req); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size == 0) { + return 0; + } + uint8_t decider = data[0] % 7; + data++; + size--; + + /* Allocate a null-terminated string that can be used in various fuzz + * operations. + */ + char *new_str = (char *)malloc(size + 1); + if (new_str == NULL) { + return 0; + } + memcpy(new_str, data, size); + new_str[size] = '\0'; + + /* Perform a single fuzz operation and use the fuzz data to decide + * which it should be. + */ + if (decider == 0) { + uv_fs_t req; + loop = uv_default_loop(); + uv_fs_realpath(loop, &req, new_str, dummy_cb); + uv_run(loop, UV_RUN_DEFAULT); + uv_fs_req_cleanup(&req); + } else if (decider == 1) { + struct sockaddr_in addr; + uv_ip4_addr(new_str, 9123, &addr); + } else if (decider == 2) { + struct sockaddr_in6 addr; + uv_ip6_addr(new_str, 9123, &addr); + } else if (decider == 3) { + test_file_ops_1(data, size); + } else if (decider == 4) { + test_file_ops_2(data, size); + } else if (decider == 5) { + test_idna(data, size); + } else { + uv_fs_t req; + loop = uv_default_loop(); + uv_fs_open(NULL, &req, new_str, UV_FS_O_RDONLY, 0, NULL); + uv_run(loop, UV_RUN_DEFAULT); + uv_fs_req_cleanup(&req); + } + + free(new_str); + return 0; +} diff --git a/.clusterfuzzlite/project.yaml b/.clusterfuzzlite/project.yaml new file mode 100644 index 00000000..b455aa39 --- /dev/null +++ b/.clusterfuzzlite/project.yaml @@ -0,0 +1 @@ +language: c diff --git a/.github/workflows/cflite_pr.yml b/.github/workflows/cflite_pr.yml new file mode 100644 index 00000000..5188f8a2 --- /dev/null +++ b/.github/workflows/cflite_pr.yml @@ -0,0 +1,30 @@ +name: ClusterFuzzLite PR fuzzing +on: + workflow_dispatch: + pull_request: + branches: [ v1.x ] +permissions: read-all +jobs: + PR: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: [address] + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + language: c + bad-build-check: false + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 100 + mode: 'code-change' + report-unreproducible-crashes: false + sanitizer: ${{ matrix.sanitizer }} From 744b38a1e1f43f2f9ca92d41bb895dae9b0ab36b Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 30 Jul 2024 03:47:22 -0700 Subject: [PATCH 2/4] address review Signed-off-by: David Korczynski --- .clusterfuzzlite/build.sh | 2 +- .clusterfuzzlite/libuv_fuzzer.c | 40 ++++++++++++++++----------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh index fd20e427..6bfdb12b 100644 --- a/.clusterfuzzlite/build.sh +++ b/.clusterfuzzlite/build.sh @@ -13,7 +13,7 @@ rm -f $OUT/test_file* sh autogen.sh ./configure -make +make -j2 make install find . -name "*.a" diff --git a/.clusterfuzzlite/libuv_fuzzer.c b/.clusterfuzzlite/libuv_fuzzer.c index eb643670..a74cce5e 100644 --- a/.clusterfuzzlite/libuv_fuzzer.c +++ b/.clusterfuzzlite/libuv_fuzzer.c @@ -27,12 +27,12 @@ #include "../src/idna.c" #include "uv.h" -static uv_loop_t *loop; +static uv_loop_t* loop; -static void dummy_cb(uv_fs_t *req) { (void)req; } +static void dummy_cb(uv_fs_t* req) { (void)req; } -void test_idna(const uint8_t *data, size_t size) { - char *new_str = (char *)malloc(size + 1); +void test_idna(const uint8_t* data, size_t size) { + char* new_str = malloc(size + 1); if (new_str == NULL) { return; } @@ -47,8 +47,7 @@ void test_idna(const uint8_t *data, size_t size) { free(new_str); } -void test_file_ops_1(const uint8_t *data, size_t size) { - int r; +void test_file_ops_1(const uint8_t* data, size_t size) { char read_buf[256]; uv_fs_t open_req1; uv_fs_t write_req; @@ -57,19 +56,20 @@ void test_file_ops_1(const uint8_t *data, size_t size) { uv_fs_t read_req; unlink("test_file"); - r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); uv_fs_req_cleanup(&open_req1); - iov = uv_buf_init((char *)data, size); - r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + iov = uv_buf_init((char*)data, size); + uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); uv_fs_req_cleanup(&write_req); /* Close after writing */ - r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_req_cleanup(&close_req); /* Open again to read */ - r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); uv_fs_req_cleanup(&open_req1); @@ -77,36 +77,36 @@ void test_file_ops_1(const uint8_t *data, size_t size) { uv_fs_read(NULL, &read_req, open_req1.result, &iov, 1, -1, NULL); uv_fs_req_cleanup(&read_req); - r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_close(NULL, &close_req, open_req1.result, NULL); uv_fs_req_cleanup(&close_req); unlink("test_file"); } -void test_file_ops_2(const uint8_t *data, size_t size) { - int r; +void test_file_ops_2(const uint8_t* data, size_t size) { uv_buf_t iov; uv_fs_t open_req1; uv_fs_t write_req; uv_fs_t copy_req; uv_fs_t close_req; - r = uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, + uv_fs_open(NULL, &open_req1, "test_file", UV_FS_O_WRONLY | UV_FS_O_CREAT, S_IWUSR | S_IRUSR, NULL); uv_fs_req_cleanup(&open_req1); - iov = uv_buf_init((char *)data, size); - r = uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); + iov = uv_buf_init(data, size); + uv_fs_write(NULL, &write_req, open_req1.result, &iov, 1, -1, NULL); uv_fs_req_cleanup(&write_req); uv_fs_copyfile(NULL, ©_req, "test_file", "test_file2", 0, NULL); + uv_fs_req_cleanup(©_req); - r = uv_fs_close(NULL, &close_req, open_req1.result, NULL); + uv_fs_close(NULL, &close_req, open_req1.result, NULL); uv_fs_req_cleanup(&close_req); uv_fs_req_cleanup(©_req); } -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (size == 0) { return 0; } @@ -117,7 +117,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { /* Allocate a null-terminated string that can be used in various fuzz * operations. */ - char *new_str = (char *)malloc(size + 1); + char* new_str = malloc(size + 1); if (new_str == NULL) { return 0; } From 7cab4d40399b5cd3c8b8b295b8a0d074972a58d6 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 30 Jul 2024 03:51:18 -0700 Subject: [PATCH 3/4] drop braces from single line ifs Signed-off-by: David Korczynski --- .clusterfuzzlite/libuv_fuzzer.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.clusterfuzzlite/libuv_fuzzer.c b/.clusterfuzzlite/libuv_fuzzer.c index a74cce5e..8213e167 100644 --- a/.clusterfuzzlite/libuv_fuzzer.c +++ b/.clusterfuzzlite/libuv_fuzzer.c @@ -33,9 +33,9 @@ static void dummy_cb(uv_fs_t* req) { (void)req; } void test_idna(const uint8_t* data, size_t size) { char* new_str = malloc(size + 1); - if (new_str == NULL) { + if (new_str == NULL) return; - } + memcpy(new_str, data, size); new_str[size] = '\0'; @@ -107,9 +107,9 @@ void test_file_ops_2(const uint8_t* data, size_t size) { } int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size == 0) { + if (size == 0) return 0; - } + uint8_t decider = data[0] % 7; data++; size--; @@ -118,9 +118,9 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { * operations. */ char* new_str = malloc(size + 1); - if (new_str == NULL) { + if (new_str == NULL) return 0; - } + memcpy(new_str, data, size); new_str[size] = '\0'; From a94440b1e42080777249b879ce90747af2c9be7f Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Tue, 30 Jul 2024 03:58:33 -0700 Subject: [PATCH 4/4] adjust test_idna to avoid double allocations Signed-off-by: David Korczynski --- .clusterfuzzlite/libuv_fuzzer.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/.clusterfuzzlite/libuv_fuzzer.c b/.clusterfuzzlite/libuv_fuzzer.c index 8213e167..695bd7af 100644 --- a/.clusterfuzzlite/libuv_fuzzer.c +++ b/.clusterfuzzlite/libuv_fuzzer.c @@ -31,20 +31,11 @@ static uv_loop_t* loop; static void dummy_cb(uv_fs_t* req) { (void)req; } -void test_idna(const uint8_t* data, size_t size) { - char* new_str = malloc(size + 1); - if (new_str == NULL) - return; - - memcpy(new_str, data, size); - new_str[size] = '\0'; - +void test_idna(char *payload, size_t size) { char de[256]; - uv__idna_toascii(new_str, new_str + size, de, de + 256); + uv__idna_toascii(payload, payload + size, de, de + 256); - uv_wtf8_length_as_utf16(new_str); - - free(new_str); + uv_wtf8_length_as_utf16(payload); } void test_file_ops_1(const uint8_t* data, size_t size) { @@ -144,7 +135,7 @@ int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { } else if (decider == 4) { test_file_ops_2(data, size); } else if (decider == 5) { - test_idna(data, size); + test_idna(new_str, size); } else { uv_fs_t req; loop = uv_default_loop();