Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/clusterfuzzlite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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
# v4.3.3
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
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
# v4.3.3
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
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 }}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
11 changes: 11 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Fuzz artifacts
*.o
*.a
crash-*
leak-*
timeout-*
oom-*

# Build output
build/
out/
96 changes: 96 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -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/<target_name>/` 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/<target_name>/`
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/)
58 changes: 58 additions & 0 deletions fuzz/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/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 \
-DQUIC_ENABLE_LOGGING=OFF

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 \
-DQUIC_API_ENABLE_PREVIEW_FEATURES=1

$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
Binary file added fuzz/corpus/fuzz_config/seed1
Binary file not shown.
Binary file added fuzz/corpus/fuzz_config/seed2
Binary file not shown.
Binary file added fuzz/corpus/fuzz_config/seed3
Binary file not shown.
88 changes: 88 additions & 0 deletions fuzz/fuzz_config.c
Copy link
Collaborator

@qzhuyan qzhuyan Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks fuzzing msquic not quicer, I am proposing adding a fuzzing entry in NIF so that it could run within the VM context. @copilot

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*--------------------------------------------------------------------
Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.

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.
-------------------------------------------------------------------*/

#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <msquic.h>

// 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;
}
11 changes: 11 additions & 0 deletions fuzz/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
homepage: "https://github.com/emqx/quic"
language: c
primary_contact: "[email protected]"
main_repo: "https://github.com/emqx/quic"
file_github_issue: true
sanitizers:
- address
- undefined
- memory
architectures:
- x86_64