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
14 changes: 14 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM gcr.io/oss-fuzz-base/base-builder:v1@sha256:6fce2f40942176a0bb0c2cd6ee4bf5faea55e64ef3bee65f1c647cc2c8dcbe82
USER root
RUN apt-get update && \
apt-get install -y --no-install-recommends \
meson \
ninja-build \
&& rm -rf /var/lib/apt/lists/*

COPY bstring/ $SRC/project/bstring/
COPY fuzz/ $SRC/project/fuzz/
COPY meson_options.txt $SRC/project/
COPY meson.build $SRC/project/
WORKDIR $SRC/project
COPY .clusterfuzzlite/build.sh $SRC/build.sh
51 changes: 51 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash -eu
#
# ClusterFuzzLite build script for bstring.
#
# ClusterFuzzLite sets:
# $CC / $CXX - clang
# $CFLAGS - sanitizer + coverage flags (e.g. -fsanitize=address,fuzzer-no-link)
# $LDFLAGS - sanitizer link flags
# $LIB_FUZZING_ENGINE - fuzzer driver (e.g. -fsanitize=fuzzer)
# $OUT - output directory for fuzz target binaries
#
# Meson picks up $CC/$CFLAGS/$LDFLAGS from the environment during setup.
# Do not append $LIB_FUZZING_ENGINE to global LDFLAGS here: Meson's
# compiler sanity check links a regular main() and fails when libFuzzer's
# main() is injected globally.

PROJECT_SRC="$SRC/project"
if [[ ! -f "$PROJECT_SRC/meson.build" ]]; then
PROJECT_SRC="$SRC/bstring"
fi

build_targets() {
cd "$PROJECT_SRC"

rm -rf build

meson_args=(
-Ddefault_library=static
-Denable-docs=false
-Denable-fuzzing=true
-Denable-utf8=true
-Denable-tests=false
--buildtype=plain
)

if [[ -n "${LIB_FUZZING_ENGINE:-}" ]]; then
meson_args+=("-Dfuzz-link-arg=${LIB_FUZZING_ENGINE}")
fi

meson setup build "${meson_args[@]}"
ninja -C build
}

package_outputs() {
cd "$PROJECT_SRC"
cp build/fuzz/fuzz_bstring "$OUT/"
zip -j "$OUT/fuzz_bstring_seed_corpus.zip" fuzz/corpus/*
}

build_targets
package_outputs
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
version: 2
updates:
- package-ecosystem: "docker"
directory: "/.clusterfuzzlite"
schedule:
interval: "weekly"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
79 changes: 79 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: ClusterFuzzLite
on:
push:
branches:
- main
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
schedule:
- cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC
workflow_dispatch:

permissions:
actions: read
contents: read

jobs:
# Run for a short window on every PR / push to catch regressions introduced
# by the change under review.
PR:
name: Fuzzing (code-change)
if: github.event_name == 'pull_request' || github.event_name == 'push'
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-pr
cancel-in-progress: true
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build fuzz targets
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: c
sanitizer: address
- name: Run fuzz targets
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 60
mode: code-change
sanitizer: address
output-sarif: true
- name: Upload SARIF results
if: always() && hashFiles('sarif-results/address.sarif') != ''
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
sarif_file: sarif-results/address.sarif

# Run longer on a schedule to build up a persistent corpus and surface
# crashes that require deeper exploration.
batch:
name: Fuzzing (batch)
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build fuzz targets
uses: google/clusterfuzzlite/actions/build_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
language: c
sanitizer: address
- name: Run fuzz targets
uses: google/clusterfuzzlite/actions/run_fuzzers@884713a6c30a92e5e8544c39945cd7cb630abcd1 # v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 3600
mode: batch
sanitizer: address
29 changes: 21 additions & 8 deletions bstring/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,26 @@ endif

install_headers(bstring_headers)

libbstring = library(
meson.project_name(),
bstring_sources,
version: meson.project_version(),
soversion: '1',
include_directories: bstring_inc,
install: true,
)
# When fuzzing, the library must be static so that coverage-instrumented object
# files are linked directly into the fuzz binary, where the sanitizer runtime
# can resolve the __sanitizer_cov_* symbols. A shared library would leave
# those references dangling at library link time.
if get_option('enable-fuzzing')
libbstring = static_library(
meson.project_name(),
bstring_sources,
include_directories: bstring_inc,
install: false,
)
else
libbstring = library(
meson.project_name(),
bstring_sources,
version: meson.project_version(),
soversion: '1',
include_directories: bstring_inc,
install: true,
)
endif

bstring_dep = declare_dependency(include_directories: bstring_inc, link_with: libbstring)
85 changes: 85 additions & 0 deletions fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Fuzz Testing

A single libFuzzer fuzz target is provided which tests `bstrlib`, `bstraux`, and optionally `utf8util` and `buniutil`.

Unicode support is conditional: when the project is built with `-Denable-utf8=true`,
the target is compiled with `-DBSTRING_ENABLE_UTF8` and the unicode fuzzing code
is included automatically.

## Prerequisites

libFuzzer is part of LLVM. The compiler runtime libraries are shipped in a
separate package from the compiler itself.

**Debian/Ubuntu:**

```sh
sudo apt-get install clang libclang-rt-dev
```

**Fedora:**

```sh
sudo dnf install clang compiler-rt
```

## Build

```sh
CC=clang meson setup build-fuzz \
-Denable-fuzzing=true \
-Denable-tests=false \
--buildtype=plain
meson compile -C build-fuzz
```

## Run

```sh
# Run against the seed corpus for 60 seconds
./build-fuzz/fuzz/fuzz_bstring fuzz/corpus/ -max_total_time=60 -max_len=260

# Run indefinitely, saving new corpus entries and crash inputs as they are found
mkdir -p fuzz/crashes
./build-fuzz/fuzz/fuzz_bstring fuzz/corpus/ \
-max_len=260 \
-artifact_prefix=fuzz/crashes/ \
-jobs=$(nproc)
```

Useful libFuzzer flags:

| Flag | Description |
| --- | --- |
| `-max_total_time=N` | Stop after N seconds |
| `-jobs=N` | Run N parallel fuzzing instances |
| `-max_len=4096` | Cap input size in bytes |
| `-artifact_prefix=DIR/` | Directory for crash and timeout inputs |
| `-runs=N` | Stop after N iterations |

## Reproducing a crash

If a crash input is found, reproduce it by passing the file directly to the
relevant target:

```sh
./build-fuzz/fuzz/fuzz_bstring path/to/crash-input
```

## Seed corpus

The `corpus/` directory contains initial seed inputs that guide the fuzzer
toward interesting code paths on the first run. As the fuzzer discovers new
coverage it writes additional entries to the corpus directory automatically.

## CI (ClusterFuzzLite)

The `.clusterfuzzlite/` directory at the repository root contains the
Dockerfile and `build.sh` used by ClusterFuzzLite. The GitHub Actions
workflow at `.github/workflows/clusterfuzzlite.yml` runs two jobs:

- **code-change** — 60 seconds on every push and pull request targeting `main`
- **batch** — 1 hour on a weekly schedule

Crash reports from the batch job are surfaced as GitHub Security alerts via
SARIF upload.
Binary file added fuzz/corpus/01_hello_world
Binary file not shown.
Binary file added fuzz/corpus/02_html_markup
Binary file not shown.
Binary file added fuzz/corpus/03_underscore_split
Binary file not shown.
Binary file added fuzz/corpus/04_base64
Binary file not shown.
Binary file added fuzz/corpus/05_empty
Binary file not shown.
Binary file added fuzz/corpus/06_embedded_nul
Binary file not shown.
Binary file added fuzz/corpus/07_newlines
Binary file not shown.
Binary file added fuzz/corpus/08_whitespace
Binary file not shown.
1 change: 1 addition & 0 deletions fuzz/corpus/09_long_string
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x/abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij
Binary file added fuzz/corpus/10_sgml_entities
Binary file not shown.
Binary file added fuzz/corpus/11_utf8_multibyte
Binary file not shown.
1 change: 1 addition & 0 deletions fuzz/corpus/12_utf8_malformed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ãâ‚ðŸ˜€í €
Binary file added fuzz/corpus/13_utf16le_bom
Binary file not shown.
Binary file added fuzz/corpus/14_utf16be_bom
Binary file not shown.
Binary file added fuzz/corpus/15_ucs4_codepoints
Binary file not shown.
Binary file added fuzz/corpus/16_surrogate_codepoints
Binary file not shown.
Loading