From 9cf7f42f7569dd60dd3849f3c98f1a538e7d12d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:38:06 +0000 Subject: [PATCH 1/4] Initial plan From 25b8316454c3f339fd34deb1b0d2ba9527b6f72e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:44:24 +0000 Subject: [PATCH 2/4] Add OSS-Fuzz/ClusterFuzzLite integration Co-authored-by: qzhuyan <200020+qzhuyan@users.noreply.github.com> --- .github/workflows/clusterfuzzlite.yml | 108 ++++++++++++++++++++++++++ fuzz/.gitignore | 11 +++ fuzz/README.md | 96 +++++++++++++++++++++++ fuzz/build.sh | 56 +++++++++++++ fuzz/corpus/fuzz_config/seed1 | Bin 0 -> 8 bytes fuzz/corpus/fuzz_config/seed2 | Bin 0 -> 8 bytes fuzz/corpus/fuzz_config/seed3 | Bin 0 -> 8 bytes fuzz/fuzz_config.c | 88 +++++++++++++++++++++ fuzz/project.yaml | 11 +++ 9 files changed, 370 insertions(+) create mode 100644 .github/workflows/clusterfuzzlite.yml create mode 100644 fuzz/.gitignore create mode 100644 fuzz/README.md create mode 100755 fuzz/build.sh create mode 100644 fuzz/corpus/fuzz_config/seed1 create mode 100644 fuzz/corpus/fuzz_config/seed2 create mode 100644 fuzz/corpus/fuzz_config/seed3 create mode 100644 fuzz/fuzz_config.c create mode 100644 fuzz/project.yaml diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml new file mode 100644 index 00000000..52b3814a --- /dev/null +++ b/.github/workflows/clusterfuzzlite.yml @@ -0,0 +1,108 @@ +name: ClusterFuzzLite +on: + pull_request: + paths: + - 'c_src/**' + - 'fuzz/**' + - 'CMakeLists.txt' + push: + branches: + - main + schedule: + # Run at 00:00 UTC daily + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: read-all + +jobs: + Fuzzing: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sanitizer: + - address + - undefined + - memory + steps: + - name: Build Fuzzers (${{ matrix.sanitizer }}) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: ${{ matrix.sanitizer }} + language: c + build-integration-path: ./fuzz/build.sh + + - name: Run Fuzzers (${{ matrix.sanitizer }}) + id: run + uses: google/clusterfuzzlite/actions/run_fuzzers@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fuzz-seconds: 300 + mode: 'code-change' + sanitizer: ${{ matrix.sanitizer }} + output-sarif: true + + - name: Upload Crash + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: failure() && steps.run.outcome == 'failure' + with: + name: ${{ matrix.sanitizer }}-artifacts + path: ./out/artifacts + + - name: Upload SARIF + if: always() && steps.run.outcome == 'success' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ./out/results.sarif + category: clusterfuzzlite-${{ matrix.sanitizer }} + + Batch: + 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 + build-integration-path: ./fuzz/build.sh + + - 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 }} + + - name: Upload Crash + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + if: failure() && steps.run.outcome == 'failure' + with: + name: ${{ matrix.sanitizer }}-batch-artifacts + path: ./out/artifacts + + Prune: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers (address) + id: build + uses: google/clusterfuzzlite/actions/build_fuzzers@v1 + with: + sanitizer: address + language: c + build-integration-path: ./fuzz/build.sh + + - name: Minimize Corpus + id: prune + uses: google/clusterfuzzlite/actions/prune@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..006a4532 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,11 @@ +# Fuzz artifacts +*.o +*.a +crash-* +leak-* +timeout-* +oom-* + +# Build output +build/ +out/ diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..e4ccf875 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,96 @@ +# Fuzzing for Quicer + +This directory contains fuzz targets and infrastructure for continuous fuzzing of the Quicer library using [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/). + +## Overview + +Quicer uses ClusterFuzzLite to run fuzz tests automatically via GitHub Actions. The fuzzing workflow runs: +- On pull requests that modify C source code or fuzz targets +- On pushes to the main branch +- Daily via scheduled runs +- On manual workflow dispatch + +## Fuzz Targets + +### fuzz_config.c +Tests QUIC configuration handling and parameter setting through the msquic library. + +## Running Locally + +### Prerequisites +- Docker +- ClusterFuzzLite (via Docker) + +### Build and Run + +To build fuzz targets locally: + +```bash +# Install dependencies +sudo apt-get update +sudo apt-get install -y build-essential cmake git curl wget clang + +# Build the project normally first +./build.sh v2.3.8 + +# Build fuzz targets with sanitizers (requires special flags) +export CC=clang +export CXX=clang++ +export CFLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,fuzzer-no-link" +export CXXFLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,fuzzer-no-link" +export LIB_FUZZING_ENGINE="-fsanitize=fuzzer" +export OUT=/tmp/fuzz_out +export SRC=/home/runner/work/quic + +mkdir -p $OUT +./fuzz/build.sh + +# Run a fuzz target +/tmp/fuzz_out/fuzz_config +``` + +### Using Docker + +```bash +# Use ClusterFuzzLite's Docker container +docker run --rm -ti -v $(pwd):/src gcr.io/oss-fuzz-base/base-builder \ + bash -c "cd /src && ./fuzz/build.sh" +``` + +## Seed Corpus + +Seed corpus files are stored in `fuzz/corpus//` directories. These provide initial inputs for the fuzzer to start from. + +## GitHub Actions Integration + +The fuzzing workflow is defined in `.github/workflows/clusterfuzzlite.yml` and includes: + +1. **Fuzzing Job**: Runs on code changes with multiple sanitizers (address, undefined, memory) +2. **Batch Job**: Runs longer fuzzing sessions (1 hour) on a schedule +3. **Prune Job**: Minimizes the corpus to remove redundant test cases + +## Sanitizers + +The fuzz tests run with multiple sanitizers: +- **AddressSanitizer (ASan)**: Detects memory errors like buffer overflows and use-after-free +- **UndefinedBehaviorSanitizer (UBSan)**: Detects undefined behavior like integer overflow +- **MemorySanitizer (MSan)**: Detects uninitialized memory reads + +## Crash Artifacts + +When fuzzing finds a crash, the artifacts are uploaded as GitHub Actions artifacts for investigation. + +## Contributing + +To add new fuzz targets: + +1. Create a new `.c` file in the `fuzz/` directory +2. Implement the `LLVMFuzzerTestOneInput` function +3. Add seed corpus files to `fuzz/corpus//` +4. The build script will automatically pick up the new target + +## References + +- [ClusterFuzzLite Documentation](https://google.github.io/clusterfuzzlite/) +- [libFuzzer Tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) +- [OSS-Fuzz](https://google.github.io/oss-fuzz/) diff --git a/fuzz/build.sh b/fuzz/build.sh new file mode 100755 index 00000000..eb6d01ac --- /dev/null +++ b/fuzz/build.sh @@ -0,0 +1,56 @@ +#!/bin/bash -eu +# Copyright 2024 EMQ Technologies Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build script for OSS-Fuzz +# This script builds the fuzz targets for quicer + +set -x + +# Get msquic dependency +./get-msquic.sh v2.3.8 + +# Build msquic library +cmake -B c_build \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DQUIC_BUILD_TEST=OFF \ + -DQUIC_BUILD_TOOLS=OFF \ + -DQUIC_BUILD_PERF=OFF \ + -DQUIC_TLS_SECRETS_SUPPORT=ON + +make -C c_build -j$(nproc) inc platform core warnings logging + +# Build fuzz targets +for fuzz_target in fuzz/*.c; do + target_name=$(basename "$fuzz_target" .c) + + $CC $CFLAGS -c "$fuzz_target" -o "/tmp/${target_name}.o" \ + -I${SRC}/quicer/msquic/src/inc \ + -I${SRC}/quicer/c_build + + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE "/tmp/${target_name}.o" \ + -o "$OUT/${target_name}" \ + ${SRC}/quicer/c_build/bin/RelWithDebInfo/libmsquic.a \ + -lpthread -ldl -lm +done + +# Copy corpus if it exists +if [ -d "fuzz/corpus" ]; then + for fuzz_target in fuzz/*.c; do + target_name=$(basename "$fuzz_target" .c) + if [ -d "fuzz/corpus/${target_name}" ]; then + cp -r "fuzz/corpus/${target_name}" "$OUT/" + fi + done +fi diff --git a/fuzz/corpus/fuzz_config/seed1 b/fuzz/corpus/fuzz_config/seed1 new file mode 100644 index 0000000000000000000000000000000000000000..abfe96a7aa4a41dcfd4791a2c56c900da29e2bdc GIT binary patch literal 8 PcmZQzWME=oU|<9Q02%-V literal 0 HcmV?d00001 diff --git a/fuzz/corpus/fuzz_config/seed2 b/fuzz/corpus/fuzz_config/seed2 new file mode 100644 index 0000000000000000000000000000000000000000..5e890c758e333f63f27fb53a2c65b90d06ff3f48 GIT binary patch literal 8 PcmezWpMimqL4W}O4;=y! literal 0 HcmV?d00001 diff --git a/fuzz/corpus/fuzz_config/seed3 b/fuzz/corpus/fuzz_config/seed3 new file mode 100644 index 0000000000000000000000000000000000000000..5059ab7dd2b66b36070e0547f51c007c5a78784f GIT binary patch literal 8 PcmWe&U}a!pU| +#include +#include +#include + +// Fuzzing target for QUIC configuration handling +// This tests msquic library's configuration parsing and parameter setting + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + // Initialize QUIC library + const QUIC_API_TABLE* MsQuic = NULL; + QUIC_REGISTRATION_CONFIG RegConfig = { "fuzz", QUIC_EXECUTION_PROFILE_LOW_LATENCY }; + HQUIC Registration = NULL; + + if (Size < 4 || Size > 1024) { + return 0; + } + + // Initialize the QUIC library + if (QUIC_FAILED(MsQuicOpen2(&MsQuic))) { + return 0; + } + + // Create a registration + if (QUIC_FAILED(MsQuic->RegistrationOpen(&RegConfig, &Registration))) { + MsQuicClose(MsQuic); + return 0; + } + + // Test configuration with fuzzer input + QUIC_SETTINGS Settings = {0}; + Settings.IsSet.IdleTimeoutMs = TRUE; + Settings.IdleTimeoutMs = (Data[0] << 8) | Data[1]; + + if (Size >= 4) { + Settings.IsSet.MaxBytesPerKey = TRUE; + Settings.MaxBytesPerKey = ((uint64_t)Data[2] << 8) | Data[3]; + } + + if (Size >= 6) { + Settings.IsSet.ServerResumptionLevel = TRUE; + Settings.ServerResumptionLevel = Data[4] % 3; // Valid values: 0, 1, 2 + } + + if (Size >= 8) { + Settings.IsSet.PeerBidiStreamCount = TRUE; + Settings.PeerBidiStreamCount = (Data[6] << 8) | Data[7]; + } + + // Apply settings to a configuration + QUIC_CREDENTIAL_CONFIG CredConfig; + memset(&CredConfig, 0, sizeof(CredConfig)); + CredConfig.Type = QUIC_CREDENTIAL_TYPE_NONE; + CredConfig.Flags = QUIC_CREDENTIAL_FLAG_CLIENT; + + HQUIC Configuration = NULL; + QUIC_BUFFER Alpn = { sizeof("fuzz") - 1, (uint8_t*)"fuzz" }; + + if (QUIC_SUCCEEDED(MsQuic->ConfigurationOpen(Registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &Configuration))) { + // Load credentials + MsQuic->ConfigurationLoadCredential(Configuration, &CredConfig); + + // Clean up configuration + MsQuic->ConfigurationClose(Configuration); + } + + // Clean up + MsQuic->RegistrationClose(Registration); + MsQuicClose(MsQuic); + + return 0; +} diff --git a/fuzz/project.yaml b/fuzz/project.yaml new file mode 100644 index 00000000..eea4e32b --- /dev/null +++ b/fuzz/project.yaml @@ -0,0 +1,11 @@ +homepage: "https://github.com/emqx/quic" +language: c +primary_contact: "emqx-support@emqx.io" +main_repo: "https://github.com/emqx/quic" +file_github_issue: true +sanitizers: + - address + - undefined + - memory +architectures: + - x86_64 From ec7ba0911ce699db861462c7df33e956163ac044 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:45:58 +0000 Subject: [PATCH 3/4] Fix YAML line length and improve fuzz build script Co-authored-by: qzhuyan <200020+qzhuyan@users.noreply.github.com> --- .github/workflows/clusterfuzzlite.yml | 6 ++++-- fuzz/build.sh | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/clusterfuzzlite.yml b/.github/workflows/clusterfuzzlite.yml index 52b3814a..005d6ed6 100644 --- a/.github/workflows/clusterfuzzlite.yml +++ b/.github/workflows/clusterfuzzlite.yml @@ -45,7 +45,8 @@ jobs: output-sarif: true - name: Upload Crash - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + # v4.3.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 if: failure() && steps.run.outcome == 'failure' with: name: ${{ matrix.sanitizer }}-artifacts @@ -84,7 +85,8 @@ jobs: sanitizer: ${{ matrix.sanitizer }} - name: Upload Crash - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + # v4.3.3 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 if: failure() && steps.run.outcome == 'failure' with: name: ${{ matrix.sanitizer }}-batch-artifacts diff --git a/fuzz/build.sh b/fuzz/build.sh index eb6d01ac..d2bdfdb2 100755 --- a/fuzz/build.sh +++ b/fuzz/build.sh @@ -27,7 +27,8 @@ cmake -B c_build \ -DQUIC_BUILD_TEST=OFF \ -DQUIC_BUILD_TOOLS=OFF \ -DQUIC_BUILD_PERF=OFF \ - -DQUIC_TLS_SECRETS_SUPPORT=ON + -DQUIC_TLS_SECRETS_SUPPORT=ON \ + -DQUIC_ENABLE_LOGGING=OFF make -C c_build -j$(nproc) inc platform core warnings logging @@ -37,7 +38,8 @@ for fuzz_target in fuzz/*.c; do $CC $CFLAGS -c "$fuzz_target" -o "/tmp/${target_name}.o" \ -I${SRC}/quicer/msquic/src/inc \ - -I${SRC}/quicer/c_build + -I${SRC}/quicer/c_build \ + -DQUIC_API_ENABLE_PREVIEW_FEATURES=1 $CXX $CXXFLAGS $LIB_FUZZING_ENGINE "/tmp/${target_name}.o" \ -o "$OUT/${target_name}" \ From 04d35c9a3b65b15a73a851b0dcd6a25aaa9c696e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Oct 2025 18:46:36 +0000 Subject: [PATCH 4/4] Document fuzzing in main README Co-authored-by: qzhuyan <200020+qzhuyan@users.noreply.github.com> --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 6e031518..d025cd44 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,12 @@ firefox doc/index.html make ci ``` +## Fuzzing + +This project uses [ClusterFuzzLite](https://google.github.io/clusterfuzzlite/) for continuous fuzzing to find bugs and security vulnerabilities. Fuzz tests run automatically via GitHub Actions on pull requests and daily. + +For more information on running fuzz tests locally and contributing new fuzz targets, see [fuzz/README.md](./fuzz/README.md). + # Troubleshooting ### Log to `stdout`