xref: /aosp_15_r20/external/bc/scripts/afl.py (revision 5a6e848804d15c18a0125914844ee4eb0bda4fcf)
1*5a6e8488SAndroid Build Coastguard Worker#! /usr/bin/python3 -B
2*5a6e8488SAndroid Build Coastguard Worker#
3*5a6e8488SAndroid Build Coastguard Worker# SPDX-License-Identifier: BSD-2-Clause
4*5a6e8488SAndroid Build Coastguard Worker#
5*5a6e8488SAndroid Build Coastguard Worker# Copyright (c) 2018-2024 Gavin D. Howard and contributors.
6*5a6e8488SAndroid Build Coastguard Worker#
7*5a6e8488SAndroid Build Coastguard Worker# Redistribution and use in source and binary forms, with or without
8*5a6e8488SAndroid Build Coastguard Worker# modification, are permitted provided that the following conditions are met:
9*5a6e8488SAndroid Build Coastguard Worker#
10*5a6e8488SAndroid Build Coastguard Worker# * Redistributions of source code must retain the above copyright notice, this
11*5a6e8488SAndroid Build Coastguard Worker#   list of conditions and the following disclaimer.
12*5a6e8488SAndroid Build Coastguard Worker#
13*5a6e8488SAndroid Build Coastguard Worker# * Redistributions in binary form must reproduce the above copyright notice,
14*5a6e8488SAndroid Build Coastguard Worker#   this list of conditions and the following disclaimer in the documentation
15*5a6e8488SAndroid Build Coastguard Worker#   and/or other materials provided with the distribution.
16*5a6e8488SAndroid Build Coastguard Worker#
17*5a6e8488SAndroid Build Coastguard Worker# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18*5a6e8488SAndroid Build Coastguard Worker# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19*5a6e8488SAndroid Build Coastguard Worker# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20*5a6e8488SAndroid Build Coastguard Worker# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21*5a6e8488SAndroid Build Coastguard Worker# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22*5a6e8488SAndroid Build Coastguard Worker# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23*5a6e8488SAndroid Build Coastguard Worker# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24*5a6e8488SAndroid Build Coastguard Worker# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25*5a6e8488SAndroid Build Coastguard Worker# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26*5a6e8488SAndroid Build Coastguard Worker# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27*5a6e8488SAndroid Build Coastguard Worker# POSSIBILITY OF SUCH DAMAGE.
28*5a6e8488SAndroid Build Coastguard Worker#
29*5a6e8488SAndroid Build Coastguard Worker
30*5a6e8488SAndroid Build Coastguard Workerimport os
31*5a6e8488SAndroid Build Coastguard Workerimport sys
32*5a6e8488SAndroid Build Coastguard Workerimport shutil
33*5a6e8488SAndroid Build Coastguard Workerimport subprocess
34*5a6e8488SAndroid Build Coastguard Worker
35*5a6e8488SAndroid Build Coastguard Worker
36*5a6e8488SAndroid Build Coastguard Worker# Print the usage and exit with an error.
37*5a6e8488SAndroid Build Coastguard Workerdef usage():
38*5a6e8488SAndroid Build Coastguard Worker	print("usage: {} [--asan] dir [results_dir [exe options...]]".format(script))
39*5a6e8488SAndroid Build Coastguard Worker	print("       The valid values for dir are: 'bc1', 'bc2', and 'dc'.")
40*5a6e8488SAndroid Build Coastguard Worker	sys.exit(1)
41*5a6e8488SAndroid Build Coastguard Worker
42*5a6e8488SAndroid Build Coastguard Worker
43*5a6e8488SAndroid Build Coastguard Worker# Check for a crash.
44*5a6e8488SAndroid Build Coastguard Worker# @param exebase  The calculator that crashed.
45*5a6e8488SAndroid Build Coastguard Worker# @param out      The file to copy the crash file to.
46*5a6e8488SAndroid Build Coastguard Worker# @param error    The error code (negative).
47*5a6e8488SAndroid Build Coastguard Worker# @param file     The crash file.
48*5a6e8488SAndroid Build Coastguard Worker# @param type     The type of run that caused the crash. This is just a string
49*5a6e8488SAndroid Build Coastguard Worker#                 that would make sense to the user.
50*5a6e8488SAndroid Build Coastguard Worker# @param test     The contents of the crash file, or which line caused the crash
51*5a6e8488SAndroid Build Coastguard Worker#                 for a run through stdin.
52*5a6e8488SAndroid Build Coastguard Workerdef check_crash(exebase, out, error, file, type, test):
53*5a6e8488SAndroid Build Coastguard Worker	if error < 0:
54*5a6e8488SAndroid Build Coastguard Worker		print("\n{} crashed ({}) on {}:\n".format(exebase, -error, type))
55*5a6e8488SAndroid Build Coastguard Worker		print("    {}".format(test))
56*5a6e8488SAndroid Build Coastguard Worker		print("\nCopying to \"{}\"".format(out))
57*5a6e8488SAndroid Build Coastguard Worker		shutil.copy2(file, out)
58*5a6e8488SAndroid Build Coastguard Worker		print("\nexiting...")
59*5a6e8488SAndroid Build Coastguard Worker		sys.exit(error)
60*5a6e8488SAndroid Build Coastguard Worker
61*5a6e8488SAndroid Build Coastguard Worker
62*5a6e8488SAndroid Build Coastguard Worker# Runs a test. This function is used to ensure that if a test times out, it is
63*5a6e8488SAndroid Build Coastguard Worker# discarded. Otherwise, some tests result in incredibly long runtimes. We need
64*5a6e8488SAndroid Build Coastguard Worker# to ignore those.
65*5a6e8488SAndroid Build Coastguard Worker#
66*5a6e8488SAndroid Build Coastguard Worker# @param cmd      The command to run.
67*5a6e8488SAndroid Build Coastguard Worker# @param exebase  The calculator to test.
68*5a6e8488SAndroid Build Coastguard Worker# @param tout     The timeout to use.
69*5a6e8488SAndroid Build Coastguard Worker# @param indata   The data to push through stdin for the test.
70*5a6e8488SAndroid Build Coastguard Worker# @param out      The file to copy the test file to if it causes a crash.
71*5a6e8488SAndroid Build Coastguard Worker# @param file     The test file.
72*5a6e8488SAndroid Build Coastguard Worker# @param type     The type of test. This is just a string that would make sense
73*5a6e8488SAndroid Build Coastguard Worker#                 to the user.
74*5a6e8488SAndroid Build Coastguard Worker# @param test     The test. It could be an entire file, or just one line.
75*5a6e8488SAndroid Build Coastguard Worker# @param environ  The environment to run the command under.
76*5a6e8488SAndroid Build Coastguard Workerdef run_test(cmd, exebase, tout, indata, out, file, type, test, environ=None):
77*5a6e8488SAndroid Build Coastguard Worker	try:
78*5a6e8488SAndroid Build Coastguard Worker		p = subprocess.run(cmd, timeout=tout, input=indata, stdout=subprocess.PIPE,
79*5a6e8488SAndroid Build Coastguard Worker		                   stderr=subprocess.PIPE, env=environ)
80*5a6e8488SAndroid Build Coastguard Worker		check_crash(exebase, out, p.returncode, file, type, test)
81*5a6e8488SAndroid Build Coastguard Worker	except subprocess.TimeoutExpired:
82*5a6e8488SAndroid Build Coastguard Worker		print("\n    {} timed out. Continuing...\n".format(exebase))
83*5a6e8488SAndroid Build Coastguard Worker
84*5a6e8488SAndroid Build Coastguard Worker
85*5a6e8488SAndroid Build Coastguard Worker# Creates and runs a test. This basically just takes a file, runs it through the
86*5a6e8488SAndroid Build Coastguard Worker# appropriate calculator as a whole file, then runs it through the calculator
87*5a6e8488SAndroid Build Coastguard Worker# using stdin.
88*5a6e8488SAndroid Build Coastguard Worker# @param file     The file to test.
89*5a6e8488SAndroid Build Coastguard Worker# @param tout     The timeout to use.
90*5a6e8488SAndroid Build Coastguard Worker# @param environ  The environment to run under.
91*5a6e8488SAndroid Build Coastguard Workerdef create_test(file, tout, environ=None):
92*5a6e8488SAndroid Build Coastguard Worker
93*5a6e8488SAndroid Build Coastguard Worker	print("    {}".format(file))
94*5a6e8488SAndroid Build Coastguard Worker
95*5a6e8488SAndroid Build Coastguard Worker	base = os.path.basename(file)
96*5a6e8488SAndroid Build Coastguard Worker
97*5a6e8488SAndroid Build Coastguard Worker	if base == "README.txt":
98*5a6e8488SAndroid Build Coastguard Worker		return
99*5a6e8488SAndroid Build Coastguard Worker
100*5a6e8488SAndroid Build Coastguard Worker	with open(file, "rb") as f:
101*5a6e8488SAndroid Build Coastguard Worker		lines = f.readlines()
102*5a6e8488SAndroid Build Coastguard Worker
103*5a6e8488SAndroid Build Coastguard Worker	print("        Running whole file...")
104*5a6e8488SAndroid Build Coastguard Worker
105*5a6e8488SAndroid Build Coastguard Worker	run_test(exe + [ file ], exebase, tout, halt.encode(), out, file, "file", file, environ)
106*5a6e8488SAndroid Build Coastguard Worker
107*5a6e8488SAndroid Build Coastguard Worker	print("        Running file through stdin...")
108*5a6e8488SAndroid Build Coastguard Worker
109*5a6e8488SAndroid Build Coastguard Worker	with open(file, "rb") as f:
110*5a6e8488SAndroid Build Coastguard Worker		content = f.read()
111*5a6e8488SAndroid Build Coastguard Worker
112*5a6e8488SAndroid Build Coastguard Worker	run_test(exe, exebase, tout, content, out, file,
113*5a6e8488SAndroid Build Coastguard Worker	         "running {} through stdin".format(file), file, environ)
114*5a6e8488SAndroid Build Coastguard Worker
115*5a6e8488SAndroid Build Coastguard Worker
116*5a6e8488SAndroid Build Coastguard Worker# Get the children of a directory.
117*5a6e8488SAndroid Build Coastguard Worker# @param dir        The directory to get the children of.
118*5a6e8488SAndroid Build Coastguard Worker# @param get_files  True if files should be gotten, false if directories should
119*5a6e8488SAndroid Build Coastguard Worker#                   be gotten.
120*5a6e8488SAndroid Build Coastguard Workerdef get_children(dir, get_files):
121*5a6e8488SAndroid Build Coastguard Worker	dirs = []
122*5a6e8488SAndroid Build Coastguard Worker	with os.scandir(dir) as it:
123*5a6e8488SAndroid Build Coastguard Worker		for entry in it:
124*5a6e8488SAndroid Build Coastguard Worker			if not entry.name.startswith('.') and     \
125*5a6e8488SAndroid Build Coastguard Worker			   ((entry.is_dir() and not get_files) or \
126*5a6e8488SAndroid Build Coastguard Worker			    (entry.is_file() and get_files)):
127*5a6e8488SAndroid Build Coastguard Worker				dirs.append(entry.name)
128*5a6e8488SAndroid Build Coastguard Worker	dirs.sort()
129*5a6e8488SAndroid Build Coastguard Worker	return dirs
130*5a6e8488SAndroid Build Coastguard Worker
131*5a6e8488SAndroid Build Coastguard Worker
132*5a6e8488SAndroid Build Coastguard Worker# Returns the correct executable name for the directory under test.
133*5a6e8488SAndroid Build Coastguard Worker# @param d  The directory under test.
134*5a6e8488SAndroid Build Coastguard Workerdef exe_name(d):
135*5a6e8488SAndroid Build Coastguard Worker	return "bc" if d == "bc1" or d == "bc2" else "dc"
136*5a6e8488SAndroid Build Coastguard Worker
137*5a6e8488SAndroid Build Coastguard Worker
138*5a6e8488SAndroid Build Coastguard Worker# Housekeeping.
139*5a6e8488SAndroid Build Coastguard Workerscript = sys.argv[0]
140*5a6e8488SAndroid Build Coastguard Workerscriptdir = os.path.dirname(script)
141*5a6e8488SAndroid Build Coastguard Worker
142*5a6e8488SAndroid Build Coastguard Worker# Must run this script alone.
143*5a6e8488SAndroid Build Coastguard Workerif __name__ != "__main__":
144*5a6e8488SAndroid Build Coastguard Worker	usage()
145*5a6e8488SAndroid Build Coastguard Worker
146*5a6e8488SAndroid Build Coastguard Workertimeout = 2.5
147*5a6e8488SAndroid Build Coastguard Worker
148*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) < 2:
149*5a6e8488SAndroid Build Coastguard Worker	usage()
150*5a6e8488SAndroid Build Coastguard Worker
151*5a6e8488SAndroid Build Coastguard Workeridx = 1
152*5a6e8488SAndroid Build Coastguard Worker
153*5a6e8488SAndroid Build Coastguard Workerexedir = sys.argv[idx]
154*5a6e8488SAndroid Build Coastguard Worker
155*5a6e8488SAndroid Build Coastguard Workerasan = (exedir == "--asan")
156*5a6e8488SAndroid Build Coastguard Worker
157*5a6e8488SAndroid Build Coastguard Worker# We could possibly run under ASan. See later for what that means.
158*5a6e8488SAndroid Build Coastguard Workerif asan:
159*5a6e8488SAndroid Build Coastguard Worker	idx += 1
160*5a6e8488SAndroid Build Coastguard Worker	if len(sys.argv) < idx + 1:
161*5a6e8488SAndroid Build Coastguard Worker		usage()
162*5a6e8488SAndroid Build Coastguard Worker	exedir = sys.argv[idx]
163*5a6e8488SAndroid Build Coastguard Worker
164*5a6e8488SAndroid Build Coastguard Workerprint("exedir: {}".format(exedir))
165*5a6e8488SAndroid Build Coastguard Worker
166*5a6e8488SAndroid Build Coastguard Worker# Grab the correct directory of AFL++ results.
167*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= idx + 2:
168*5a6e8488SAndroid Build Coastguard Worker	resultsdir = sys.argv[idx + 1]
169*5a6e8488SAndroid Build Coastguard Workerelse:
170*5a6e8488SAndroid Build Coastguard Worker	if exedir == "bc1":
171*5a6e8488SAndroid Build Coastguard Worker		resultsdir = scriptdir + "/../tests/fuzzing/bc_outputs1"
172*5a6e8488SAndroid Build Coastguard Worker	elif exedir == "bc2":
173*5a6e8488SAndroid Build Coastguard Worker		resultsdir = scriptdir + "/../tests/fuzzing/bc_outputs2"
174*5a6e8488SAndroid Build Coastguard Worker	elif exedir == "dc":
175*5a6e8488SAndroid Build Coastguard Worker		resultsdir = scriptdir + "/../tests/fuzzing/dc_outputs"
176*5a6e8488SAndroid Build Coastguard Worker	else:
177*5a6e8488SAndroid Build Coastguard Worker		raise ValueError("exedir must be either bc1, bc2, or dc");
178*5a6e8488SAndroid Build Coastguard Worker
179*5a6e8488SAndroid Build Coastguard Workerprint("resultsdir: {}".format(resultsdir))
180*5a6e8488SAndroid Build Coastguard Worker
181*5a6e8488SAndroid Build Coastguard Worker# More command-line processing.
182*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= idx + 3:
183*5a6e8488SAndroid Build Coastguard Worker	exe = sys.argv[idx + 2]
184*5a6e8488SAndroid Build Coastguard Workerelse:
185*5a6e8488SAndroid Build Coastguard Worker	exe = scriptdir + "/../bin/" + exe_name(exedir)
186*5a6e8488SAndroid Build Coastguard Worker
187*5a6e8488SAndroid Build Coastguard Workerexebase = os.path.basename(exe)
188*5a6e8488SAndroid Build Coastguard Worker
189*5a6e8488SAndroid Build Coastguard Worker
190*5a6e8488SAndroid Build Coastguard Worker# Use the correct options.
191*5a6e8488SAndroid Build Coastguard Workerif exebase == "bc":
192*5a6e8488SAndroid Build Coastguard Worker	halt = "halt\n"
193*5a6e8488SAndroid Build Coastguard Worker	options = "-lq"
194*5a6e8488SAndroid Build Coastguard Worker	seed = ["-e", "seed = 1280937142.20981723890730892738902938071028973408912703984712093", "-f-" ]
195*5a6e8488SAndroid Build Coastguard Workerelse:
196*5a6e8488SAndroid Build Coastguard Worker	halt = "q\n"
197*5a6e8488SAndroid Build Coastguard Worker	options = "-x"
198*5a6e8488SAndroid Build Coastguard Worker	seed = ["-e", "1280937142.20981723890730892738902938071028973408912703984712093j", "-f-" ]
199*5a6e8488SAndroid Build Coastguard Worker
200*5a6e8488SAndroid Build Coastguard Worker# More command-line processing.
201*5a6e8488SAndroid Build Coastguard Workerif len(sys.argv) >= idx + 4:
202*5a6e8488SAndroid Build Coastguard Worker	exe = [ exe, sys.argv[idx + 3:], options ] + seed
203*5a6e8488SAndroid Build Coastguard Workerelse:
204*5a6e8488SAndroid Build Coastguard Worker	exe = [ exe, options ] + seed
205*5a6e8488SAndroid Build Coastguard Workerfor i in range(4, len(sys.argv)):
206*5a6e8488SAndroid Build Coastguard Worker	exe.append(sys.argv[i])
207*5a6e8488SAndroid Build Coastguard Worker
208*5a6e8488SAndroid Build Coastguard Workerout = scriptdir + "/../.test.txt"
209*5a6e8488SAndroid Build Coastguard Worker
210*5a6e8488SAndroid Build Coastguard Workerprint(os.path.realpath(os.getcwd()))
211*5a6e8488SAndroid Build Coastguard Worker
212*5a6e8488SAndroid Build Coastguard Workerdirs = get_children(resultsdir, False)
213*5a6e8488SAndroid Build Coastguard Worker
214*5a6e8488SAndroid Build Coastguard Worker# Set the correct ASAN_OPTIONS.
215*5a6e8488SAndroid Build Coastguard Workerif asan:
216*5a6e8488SAndroid Build Coastguard Worker	env = os.environ.copy()
217*5a6e8488SAndroid Build Coastguard Worker	env['ASAN_OPTIONS'] = 'abort_on_error=1:allocator_may_return_null=1'
218*5a6e8488SAndroid Build Coastguard Worker
219*5a6e8488SAndroid Build Coastguard Workerfor d in dirs:
220*5a6e8488SAndroid Build Coastguard Worker
221*5a6e8488SAndroid Build Coastguard Worker	d = resultsdir + "/" + d
222*5a6e8488SAndroid Build Coastguard Worker
223*5a6e8488SAndroid Build Coastguard Worker	print(d)
224*5a6e8488SAndroid Build Coastguard Worker
225*5a6e8488SAndroid Build Coastguard Worker	# Check the crash files.
226*5a6e8488SAndroid Build Coastguard Worker	files = get_children(d + "/crashes/", True)
227*5a6e8488SAndroid Build Coastguard Worker
228*5a6e8488SAndroid Build Coastguard Worker	for file in files:
229*5a6e8488SAndroid Build Coastguard Worker		file = d + "/crashes/" + file
230*5a6e8488SAndroid Build Coastguard Worker		create_test(file, timeout)
231*5a6e8488SAndroid Build Coastguard Worker
232*5a6e8488SAndroid Build Coastguard Worker	# If we are running under ASan, we want to check all files. Otherwise, skip.
233*5a6e8488SAndroid Build Coastguard Worker	if not asan:
234*5a6e8488SAndroid Build Coastguard Worker		continue
235*5a6e8488SAndroid Build Coastguard Worker
236*5a6e8488SAndroid Build Coastguard Worker	# Check all of the test cases found by AFL++.
237*5a6e8488SAndroid Build Coastguard Worker	files = get_children(d + "/queue/", True)
238*5a6e8488SAndroid Build Coastguard Worker
239*5a6e8488SAndroid Build Coastguard Worker	for file in files:
240*5a6e8488SAndroid Build Coastguard Worker		file = d + "/queue/" + file
241*5a6e8488SAndroid Build Coastguard Worker		create_test(file, timeout * 2, env)
242*5a6e8488SAndroid Build Coastguard Worker
243*5a6e8488SAndroid Build Coastguard Workerprint("Done")
244