Skip to content

Fuzzing: Integrate ClusterFuzzLite #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 22, 2024
Merged
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
9 changes: 9 additions & 0 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM gcr.io/oss-fuzz-base/base-builder-swift:v1
RUN apt-get update && apt-get install -y make autoconf automake libtool
ENV SWIFT_PREFIX=/opt/swift
RUN mkdir -p "$SWIFT_PREFIX" && \
curl -L https://download.swift.org/swift-6.0.1-release/ubuntu2004/swift-6.0.1-RELEASE/swift-6.0.1-RELEASE-ubuntu20.04.tar.gz | tar xz -C "$SWIFT_PREFIX" --strip-component 1
ENV PATH="$SWIFT_PREFIX/usr/bin:$PATH"
COPY . $SRC/wasmkit
WORKDIR $SRC/wasmkit
COPY .clusterfuzzlite/build.sh $SRC/
7 changes: 7 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash -eu

cd FuzzTesting
./fuzz.py --verbose build --sanitizer="$SANITIZER" FuzzTranslator

find .build/debug/ -maxdepth 1 -type f -name "Fuzz*" -executable -exec cp {} "$OUT/" \;

1 change: 1 addition & 0 deletions .clusterfuzzlite/project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: swift
32 changes: 32 additions & 0 deletions .github/workflows/cflite_batch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: swift
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 }}
output-sarif: true
storage-repo: https://${{ secrets.SWIFTWASM_BOT_GITHUB_TOKEN }}@github.com/swiftwasm/wasmkit-fuzz-corpora.git
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
25 changes: 25 additions & 0 deletions .github/workflows/cflite_build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ClusterFuzzLite continuous builds
on:
push:
branches:
- main
permissions: read-all
jobs:
Build:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
sanitizer:
- address
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: swift
sanitizer: ${{ matrix.sanitizer }}
upload-build: true
45 changes: 45 additions & 0 deletions .github/workflows/cflite_cron.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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
with:
language: swift
- name: Run Fuzzers
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 600
mode: 'prune'
output-sarif: true
storage-repo: https://${{ secrets.SWIFTWASM_BOT_GITHUB_TOKEN }}@github.com/swiftwasm/wasmkit-fuzz-corpora.git
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
Coverage:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: swift
sanitizer: coverage
- name: Run Fuzzers
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 600
mode: 'coverage'
sanitizer: 'coverage'
storage-repo: https://${{ secrets.SWIFTWASM_BOT_GITHUB_TOKEN }}@github.com/swiftwasm/wasmkit-fuzz-corpora.git
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
41 changes: 41 additions & 0 deletions .github/workflows/cflite_pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: ClusterFuzzLite PR fuzzing
on:
pull_request:
paths:
- '**'
permissions: read-all
jobs:
PR:
name: PR Fuzzing (${{ matrix.sanitizer }} sanitizer)
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
sanitizer:
- address
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: swift
github-token: ${{ secrets.GITHUB_TOKEN }}
sanitizer: ${{ matrix.sanitizer }}
storage-repo: https://${{ secrets.SWIFTWASM_BOT_GITHUB_TOKEN }}@github.com/swiftwasm/wasmkit-fuzz-corpora.git
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
- 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
storage-repo: https://${{ secrets.SWIFTWASM_BOT_GITHUB_TOKEN }}@github.com/swiftwasm/wasmkit-fuzz-corpora.git
storage-repo-branch: main
storage-repo-branch-coverage: gh-pages
3 changes: 3 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
jobs:
format:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
container:
image: swift:6.0.1-jammy
steps:
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build-macos:
strategy:
Expand All @@ -16,12 +20,14 @@ jobs:
development-toolchain-tag: swift-DEVELOPMENT-SNAPSHOT-2024-07-08-a
wasi-swift-sdk-download: "https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip"
wasi-swift-sdk-id: DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi
test-args: ""
# Swift 5.9.0
- os: macos-13
xcode: Xcode_15.0.1
development-toolchain-tag: swift-DEVELOPMENT-SNAPSHOT-2024-07-08-a
wasi-swift-sdk-download: "https://github.com/swiftwasm/swift/releases/download/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a/swift-wasm-DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi.artifactbundle.zip"
wasi-swift-sdk-id: DEVELOPMENT-SNAPSHOT-2024-07-09-a-wasm32-unknown-wasi
test-args: "--sanitize address"

runs-on: ${{ matrix.os }}
name: "build-macos (${{ matrix.xcode }})"
Expand Down Expand Up @@ -52,7 +58,7 @@ jobs:
}
EOS
- run: ./Vendor/checkout-dependency
- run: swift test --sanitize address
- run: swift test ${{ matrix.test-args }}

build-xcode:
runs-on: macos-15
Expand Down
26 changes: 18 additions & 8 deletions FuzzTesting/Package.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
// swift-tools-version: 5.10
// swift-tools-version: 5.8

import PackageDescription

let package = Package(
name: "FuzzTesting",
products: [
// Discussion: Why we build libraries instead of executables linking libFuzzer?
//
// First, libclang_rt.fuzzer.a defines the main function for the fuzzing process
// and object files given by the user are expected not to have a "main" function
// to avoid conflicts.
// Fortunately, SwiftPM asks the compiler frontend to define the main entrypoint as
// `<module_name>_main` for testing executable targets (`-entry-point-function-name`)
// so object files of `executableTarget` targets are capable of being linked with
// libclang_rt.fuzzer.a.
// However, at link-time, SwiftPM asks the linker to rename the `<module_name>_main`
// symbol back to `main` for the final executable (`--defsym main=<module_name>_main`)
// and gold linker respects the renamed "main" symbol rather than the one defined in
// libclang_rt.fuzzer.a, so the final executable does not start the fuzzing process.
//
// Instead of relying on the SwiftPM's linking process, we build libraries defining
// fuzzing target functions and manually link them with fuzzing runtime libraries.
.library(name: "FuzzTranslator", type: .static, targets: ["FuzzTranslator"]),
.library(name: "FuzzExecute", type: .static, targets: ["FuzzExecute"]),
// FuzzDifferential is not a libFuzzer-based target, so we build it as an executable.
.executable(name: "FuzzDifferential", targets: ["FuzzDifferential"]),
],
dependencies: [
Expand All @@ -27,10 +44,3 @@ let package = Package(
.target(name: "WasmCAPI"),
]
)

let libFuzzerTargets = ["FuzzTranslator", "FuzzExecute"]

for target in package.targets {
guard libFuzzerTargets.contains(target.name) else { continue }
target.swiftSettings = [.unsafeFlags(["-Xfrontend", "-sanitize=fuzzer,address"])]
}
33 changes: 27 additions & 6 deletions FuzzTesting/fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def main():
build_parser = subparsers.add_parser('build', help='Build the fuzzer')
build_parser.add_argument(
'target_name', type=str, help='Name of the target', choices=available_targets)
build_parser.add_argument(
'--sanitizer', type=str, default='address')
build_parser.set_defaults(func=build)

run_parser = subparsers.add_parser('run', help='Run the fuzzer')
Expand Down Expand Up @@ -96,16 +98,35 @@ def executable_path(target_name: str) -> str:
def build(args, runner: CommandRunner):
print(f'Building fuzzer for {args.target_name}')

runner.run([
'swift', 'build', '--product', args.target_name
], check=True)
driver_flags = []
if args.sanitizer == 'coverage':
driver_flags += [
'-profile-generate', '-profile-coverage-mapping',
'-sanitize=fuzzer'
]
else:
driver_flags += [f'-sanitize=fuzzer,{args.sanitizer}']

build_args = [
'swift', 'build', '--product', args.target_name,
]
for driver_flag in driver_flags:
build_args += ['-Xswiftc', driver_flag]

runner.run(build_args, check=True)

print('Building fuzzer executable')
# See "Discussion" in Package.swift for why we need to manually link
# the library product.
output = executable_path(args.target_name)
runner.run([
link_args = [
'swiftc', f'./.build/debug/lib{args.target_name}.a', '-g',
'-sanitize=fuzzer,address', '-o', output
], check=True)
# Link Swift runtime statically to allow copying fuzzers to other
# machines (oss-fuzz does this)
'-static-stdlib', '-o', output
]
link_args += driver_flags
runner.run(link_args, check=True)

print('Fuzzer built successfully: ', output)

Expand Down