-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathfuzz.py
executable file
·156 lines (122 loc) · 4.79 KB
/
fuzz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/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 available_libfuzzer_targets():
import json
# The list of available library products
args = ['swift', 'package', 'dump-package']
result = subprocess.run(args, stdout=subprocess.PIPE, check=True)
package = result.stdout.decode('utf-8')
package = json.loads(package)
return [product['name'] for product in package['products']
if 'library' in product['type']]
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 = available_libfuzzer_targets()
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')
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}')
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)
link_args = [
'swiftc', f'./.build/debug/lib{args.target_name}.a', '-g',
# 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)
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',
'-fork=2',
'-timeout=5', '-ignore_timeouts=1',
# Relax the RSS limit to 5GB (default is 4GB) to allow
# allocating maximum memory for 32-bit space.
'-rss_limit_mb=5368709120',
f'-artifact_prefix={artifact_dir}'
] + args.args
runner.run(fuzzer_args, env={'SWIFT_BACKTRACE': 'enable=off'})
if __name__ == '__main__':
main()