-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #110 from swiftwasm/katei/fuzz
Add fuzz testing infrastructure and fix first-round crashes
- Loading branch information
Showing
22 changed files
with
345 additions
and
81 deletions.
There are no files selected for viewing
Binary file added
BIN
221 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-00c03bb238a993ad521865092d1faa092ccba0fa
Binary file not shown.
Binary file added
BIN
221 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-362df2a30ead679d48492f81479924f404ce926d
Binary file not shown.
Binary file added
BIN
245 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-3769772d5ea06bc5e4214bf4abfb4f50feea05e6
Binary file not shown.
Binary file added
BIN
229 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-4fd35c09b62a4ee7e152a2b4586d1f55635bb673
Binary file not shown.
Binary file added
BIN
878 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-5f8fe66dfa8b7f12881421d7110fe834438a29d9
Binary file not shown.
Binary file added
BIN
152 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-8b2f015e5de0aa1eb03af3e970169f732eeea21b
Binary file not shown.
Binary file added
BIN
245 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-bfca64d7e0e9d9e8ad6371a977d47c07db7bd93f
Binary file not shown.
Binary file added
BIN
215 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-c639d0c0e8a2667af85537f2beebd98dac42c136
Binary file not shown.
Binary file added
BIN
229 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-cc3c3de2c72d1acf6dd0ed8c53e6f1d4bd0a783b
Binary file not shown.
Binary file added
BIN
229 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-d7b38baf235334838c29be758f6d708cdc4f4b2b
Binary file not shown.
Binary file added
BIN
221 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-d8d325cf3a2836ddbe2ae146ff18f554c97e42f6
Binary file not shown.
Binary file added
BIN
221 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-e19eb785046f30e38732ca80ebc521fae813a6db
Binary file not shown.
Binary file added
BIN
229 Bytes
FuzzTesting/FailCases/FuzzTranslator/crash-fc13f28803f8114de1324454b0c995d6ed3d7460
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,22 @@ | ||
// swift-tools-version: 5.10 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "FuzzTesting", | ||
products: [ | ||
.library(name: "FuzzTranslator", type: .static, targets: ["FuzzTranslator"]), | ||
], | ||
dependencies: [ | ||
.package(path: "../"), | ||
], | ||
targets: [ | ||
.target(name: "FuzzTranslator", dependencies: [ | ||
.product(name: "WasmKit", package: "WasmKit") | ||
]), | ||
] | ||
) | ||
|
||
for target in package.targets { | ||
target.swiftSettings = [.unsafeFlags(["-Xfrontend", "-sanitize=fuzzer,address"])] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,38 @@ | ||
# Fuzz Testing | ||
|
||
This subdirectory contains some [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) fuzzing targets for WasmKit. | ||
|
||
> [!WARNING] | ||
> libFuzzer does not work with the latest Swift runtime library on macOS for some reason. Run the fuzzing targets on Linux for now. | ||
## Requirements | ||
|
||
- [Open Source Swift Toolchain](https://swift.org/install) - Xcode toolchain does not contain fuzzing supoort, so you need to install the open source toolchain. | ||
- [wasm-tools](https://github.com/bytecodealliance/wasm-tools) - Required to generate random seed corpora | ||
|
||
|
||
## Running the Fuzzing Targets | ||
|
||
1. Generate seed corpora for the fuzzing targets: | ||
```sh | ||
./fuzz.py seed | ||
``` | ||
2. Run the fuzzing targets, where `<target>` is one of the fuzzing targets available in `./Sources` directory: | ||
```sh | ||
./fuzz.py run <target> | ||
``` | ||
3. Once the fuzzer finds a crash, it will generate a test case in the `FailCases/<target>` directory. | ||
|
||
|
||
## Reproducing Crashes | ||
|
||
To reproduce a crash found by the fuzzer | ||
|
||
1. Build the fuzzer executable: | ||
```sh | ||
./fuzz.py build <target> | ||
``` | ||
2. Run the fuzzer executable with the test case: | ||
```sh | ||
./.build/debug/<target> <testcase> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,13 @@ | ||
import WasmKit | ||
|
||
@_cdecl("LLVMFuzzerTestOneInput") | ||
public func FuzzCheck(_ start: UnsafePointer<UInt8>, _ count: Int) -> CInt { | ||
let bytes = Array(UnsafeBufferPointer(start: start, count: count)) | ||
do { | ||
var module = try WasmKit.parseWasm(bytes: bytes) | ||
try module.materializeAll() | ||
} catch { | ||
// Ignore errors | ||
} | ||
return 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 1,118 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import argparse | ||
import os | ||
import subprocess | ||
|
||
class CommandRunner: | ||
def __init__(self, verbose: bool = False, dry_run: bool = False): | ||
self.verbose = verbose | ||
self.dry_run = dry_run | ||
|
||
def run(self, args, **kwargs): | ||
if self.verbose or self.dry_run: | ||
print(' '.join(args)) | ||
if self.dry_run: | ||
return | ||
return subprocess.run(args, **kwargs) | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser(description='Build fuzzer') | ||
# Common options | ||
parser.add_argument( | ||
'-v', '--verbose', action='store_true', help='Print commands') | ||
parser.add_argument( | ||
'-n', '--dry-run', action='store_true', | ||
help='Print commands but do not execute them') | ||
|
||
# Subcommands | ||
subparsers = parser.add_subparsers(required=True) | ||
|
||
available_targets = list(os.listdir('Sources')) | ||
|
||
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.set_defaults(func=build) | ||
|
||
run_parser = subparsers.add_parser('run', help='Run the fuzzer') | ||
run_parser.add_argument( | ||
'target_name', type=str, help='Name of the target', choices=available_targets) | ||
run_parser.add_argument( | ||
'--skip-build', action='store_true', | ||
help='Skip building the fuzzer') | ||
run_parser.add_argument( | ||
'args', nargs=argparse.REMAINDER, | ||
help='Arguments to pass to the fuzzer') | ||
run_parser.set_defaults(func=run) | ||
|
||
seed_parser = subparsers.add_parser( | ||
'seed', help='Generate seed corpus for the fuzzer') | ||
seed_parser.set_defaults(func=seed) | ||
|
||
args = parser.parse_args() | ||
runner = CommandRunner(verbose=args.verbose, dry_run=args.dry_run) | ||
args.func(args, runner) | ||
|
||
|
||
def seed(args, runner): | ||
def generate_seed_corpus(output_path: str): | ||
args = [ | ||
"wasm-tools", "smith", "-o", output_path | ||
] | ||
# Random stdin input | ||
stdin = os.urandom(1024) | ||
process = subprocess.Popen(args, stdin=subprocess.PIPE) | ||
process.communicate(input=stdin) | ||
if process.returncode != 0: | ||
raise Exception(f"Failed to generate seed corpus: {output_path}") | ||
|
||
output_dir = ".build/fuzz-corpus" | ||
os.makedirs(output_dir, exist_ok=True) | ||
|
||
for i in range(100): | ||
output = f"{output_dir}/corpus-{i}.wasm" | ||
generate_seed_corpus(output) | ||
print(f"Generated seed corpus: {output}") | ||
|
||
|
||
def executable_path(target_name: str) -> str: | ||
return f'./.build/debug/{target_name}' | ||
|
||
|
||
def build(args, runner: CommandRunner): | ||
print(f'Building fuzzer for {args.target_name}') | ||
|
||
runner.run([ | ||
'swift', 'build', '--product', args.target_name | ||
], check=True) | ||
|
||
print('Building fuzzer executable') | ||
output = executable_path(args.target_name) | ||
runner.run([ | ||
'swiftc', f'./.build/debug/lib{args.target_name}.a', '-g', | ||
'-sanitize=fuzzer,address', '-o', output | ||
], check=True) | ||
|
||
print('Fuzzer built successfully: ', output) | ||
|
||
|
||
def run(args, runner: CommandRunner): | ||
|
||
if not args.skip_build: | ||
build(args, runner) | ||
|
||
print('Running fuzzer') | ||
|
||
artifact_dir = f'./FailCases/{args.target_name}/' | ||
os.makedirs(artifact_dir, exist_ok=True) | ||
fuzzer_args = [ | ||
executable_path(args.target_name), './.build/fuzz-corpus', | ||
f'-artifact_prefix={artifact_dir}' | ||
] args.args | ||
runner.run(fuzzer_args, env={'SWIFT_BACKTRACE': 'enable=off'}) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.