diff options
Diffstat (limited to 'infra/cifuzz/run_fuzzers.py')
-rw-r--r-- | infra/cifuzz/run_fuzzers.py | 153 |
1 files changed, 123 insertions, 30 deletions
diff --git a/infra/cifuzz/run_fuzzers.py b/infra/cifuzz/run_fuzzers.py index 513cfb6fa..67c4c66fd 100644 --- a/infra/cifuzz/run_fuzzers.py +++ b/infra/cifuzz/run_fuzzers.py @@ -21,7 +21,9 @@ import time import clusterfuzz_deployment import fuzz_target +import generate_coverage_report import stack_parser +import workspace_utils # pylint: disable=wrong-import-position,import-error sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -41,12 +43,17 @@ class BaseFuzzTargetRunner: def __init__(self, config): self.config = config + self.workspace = workspace_utils.Workspace(config) self.clusterfuzz_deployment = ( - clusterfuzz_deployment.get_clusterfuzz_deployment(self.config)) + clusterfuzz_deployment.get_clusterfuzz_deployment( + self.config, self.workspace)) + # Set by the initialize method. - self.out_dir = None self.fuzz_target_paths = None - self.artifacts_dir = None + + def get_fuzz_targets(self): + """Returns fuzz targets in out directory.""" + return utils.get_fuzz_targets(self.workspace.out) def initialize(self): """Initialization method. Must be called before calling run_fuzz_targets. @@ -64,55 +71,51 @@ class BaseFuzzTargetRunner: self.config.fuzz_seconds) return False - self.out_dir = os.path.join(self.config.workspace, 'out') - if not os.path.exists(self.out_dir): - logging.error('Out directory: %s does not exist.', self.out_dir) + if not os.path.exists(self.workspace.out): + logging.error('Out directory: %s does not exist.', self.workspace.out) return False - self.artifacts_dir = os.path.join(self.out_dir, 'artifacts') - if not os.path.exists(self.artifacts_dir): - os.mkdir(self.artifacts_dir) - elif (not os.path.isdir(self.artifacts_dir) or - os.listdir(self.artifacts_dir)): + if not os.path.exists(self.workspace.artifacts): + os.makedirs(self.workspace.artifacts) + elif (not os.path.isdir(self.workspace.artifacts) or + os.listdir(self.workspace.artifacts)): logging.error('Artifacts path: %s exists and is not an empty directory.', - self.artifacts_dir) + self.workspace.artifacts) return False - self.fuzz_target_paths = utils.get_fuzz_targets(self.out_dir) + self.fuzz_target_paths = self.get_fuzz_targets() logging.info('Fuzz targets: %s', self.fuzz_target_paths) if not self.fuzz_target_paths: logging.error('No fuzz targets were found in out directory: %s.', - self.out_dir) + self.workspace.out) return False return True + def cleanup_after_fuzz_target_run(self, fuzz_target_obj): # pylint: disable=no-self-use + """Cleans up after running |fuzz_target_obj|.""" + raise NotImplementedError('Child class must implement method.') + def run_fuzz_target(self, fuzz_target_obj): # pylint: disable=no-self-use """Fuzzes with |fuzz_target_obj| and returns the result.""" - # TODO(metzman): Make children implement this so that the batch runner can - # do things differently. - result = fuzz_target_obj.fuzz() - fuzz_target_obj.free_disk_if_needed() - return result + raise NotImplementedError('Child class must implement method.') @property def quit_on_bug_found(self): """Property that is checked to determine if fuzzing should quit after first bug is found.""" - raise NotImplementedError('Child class must implement method') + raise NotImplementedError('Child class must implement method.') def get_fuzz_target_artifact(self, target, artifact_name): """Returns the path of a fuzzing artifact named |artifact_name| for |fuzz_target|.""" - artifact_name = '{target_name}-{sanitizer}-{artifact_name}'.format( - target_name=target.target_name, - sanitizer=self.config.sanitizer, - artifact_name=artifact_name) - return os.path.join(self.artifacts_dir, artifact_name) + artifact_name = (f'{target.target_name}-{self.config.sanitizer}-' + f'{artifact_name}') + return os.path.join(self.workspace.artifacts, artifact_name) def create_fuzz_target_obj(self, target_path, run_seconds): """Returns a fuzz target object.""" - return fuzz_target.FuzzTarget(target_path, run_seconds, self.out_dir, + return fuzz_target.FuzzTarget(target_path, run_seconds, self.workspace, self.clusterfuzz_deployment, self.config) def run_fuzz_targets(self): @@ -134,6 +137,7 @@ class BaseFuzzTargetRunner: target = self.create_fuzz_target_obj(target_path, run_seconds) start_time = time.time() result = self.run_fuzz_target(target) + self.cleanup_after_fuzz_target_run(target) # It's OK if this goes negative since we take max when determining # run_seconds. @@ -162,6 +166,60 @@ class BaseFuzzTargetRunner: return bug_found +class PruneTargetRunner(BaseFuzzTargetRunner): + """Runner that prunes corpora.""" + + @property + def quit_on_bug_found(self): + return False + + def run_fuzz_target(self, fuzz_target_obj): + """Prunes with |fuzz_target_obj| and returns the result.""" + result = fuzz_target_obj.prune() + logging.debug('Corpus path contents: %s.', os.listdir(result.corpus_path)) + self.clusterfuzz_deployment.upload_corpus(fuzz_target_obj.target_name, + result.corpus_path, + replace=True) + return result + + def cleanup_after_fuzz_target_run(self, fuzz_target_obj): # pylint: disable=no-self-use + """Cleans up after pruning with |fuzz_target_obj|.""" + fuzz_target_obj.free_disk_if_needed() + + +class CoverageTargetRunner(BaseFuzzTargetRunner): + """Runner that runs the 'coverage' command.""" + + @property + def quit_on_bug_found(self): + raise NotImplementedError('Not implemented for CoverageTargetRunner.') + + def get_fuzz_targets(self): + """Returns fuzz targets in out directory.""" + # We only want fuzz targets from the root because during the coverage build, + # a lot of the image's filesystem is copied into /out for the purpose of + # generating coverage reports. + # TOOD(metzman): Figure out if top_level_only should be the only behavior + # for this function. + return utils.get_fuzz_targets(self.workspace.out, top_level_only=True) + + def run_fuzz_targets(self): + """Generates a coverage report. Always returns False since it never finds + any bugs.""" + generate_coverage_report.generate_coverage_report( + self.fuzz_target_paths, self.workspace, self.clusterfuzz_deployment, + self.config) + return False + + def run_fuzz_target(self, fuzz_target_obj): # pylint: disable=no-self-use + """Fuzzes with |fuzz_target_obj| and returns the result.""" + raise NotImplementedError('Not implemented for CoverageTargetRunner.') + + def cleanup_after_fuzz_target_run(self, fuzz_target_obj): # pylint: disable=no-self-use + """Cleans up after running |fuzz_target_obj|.""" + raise NotImplementedError('Not implemented for CoverageTargetRunner.') + + class CiFuzzTargetRunner(BaseFuzzTargetRunner): """Runner for fuzz targets used in CI (patch-fuzzing) context.""" @@ -169,6 +227,13 @@ class CiFuzzTargetRunner(BaseFuzzTargetRunner): def quit_on_bug_found(self): return True + def cleanup_after_fuzz_target_run(self, fuzz_target_obj): # pylint: disable=no-self-use + """Cleans up after running |fuzz_target_obj|.""" + fuzz_target_obj.free_disk_if_needed() + + def run_fuzz_target(self, fuzz_target_obj): # pylint: disable=no-self-use + return fuzz_target_obj.fuzz() + class BatchFuzzTargetRunner(BaseFuzzTargetRunner): """Runner for fuzz targets used in batch fuzzing context.""" @@ -177,14 +242,42 @@ class BatchFuzzTargetRunner(BaseFuzzTargetRunner): def quit_on_bug_found(self): return False + def run_fuzz_target(self, fuzz_target_obj): + """Fuzzes with |fuzz_target_obj| and returns the result.""" + result = fuzz_target_obj.fuzz() + logging.debug('Corpus path contents: %s.', os.listdir(result.corpus_path)) + self.clusterfuzz_deployment.upload_corpus(fuzz_target_obj.target_name, + result.corpus_path) + return result + + def cleanup_after_fuzz_target_run(self, fuzz_target_obj): + """Cleans up after running |fuzz_target_obj|.""" + # This must be done after we upload the corpus, otherwise it will be deleted + # before we get a chance to upload it. We can't delete the fuzz target + # because it is needed when we upload the build. + fuzz_target_obj.free_disk_if_needed(delete_fuzz_target=False) + + def run_fuzz_targets(self): + result = super().run_fuzz_targets() + self.clusterfuzz_deployment.upload_crashes() + return result + + +_RUN_FUZZERS_MODE_RUNNER_MAPPING = { + 'batch': BatchFuzzTargetRunner, + 'coverage': CoverageTargetRunner, + 'prune': PruneTargetRunner, + 'ci': CiFuzzTargetRunner, +} + def get_fuzz_target_runner(config): """Returns a fuzz target runner object based on the run_fuzzers_mode of |config|.""" - logging.info('RUN_FUZZERS_MODE is: %s', config.run_fuzzers_mode) - if config.run_fuzzers_mode == 'batch': - return BatchFuzzTargetRunner(config) - return CiFuzzTargetRunner(config) + runner = _RUN_FUZZERS_MODE_RUNNER_MAPPING[config.run_fuzzers_mode](config) + logging.info('RUN_FUZZERS_MODE is: %s. Runner: %s.', config.run_fuzzers_mode, + runner) + return runner def run_fuzzers(config): # pylint: disable=too-many-locals |