Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tools/fuzz/fuzzfilter.py
2725 views
1
#!/usr/bin/python3
2
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
3
4
# Given a fuzzer binary and a list of crashing programs, this tool collects unique crash reasons and prints reproducers.
5
6
import argparse
7
import multiprocessing
8
import os
9
import re
10
import subprocess
11
import sys
12
13
14
def is_crash(reproducer_name: str) -> bool:
15
return reproducer_name.startswith("crash-") or reproducer_name.startswith("oom-")
16
17
18
class Reproducer:
19
def __init__(self, file, reason, fingerprint):
20
self.file = file
21
self.reason = reason
22
self.fingerprint = fingerprint
23
24
25
def get_crash_reason(binary, file, remove_passing):
26
res = subprocess.run(
27
[binary, file], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
28
29
if res.returncode == 0:
30
if remove_passing:
31
print(f"Warning: {binary} {file} returned 0; removing from result set.", file=sys.stderr)
32
os.remove(file)
33
else:
34
print(f"Warning: {binary} {file} returned 0", file=sys.stderr)
35
36
return None
37
38
err = res.stderr.decode("utf-8")
39
40
if (pos := err.find("ERROR: AddressSanitizer:")) != -1:
41
return err[pos:]
42
43
if (pos := err.find("ERROR: libFuzzer:")) != -1:
44
return err[pos:]
45
46
print(f"Warning: {binary} {file} returned unrecognized error {err} with exit code {res.returncode}", file=sys.stderr)
47
return None
48
49
50
def get_crash_fingerprint(reason):
51
# Due to ASLR addresses are different every time, so we filter them out
52
reason = re.sub(r"0x[0-9a-f]+", "0xXXXX", reason)
53
return reason
54
55
56
parser = argparse.ArgumentParser()
57
parser.add_argument("binary")
58
parser.add_argument("files", action="append", default=[])
59
parser.add_argument("--remove-duplicates", action="store_true")
60
parser.add_argument("--remove-passing", action="store_true")
61
parser.add_argument("--workers", action="store", default=1, type=int)
62
parser.add_argument("--verbose", "-v", action="count", default=0, dest="verbosity")
63
64
args = parser.parse_args()
65
66
def process_file(file):
67
reason = get_crash_reason(args.binary, file, args.remove_passing)
68
if reason is None:
69
return None
70
71
fingerprint = get_crash_fingerprint(reason)
72
return Reproducer(file, reason, fingerprint)
73
74
75
filter_targets = []
76
if len(args.files) == 1:
77
for root, dirs, files in os.walk(args.files[0]):
78
for file in files:
79
if not is_crash(file):
80
continue
81
82
filter_targets.append(os.path.join(root, file))
83
else:
84
filter_targets = args.files
85
86
if __name__ == "__main__":
87
multiprocessing.freeze_support()
88
89
with multiprocessing.Pool(processes = args.workers) as pool:
90
print(f"Processing {len(filter_targets)} reproducers across {args.workers} workers.")
91
reproducers = [r for r in pool.map(process_file, filter_targets) if r is not None]
92
93
seen = set()
94
for index, reproducer in enumerate(reproducers):
95
if reproducer.fingerprint in seen:
96
if sys.stdout.isatty():
97
print("-\|/"[index % 4], end="\r")
98
99
if args.remove_duplicates:
100
if args.verbosity >= 1:
101
print(f"Removing duplicate reducer {reproducer.file}.")
102
os.remove(reproducer.file)
103
104
continue
105
106
seen.add(reproducer.fingerprint)
107
if args.verbosity >= 2:
108
print(f"Reproducer: {args.binary} {reproducer.file}")
109
print(f"Output: {reproducer.reason}")
110
111
print(f"Total unique crashes: {len(seen)}")
112
if args.remove_duplicates:
113
print(f"Duplicate reproducers have been removed.")
114
115