Skip to content
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

Use explicit instance dir for afl-fuzz, needed for afl++ #2206

Merged
merged 2 commits into from
Jan 25, 2021
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
6 changes: 4 additions & 2 deletions src/python/bot/fuzzers/afl/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@

OUTPUT_FLAG = '-o'

SKIP_DETERMINISTIC_FLAG = '-d'

TIMEOUT_FLAG = '-t'

INSTANCE_ID_FLAG = '-S'

# AFL environment variables.
SKIP_CRASHES_ENV_VAR = 'AFL_SKIP_CRASHES'

Expand Down Expand Up @@ -61,3 +61,5 @@
MAX_MEMORY_LIMIT = 'none'

CORE_PATTERN_FILE_PATH = '/proc/sys/kernel/core_pattern'

DEFAULT_INSTANCE_ID = 'default'
43 changes: 10 additions & 33 deletions src/python/bot/fuzzers/afl/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,15 @@ def is_testcase(cls, path):
return (os.path.isfile(path) and
bool(re.match(cls.TESTCASE_REGEX, os.path.basename(path))))

@property
def instance_directory(self):
"""Returns afl-fuzz's instance directory."""
return os.path.join(self.output_directory, constants.DEFAULT_INSTANCE_ID)

@property
def queue(self):
"""Returns afl-fuzz's queue directory."""
return os.path.join(self.output_directory, 'queue')
return os.path.join(self.instance_directory, 'queue')

def is_new_testcase(self, path):
"""Determine if |path| is a new unit."""
Expand All @@ -207,7 +212,7 @@ def copy_crash_if_needed(self, testcase_path):
created by run.py).
"""
crash_paths = list_full_file_paths(
os.path.join(self.output_directory, 'crashes'))
os.path.join(self.instance_directory, 'crashes'))

for crash_path in crash_paths:
# AFL puts a README.txt file in the crashes directory. Just ignore it.
Expand All @@ -232,7 +237,7 @@ def remove_hang_in_queue(self, hang_filename):
@property
def stats_path(self):
"""Returns the path of AFL's stats file: "fuzzer_stats"."""
return os.path.join(self.output_directory, 'fuzzer_stats')
return os.path.join(self.instance_directory, 'fuzzer_stats')


class FuzzingStrategies(object):
Expand Down Expand Up @@ -421,10 +426,6 @@ def __init__(self, input_directory, target_path, fuzzing_strategies):
# Make a new corpus without oversized inputs if needed.
self.create_new_if_needed()

# Decide if we should skip AFL's deterministic steps.
self.skip_deterministic = True
self._decide_skip_deterministic()

def restore_if_needed(self):
"""Restore the original input directory if self.original_input_directory is
set. Used to by merge() to get rid of the temporary input directory if it
Expand All @@ -438,20 +439,6 @@ def restore_if_needed(self):
self.input_directory = self.original_input_directory
self.original_input_directory = None

def _decide_skip_deterministic(self):
"""AFL typically uses deterministic steps to mutate files in the input
directory. It is only useful to do these steps once on a corpus. Thus skip
the deterministic steps if there are more than a trivial number of inputs
in the initial corpus."""
# Don't get tricked into thinking we have a new corpus when subsetting.
if (self.strategies.use_corpus_subset and
self.strategies.corpus_subset_size < self.MIN_INPUTS_FOR_SKIP):
return
inputs = os.listdir(self.input_directory)
# If the corpus is small, don't worry about skipping.
if len(inputs) < self.MIN_INPUTS_FOR_SKIP:
self.skip_deterministic = False

def create_new_if_needed(self):
"""Checks if any inputs are too large for AFL. If not then does nothing.
Otherwise creates a temporary input directory and copies the non-oversized
Expand Down Expand Up @@ -799,6 +786,7 @@ def generate_afl_args(self,
afl_output = self.afl_output.output_directory

afl_args = [
constants.INSTANCE_ID_FLAG + constants.DEFAULT_INSTANCE_ID,
constants.INPUT_FLAG + afl_input, constants.OUTPUT_FLAG + afl_output,
constants.MEMORY_LIMIT_FLAG + str(mem_limit)
]
Expand Down Expand Up @@ -1032,17 +1020,6 @@ def fuzz(self):

self._fuzz_args = self.generate_afl_args()

# Enable AFL's 'quick & dirty mode' which disable deterministic steps if
# we have already done them. See
# https://github.com/mcarpenter/afl/blob/master/docs/README for more
# details.
# TODO(metzman): Decide if we want to ensure that the deterministic stage
# finishes before terminating if we don't skip since it will never get a
# chance to run again if terminated early. This is only conceivable for
# fuzzers with large seed corpora and short timeouts.
if self.afl_input.skip_deterministic:
self._fuzz_args.insert(0, constants.SKIP_DETERMINISTIC_FLAG)

self.do_offline_mutations()

# Decide if we want to use fast cal based on the size of the input
Expand Down Expand Up @@ -1111,7 +1088,7 @@ def merge_corpus(self):
# Remove arguments for afl-fuzz.
self.remove_arg(showmap_args, constants.INPUT_FLAG)
self.remove_arg(showmap_args, constants.DICT_FLAG)
self.remove_arg(showmap_args, constants.SKIP_DETERMINISTIC_FLAG)
self.remove_arg(showmap_args, constants.INSTANCE_ID_FLAG)

# Replace -o argument.
if environment.get_value('USE_MINIJAIL'):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def test_fuzz_no_crash(self, mock_get_timeout):
testcase_path = setup_testcase_and_corpus('empty', 'corpus', fuzz=True)
output = run_launcher(testcase_path, 'test_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -i{1}/corpus '
'Command: {0}/afl-fuzz -Sdefault -i{1}/corpus '
'-o{1}/temp-1337/afl_output_dir -mnone '
'{0}/test_fuzzer 2147483647'.format(DATA_DIRECTORY, TEMP_DIRECTORY),
output)
Expand All @@ -284,7 +284,7 @@ def test_fuzz_input_crash(self, mock_get_timeout):
testcase_path = setup_testcase_and_corpus('empty', 'corpus', fuzz=True)
output = run_launcher(testcase_path, 'always_crash_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -i{1}/corpus '
'Command: {0}/afl-fuzz -Sdefault -i{1}/corpus '
'-o{1}/temp-1337/afl_output_dir -mnone '
'{0}/always_crash_fuzzer 2147483647'.format(DATA_DIRECTORY,
TEMP_DIRECTORY), output)
Expand All @@ -307,7 +307,7 @@ def test_fuzz_crash(self, mock_get_timeout):

output = run_launcher(testcase_path, 'easy_crash_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -i{1}/corpus '
'Command: {0}/afl-fuzz -Sdefault -i{1}/corpus '
'-o{1}/temp-1337/afl_output_dir -mnone '
'{0}/easy_crash_fuzzer 2147483647'.format(DATA_DIRECTORY,
TEMP_DIRECTORY), output)
Expand All @@ -334,7 +334,7 @@ def test_fuzz_merge(self, mock_get_timeout):
f.write('A' * 256)
output = run_launcher(testcase_path, 'test_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -d -i{1}/redundant_corpus '
'Command: {0}/afl-fuzz -Sdefault -i{1}/redundant_corpus '
'-o{1}/temp-1337/afl_output_dir -mnone '
'{0}/test_fuzzer 2147483647'.format(DATA_DIRECTORY, TEMP_DIRECTORY),
output)
Expand Down Expand Up @@ -400,7 +400,7 @@ def test_fuzz_no_crash(self, mock_get_timeout):
testcase_path = setup_testcase_and_corpus('empty', 'corpus', fuzz=True)
output = run_launcher(testcase_path, 'test_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -i/corpus '
'Command: {0}/afl-fuzz -Sdefault -i/corpus '
'-o/afl_output_dir -mnone {0}/test_fuzzer '
'2147483647'.format(DATA_DIRECTORY), output)

Expand All @@ -419,7 +419,7 @@ def test_fuzz_crash(self, mock_get_timeout):

output = run_launcher(testcase_path, 'easy_crash_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -i/corpus '
'Command: {0}/afl-fuzz -Sdefault -i/corpus '
'-o/afl_output_dir -mnone '
'{0}/easy_crash_fuzzer 2147483647'.format(DATA_DIRECTORY), output)

Expand All @@ -445,7 +445,7 @@ def test_fuzz_merge(self, mock_get_timeout):

output = run_launcher(testcase_path, 'test_fuzzer')
self.assertIn(
'Command: {0}/afl-fuzz -d -i/redundant_corpus '
'Command: {0}/afl-fuzz -Sdefault -i/redundant_corpus '
'-o/afl_output_dir -mnone '
'{0}/test_fuzzer 2147483647'.format(DATA_DIRECTORY), output)

Expand Down
33 changes: 4 additions & 29 deletions src/python/tests/core/bot/fuzzers/afl/afl_launcher_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class LauncherTestBase(fake_filesystem_unittest.TestCase):
TARGET_OPTIONS_PATH = TARGET_PATH + '.options'
TEMP_DIR = '/tmp'
OUTPUT_DIR = '/tmp/afl_output_dir'
CRASHES_DIR = '/tmp/afl_output_dir/crashes'
CRASHES_DIR = '/tmp/afl_output_dir/default/crashes'
DEFAULT_INPUT_DIR_CONTENTS = [fuzzer.AFL_DUMMY_INPUT]
DATA_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data')

Expand Down Expand Up @@ -212,31 +212,6 @@ def _create_oversized_file(self, directory=None, filename=None):
file_contents = 'a' * (1024 * 1024)
self._create_file(filename, directory=directory, contents=file_contents)

def test__decide_skip_deterministic(self):
"""Test the AflFuzzInputDirectory._decide_skip_deterministic() method."""

# Test that skip_deterministic is set to False when there is no real
# corpus.
afl_input = self._new_afl_input()
self.assertFalse(afl_input.skip_deterministic)

# Test that skip_deterministic is set to False when there are a small number
# of files in the corpus.
num_files = 3
for file_num in range(num_files):
self._create_file('file' + str(file_num))

afl_input = self._new_afl_input()
self.assertFalse(afl_input.skip_deterministic)

# Test that skip_deterministic is set to False when the "small number" of
# files permitted for determnisitic steps is exceeded.
for file_num in range(num_files, afl_input.MIN_INPUTS_FOR_SKIP):
self._create_file('file' + str(file_num))

afl_input = self._new_afl_input()
self.assertTrue(afl_input.skip_deterministic)

def test_create_new_if_needed(self):
"""Test that the AflFuzzInputDirectory.create_new_if_needed() method works
as expected for oversized inputs and normal sized inputs."""
Expand Down Expand Up @@ -303,7 +278,7 @@ def test_corpus_subset(self):

class AflFuzzOutputDirectoryTest(LauncherTestBase):
"""Test the launcher.AflFuzzOutputDirectory class."""
QUEUE_DIR = '/tmp/afl_output_dir/queue'
QUEUE_DIR = '/tmp/afl_output_dir/default/queue'

# Note that this is just a file that is part of the input corpus.
# It isn't an AFL testcase.
Expand All @@ -318,8 +293,8 @@ def setUp(self):
super().setUp()
# Creates self.OUTPUT_DIR.
self.afl_output = launcher.AflFuzzOutputDirectory()
os.mkdir(self.CRASHES_DIR)
os.mkdir(self.QUEUE_DIR)
os.makedirs(self.CRASHES_DIR)
os.makedirs(self.QUEUE_DIR)

self.input_testcase_path, input_testcase_obj = self._create_file(
self.INPUT_TESTCASE_FILENAME)
Expand Down