def libfuzzer( self, project: str, name: str, build: str, pool_name: PoolName, *, reports: Optional[List[str]] = None, crashes: Optional[List[File]] = None, target_exe: File = File("fuzz.exe"), tags: Optional[Dict[str, str]] = None, notification_config: Optional[NotificationConfig] = None, target_env: Optional[Dict[str, str]] = None, setup_dir: Optional[Directory] = None, reboot_after_setup: bool = False, target_options: Optional[List[str]] = None, dryrun: bool = False, duration: int = 24, crash_report_timeout: Optional[int] = None, debug: Optional[List[TaskDebugFlag]] = None, check_retry_count: Optional[int] = None, check_fuzzer_help: bool = True, delete_input_container: bool = True, check_regressions: bool = False, ) -> None: """ libfuzzer regression task :param File crashes: Specify crashing input files to check in the regression task :param str reports: Specify specific report names to verify in the regression task :param bool check_regressions: Specify if exceptions should be thrown on finding crash regressions :param bool delete_input_container: Specify wether or not to delete the input container """ self._create_job( TaskType.libfuzzer_regression, project, name, build, pool_name, crashes=crashes, reports=reports, target_exe=target_exe, tags=tags, notification_config=notification_config, target_env=target_env, setup_dir=setup_dir, reboot_after_setup=reboot_after_setup, target_options=target_options, dryrun=dryrun, duration=duration, crash_report_timeout=crash_report_timeout, debug=debug, check_retry_count=check_retry_count, check_fuzzer_help=check_fuzzer_help, delete_input_container=delete_input_container, check_regressions=check_regressions, )
def _run(self, target_os: OS, test_id: UUID, base: Directory, target: str) -> None: pool = PoolName(f"{target}-{target_os.name}-{test_id}") self.onefuzz.pools.create(pool, target_os) self.onefuzz.scalesets.create(pool, 5) broken = File(os.path.join(base, target, "broken.exe")) fixed = File(os.path.join(base, target, "fixed.exe")) self.logger.info("starting first build") self._run_job(test_id, pool, target, broken, 1) self.logger.info("starting second build") job = self._run_job(test_id, pool, target, fixed, 2) if self._check_regression(job): raise Exception("fixed binary should be a no repro") self.logger.info("starting third build") job = self._run_job(test_id, pool, target, broken, 3) if not self._check_regression(job): raise Exception("broken binary should be a crash report") self.onefuzz.pools.shutdown(pool, now=True)
def _copy_exe(self, src_sas: str, dst_sas: str, target_exe: File) -> None: files: List[File] = [target_exe] pdb_path = os.path.splitext(target_exe)[0] + ".pdb" if os.path.exists(str(pdb_path)): files.append(File(pdb_path)) for path in files: filename = os.path.basename(path) src_url = container_file_path(src_sas, filename) dst_url = container_file_path(dst_sas, filename) cmd = [ "azcopy", "copy", src_url, dst_url, ] self.logger.info("uploading %s", path) subprocess.check_output(cmd)
def _copy_exe(self, src_sas: str, dst_sas: str, target_exe: File) -> None: files: List[File] = [target_exe] pdb_path = os.path.splitext(target_exe)[0] + ".pdb" if os.path.exists(str(pdb_path)): files.append(File(pdb_path)) for path in files: filename = os.path.basename(path) src_url = container_file_path(src_sas, filename) dst_url = container_file_path(dst_sas, filename) cmd = [ "azcopy", "copy", src_url, dst_url, ] self.logger.info("uploading %s", path) # security note: the source and destination container sas URLS # are considerd trusted from the service subprocess.check_output(cmd) # nosec
def qemu_user( self, project: str, name: str, build: str, pool_name: PoolName, *, arch: QemuArch = QemuArch.aarch64, target_exe: File = File("fuzz.exe"), sysroot: Optional[File] = None, vm_count: int = 1, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, target_workers: Optional[int] = 1, target_options: Optional[List[str]] = None, fuzzing_target_options: Optional[List[str]] = None, target_env: Optional[Dict[str, str]] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, existing_inputs: Optional[Container] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, colocate_all_tasks: bool = False, crash_report_timeout: Optional[int] = 1, check_retry_count: Optional[int] = 300, check_fuzzer_help: bool = True, ) -> Optional[Job]: """ libfuzzer tasks, wrapped via qemu-user (PREVIEW FEATURE) """ self.logger.warning( "qemu_user jobs are a preview feature and may change in the future" ) pool = self.onefuzz.pools.get(pool_name) if pool.os != OS.linux: raise Exception( "libfuzzer qemu-user jobs are only compatible with Linux") self._check_is_libfuzzer(target_exe) if target_options is None: target_options = [] # disable detect_leaks, as this is non-functional on cross-compile targets if target_env is None: target_env = {} target_env["ASAN_OPTIONS"] = (target_env.get("ASAN_OPTIONS", "") + ":detect_leaks=0") helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.inputs, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ContainerType.no_repro, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.inputs] = existing_inputs else: helper.define_containers(ContainerType.inputs) fuzzer_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.inputs, helper.containers[ContainerType.inputs]), ] helper.create_containers() target_exe_blob_name = helper.setup_relative_blob_name( target_exe, None) wrapper_name = File(target_exe_blob_name + "-wrapper.sh") with tempfile.TemporaryDirectory() as tempdir: if sysroot: setup_path = File(os.path.join(tempdir, "setup.sh")) with open(setup_path, "w", newline="\n") as handle: sysroot_filename = helper.setup_relative_blob_name( sysroot, None) handle.write( "#!/bin/bash\n" "set -ex\n" "sudo apt-get install -y qemu-user g++-aarch64-linux-gnu libasan5-arm64-cross\n" 'cd $(dirname "$(readlink -f "$0")")\n' "mkdir -p sysroot\n" "tar -C sysroot -zxvf %s\n" % sysroot_filename) wrapper_path = File(os.path.join(tempdir, wrapper_name)) with open(wrapper_path, "w", newline="\n") as handle: handle.write( "#!/bin/bash\n" 'SETUP_DIR=$(dirname "$(readlink -f "$0")")\n' "qemu-%s -L $SETUP_DIR/sysroot $SETUP_DIR/%s $*" % (arch.name, target_exe_blob_name)) upload_files = [setup_path, wrapper_path, sysroot] else: setup_path = File(os.path.join(tempdir, "setup.sh")) with open(setup_path, "w", newline="\n") as handle: handle.write( "#!/bin/bash\n" "set -ex\n" "sudo apt-get install -y qemu-user g++-aarch64-linux-gnu libasan5-arm64-cross\n" ) wrapper_path = File(os.path.join(tempdir, wrapper_name)) with open(wrapper_path, "w", newline="\n") as handle: handle.write( "#!/bin/bash\n" 'SETUP_DIR=$(dirname "$(readlink -f "$0")")\n' "qemu-%s -L /usr/%s-linux-gnu $SETUP_DIR/%s $*" % (arch.name, arch.name, target_exe_blob_name)) upload_files = [setup_path, wrapper_path] helper.upload_setup(None, target_exe, upload_files) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) # Build `target_options` for the `libfuzzer_fuzz` task. # # This allows passing arguments like `-runs` to the target only when # invoked in persistent fuzzing mode, and not test case repro mode. libfuzzer_fuzz_target_options = target_options.copy() if fuzzing_target_options: libfuzzer_fuzz_target_options += fuzzing_target_options self.logger.info("creating libfuzzer_fuzz task") fuzzer_task = self.onefuzz.tasks.create( helper.job.job_id, TaskType.libfuzzer_fuzz, wrapper_name, fuzzer_containers, pool_name=pool_name, reboot_after_setup=reboot_after_setup, duration=duration, vm_count=vm_count, target_options=libfuzzer_fuzz_target_options, target_env=target_env, target_workers=target_workers, tags=tags, debug=debug, ensemble_sync_delay=ensemble_sync_delay, expect_crash_on_failure=False, check_fuzzer_help=check_fuzzer_help, ) report_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.reports, helper.containers[ContainerType.reports]), ( ContainerType.unique_reports, helper.containers[ContainerType.unique_reports], ), (ContainerType.no_repro, helper.containers[ContainerType.no_repro]), ] self.logger.info("creating libfuzzer_crash_report task") self.onefuzz.tasks.create( helper.job.job_id, TaskType.libfuzzer_crash_report, wrapper_name, report_containers, pool_name=pool_name, duration=duration, vm_count=1, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, tags=tags, prereq_tasks=[fuzzer_task.task_id], target_timeout=crash_report_timeout, check_retry_count=check_retry_count, debug=debug, colocate=colocate_all_tasks, expect_crash_on_failure=False, check_fuzzer_help=check_fuzzer_help, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def dotnet( self, project: str, name: str, build: str, pool_name: PoolName, *, setup_dir: Directory, target_harness: str, vm_count: int = 1, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, target_workers: Optional[int] = None, target_options: Optional[List[str]] = None, fuzzing_target_options: Optional[List[str]] = None, target_env: Optional[Dict[str, str]] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, existing_inputs: Optional[Container] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, check_fuzzer_help: bool = True, expect_crash_on_failure: bool = False, ) -> Optional[Job]: """ libfuzzer-dotnet task """ harness = "libfuzzer-dotnet" pool = self.onefuzz.pools.get(pool_name) if pool.os != OS.linux: raise Exception( "libfuzzer-dotnet jobs are only compatable on linux") target_exe = File(os.path.join(setup_dir, harness)) if not os.path.exists(target_exe): raise Exception(f"missing harness: {target_exe}") assembly_path = os.path.join(setup_dir, target_harness) if not os.path.exists(assembly_path): raise Exception(f"missing assembly: {assembly_path}") self._check_is_libfuzzer(target_exe) if target_options is None: target_options = [] target_options = ["--target_path={setup_dir}/" f"{target_harness}"] + target_options helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.inputs, ContainerType.crashes, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.inputs] = existing_inputs else: helper.define_containers(ContainerType.inputs) fuzzer_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.inputs, helper.containers[ContainerType.inputs]), ] helper.create_containers() helper.upload_setup(setup_dir, target_exe) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) # Build `target_options` for the `libfuzzer_fuzz` task. # # This allows passing arguments like `-runs` to the target only when # invoked in persistent fuzzing mode, and not test case repro mode. libfuzzer_fuzz_target_options = target_options.copy() if fuzzing_target_options: libfuzzer_fuzz_target_options += fuzzing_target_options self.onefuzz.tasks.create( helper.job.job_id, TaskType.libfuzzer_fuzz, harness, fuzzer_containers, pool_name=pool_name, reboot_after_setup=reboot_after_setup, duration=duration, vm_count=vm_count, target_options=libfuzzer_fuzz_target_options, target_env=target_env, target_workers=target_workers, tags=tags, debug=debug, ensemble_sync_delay=ensemble_sync_delay, check_fuzzer_help=check_fuzzer_help, expect_crash_on_failure=expect_crash_on_failure, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def merge( self, project: str, name: str, build: str, pool_name: PoolName, *, target_exe: File = File("fuzz.exe"), setup_dir: Optional[Directory] = None, inputs: Optional[Directory] = None, output_container: Optional[Container] = None, reboot_after_setup: bool = False, duration: int = 24, target_options: Optional[List[str]] = None, target_env: Optional[Dict[str, str]] = None, check_retry_count: Optional[int] = None, crash_report_timeout: Optional[int] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, extra_files: Optional[List[File]] = None, existing_inputs: Optional[List[Container]] = None, dryrun: bool = False, notification_config: Optional[NotificationConfig] = None, debug: Optional[List[TaskDebugFlag]] = None, preserve_existing_outputs: bool = False, check_fuzzer_help: bool = True, ) -> Optional[Job]: """ libfuzzer merge task """ # verify containers exist if existing_inputs: for existing_container in existing_inputs: self.onefuzz.containers.get(existing_container) elif not inputs: self.logger.error( "please specify either an input folder or at least one existing inputs container" ) return None if dryrun: return None self.logger.info("creating libfuzzer merge from template") self._check_is_libfuzzer(target_exe) helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers(ContainerType.setup, ) if inputs: helper.define_containers(ContainerType.inputs) if output_container: if self.onefuzz.containers.get(output_container): helper.define_containers(ContainerType.unique_inputs) helper.create_containers() helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe, extra_files) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) target_exe_blob_name = helper.setup_relative_blob_name( target_exe, setup_dir) merge_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), ( ContainerType.unique_inputs, output_container or helper.containers[ContainerType.unique_inputs], ), ] if inputs: merge_containers.append((ContainerType.inputs, helper.containers[ContainerType.inputs])) if existing_inputs: for existing_container in existing_inputs: merge_containers.append( (ContainerType.inputs, existing_container)) self.logger.info("creating libfuzzer_merge task") self.onefuzz.tasks.create( helper.job.job_id, TaskType.libfuzzer_merge, target_exe_blob_name, merge_containers, pool_name=pool_name, duration=duration, vm_count=1, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, tags=tags, target_timeout=crash_report_timeout, check_retry_count=check_retry_count, debug=debug, preserve_existing_outputs=preserve_existing_outputs, check_fuzzer_help=check_fuzzer_help, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def basic( self, project: str, name: str, build: str, pool_name: PoolName, *, target_exe: File = File("fuzz.exe"), setup_dir: Optional[Directory] = None, vm_count: int = 2, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, target_workers: Optional[int] = None, target_options: Optional[List[str]] = None, fuzzing_target_options: Optional[List[str]] = None, target_env: Optional[Dict[str, str]] = None, target_timeout: Optional[int] = None, check_retry_count: Optional[int] = None, crash_report_timeout: Optional[int] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, extra_files: Optional[List[File]] = None, existing_inputs: Optional[Container] = None, readonly_inputs: Optional[Container] = None, dryrun: bool = False, notification_config: Optional[NotificationConfig] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, colocate_all_tasks: bool = False, colocate_secondary_tasks: bool = True, check_fuzzer_help: bool = True, expect_crash_on_failure: bool = False, minimized_stack_depth: Optional[int] = None, coverage_filter: Optional[File] = None, ) -> Optional[Job]: """ Basic libfuzzer job :param bool ensemble_sync_delay: Specify duration between syncing inputs during ensemble fuzzing (0 to disable). """ # verify containers exist if existing_inputs: self.onefuzz.containers.get(existing_inputs) if readonly_inputs: self.onefuzz.containers.get(readonly_inputs) if dryrun: return None self.logger.info("creating libfuzzer from template") self._check_is_libfuzzer(target_exe) helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.inputs, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ContainerType.unique_inputs, ContainerType.no_repro, ContainerType.coverage, ContainerType.unique_inputs, ContainerType.regression_reports, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.inputs] = existing_inputs else: helper.define_containers(ContainerType.inputs) if readonly_inputs: self.onefuzz.containers.get(readonly_inputs) helper.containers[ContainerType.readonly_inputs] = readonly_inputs helper.create_containers() helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe, extra_files) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) target_exe_blob_name = helper.setup_relative_blob_name( target_exe, setup_dir) if coverage_filter: coverage_filter_blob_name: Optional[ str] = helper.setup_relative_blob_name(coverage_filter, setup_dir) else: coverage_filter_blob_name = None self._create_tasks( job=helper.job, containers=helper.containers, pool_name=pool_name, target_exe=target_exe_blob_name, vm_count=vm_count, reboot_after_setup=reboot_after_setup, duration=duration, target_workers=target_workers, target_options=target_options, fuzzing_target_options=fuzzing_target_options, target_env=target_env, tags=helper.tags, crash_report_timeout=crash_report_timeout, check_retry_count=check_retry_count, debug=debug, ensemble_sync_delay=ensemble_sync_delay, colocate_all_tasks=colocate_all_tasks, colocate_secondary_tasks=colocate_secondary_tasks, check_fuzzer_help=check_fuzzer_help, expect_crash_on_failure=expect_crash_on_failure, minimized_stack_depth=minimized_stack_depth, coverage_filter=coverage_filter_blob_name, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def libfuzzer( self, project: str, build: str, pool_name: str, duration: int = 24, tags: Optional[Dict[str, str]] = None, dryrun: bool = False, max_target_count: int = 20, sync_inputs: bool = False, notification_config: Optional[NotificationConfig] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, ) -> None: """ OssFuzz style libfuzzer jobs :param bool ensemble_sync_delay: Specify duration between syncing inputs during ensemble fuzzing (0 to disable). """ fuzzers = sorted(glob.glob("*fuzzer")) if fuzzers: platform = OS.linux else: platform = OS.windows fuzzers = sorted(glob.glob("*fuzzer.exe")) if dryrun: return containers = self._containers(project, build, platform) container_sas = {} for name in containers: self.logger.info("creating container: %s", name) sas_url = self.onefuzz.containers.create(containers[name]).sas_url container_sas[name] = sas_url self.logger.info("uploading build artifacts") subprocess.check_output( [ "azcopy", "sync", ".", container_sas["build"], "--exclude-pattern", "*fuzzer_seed_corpus.zip", ] ) subprocess.check_output( [ "azcopy", "sync", ".", container_sas["base"], '--include-pattern="*.so;*.dll;*.sh;*.ps1', ] ) if max_target_count: fuzzers = fuzzers[:max_target_count] base_helper = JobHelper( self.onefuzz, self.logger, project, build, "base", duration, pool_name=pool_name, target_exe=File(fuzzers[0]), ) base_helper.platform = platform helpers = [] for fuzzer in [File(x) for x in fuzzers]: fuzzer_name = fuzzer.replace(".exe", "").replace("_fuzzer", "") self.logger.info("creating tasks for %s", fuzzer) self.onefuzz.template.libfuzzer._check_is_libfuzzer(fuzzer) helper = JobHelper( self.onefuzz, self.logger, project, fuzzer_name, build, duration, job=base_helper.job, pool_name=pool_name, target_exe=fuzzer, ) helper.platform = platform helper.add_tags(tags) helper.platform = base_helper.platform helper.job = base_helper.job helper.define_containers( ContainerType.setup, ContainerType.inputs, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ContainerType.no_repro, ContainerType.coverage, ) helper.create_containers() helper.setup_notifications(notification_config) dst_sas = self.onefuzz.containers.get( helper.containers[ContainerType.setup] ).sas_url self._copy_exe(container_sas["build"], dst_sas, File(fuzzer)) self._copy_all(container_sas["base"], dst_sas) zip_name = fuzzer.replace(".exe", "").replace("_fuzzer", "_seed_corpus.zip") if os.path.exists(zip_name) and sync_inputs: self.logger.info("uploading seeds") helper.upload_inputs_zip(File(zip_name)) owners_path = File("%s.msowners" % fuzzer.replace(".exe", "")) options_path = File("%s.options" % fuzzer.replace(".exe", "")) target_env, target_options = self._options(options_path) helper.add_tags(self._owners(owners_path)) # All fuzzers are copied to the setup container root. # # Cast because `glob()` returns `str`. fuzzer_blob_name = helper.target_exe_blob_name(fuzzer, None) self.onefuzz.template.libfuzzer._create_tasks( job=base_helper.job, containers=helper.containers, pool_name=pool_name, target_exe=fuzzer_blob_name, vm_count=VM_COUNT, duration=duration, target_options=target_options, target_env=target_env, tags=helper.tags, debug=debug, ensemble_sync_delay=ensemble_sync_delay, ) helpers.append(helper) base_helper.wait()
def _create_job( self, task_type: TaskType, project: str, name: str, build: str, pool_name: PoolName, *, crashes: Optional[List[File]] = None, reports: Optional[List[str]] = None, target_exe: File = File("fuzz.exe"), tags: Optional[Dict[str, str]] = None, notification_config: Optional[NotificationConfig] = None, target_env: Optional[Dict[str, str]] = None, setup_dir: Optional[Directory] = None, reboot_after_setup: bool = False, target_options: Optional[List[str]] = None, dryrun: bool = False, duration: int = 24, crash_report_timeout: Optional[int] = None, debug: Optional[List[TaskDebugFlag]] = None, check_retry_count: Optional[int] = None, check_fuzzer_help: bool = True, delete_input_container: bool = True, check_regressions: bool = False, ) -> None: if dryrun: return None self.logger.info("creating regression task from template") helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.define_containers( ContainerType.setup, ContainerType.crashes, ContainerType.reports, ContainerType.no_repro, ContainerType.unique_reports, ContainerType.regression_reports, ) containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.reports, helper.containers[ContainerType.reports]), (ContainerType.no_repro, helper.containers[ContainerType.no_repro]), ( ContainerType.unique_reports, helper.containers[ContainerType.unique_reports], ), ( ContainerType.regression_reports, helper.containers[ContainerType.regression_reports], ), ] if crashes: helper.containers[ ContainerType. readonly_inputs] = helper.get_unique_container_name( ContainerType.readonly_inputs) containers.append(( ContainerType.readonly_inputs, helper.containers[ContainerType.readonly_inputs], )) helper.create_containers() if crashes: for file in crashes: self.onefuzz.containers.files.upload_file( helper.containers[ContainerType.readonly_inputs], file) helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe) target_exe_blob_name = helper.target_exe_blob_name( target_exe, setup_dir) self.logger.info("creating regression task") task = self.onefuzz.tasks.create( helper.job.job_id, task_type, target_exe_blob_name, containers, pool_name=pool_name, duration=duration, vm_count=1, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, tags=tags, target_timeout=crash_report_timeout, check_retry_count=check_retry_count, debug=debug, check_fuzzer_help=check_fuzzer_help, report_list=reports, ) helper.wait_for_stopped = check_regressions self.logger.info("done creating tasks") helper.wait() if check_regressions: task = self.onefuzz.tasks.get(task.task_id) if task.error: raise Exception("task failed: %s", task.error) container = helper.containers[ContainerType.regression_reports] for filename in self.onefuzz.containers.files.list( container).files: self.logger.info("checking file: %s", filename) if self._check_regression(container, File(filename)): raise Exception(f"regression identified: {filename}") self.logger.info("no regressions") if (delete_input_container and ContainerType.readonly_inputs in helper.containers): helper.delete_container( helper.containers[ContainerType.readonly_inputs])
def basic( self, project: str, name: str, build: str, *, pool_name: str, target_exe: File = File("fuzz.exe"), setup_dir: Optional[Directory] = None, vm_count: int = 2, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, generator_exe: Optional[str] = None, target_options: List[str] = ["{input}"], target_env: Optional[Dict[str, str]] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, generator_options: Optional[List[str]] = None, radamsa_seed: Optional[int] = None, analyzer_exe: Optional[str] = None, analyzer_options: Optional[List[str]] = None, analyzer_env: Optional[Dict[str, str]] = None, existing_inputs: Optional[Container] = None, check_asan_log: bool = False, check_retry_count: Optional[int] = None, disable_check_debugger: bool = False, dryrun: bool = False, notification_config: Optional[NotificationConfig] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, ) -> Optional[Job]: """ Basic radamsa job :param bool ensemble_sync_delay: Specify duration between syncing inputs during ensemble fuzzing (0 to disable). """ if inputs is None and existing_inputs is None: raise Exception("radamsa requires inputs") if dryrun: return None # disable ensemble sync if only one VM is used if ensemble_sync_delay is None and vm_count == 1: ensemble_sync_delay = 0 self.logger.info("creating radamsa from template") helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ContainerType.no_repro, ContainerType.analysis, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.readonly_inputs] = existing_inputs else: helper.define_containers(ContainerType.readonly_inputs) helper.create_containers() helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe) if inputs: helper.upload_inputs(inputs, read_only=True) helper.wait_on(wait_for_files, wait_for_running) if ( len( self.onefuzz.containers.files.list( helper.containers[ContainerType.readonly_inputs] ).files ) == 0 ): raise Exception("Radamsa requires at least one input file") target_exe_blob_name = helper.target_exe_blob_name(target_exe, setup_dir) tools = Container( "radamsa-linux" if helper.platform == OS.linux else "radamsa-win64" ) if generator_exe is None: generator_exe = ( "{tools_dir}/radamsa" if helper.platform == OS.linux else "{tools_dir}\\radamsa.exe" ) rename_output = True if generator_options is None: generator_options, rename_output = ( [ "-H", "sha256", "-o", "{generated_inputs}/input-%h.%s", "-n", "100", "-r", "{input_corpus}", ], False, ) if radamsa_seed is not None: generator_options += ["--seed", str(radamsa_seed)] self.logger.info("creating radamsa task") containers = [ (ContainerType.tools, tools), (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), ( ContainerType.readonly_inputs, helper.containers[ContainerType.readonly_inputs], ), ] fuzzer_task = self.onefuzz.tasks.create( helper.job.job_id, TaskType.generic_generator, target_exe_blob_name, containers, pool_name=pool_name, duration=duration, vm_count=vm_count, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, generator_exe=generator_exe, generator_options=generator_options, check_asan_log=check_asan_log, check_debugger=not disable_check_debugger, tags=helper.tags, rename_output=rename_output, debug=debug, ensemble_sync_delay=ensemble_sync_delay, ) report_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.reports, helper.containers[ContainerType.reports]), ( ContainerType.unique_reports, helper.containers[ContainerType.unique_reports], ), (ContainerType.no_repro, helper.containers[ContainerType.no_repro]), ] self.logger.info("creating generic_crash_report task") self.onefuzz.tasks.create( helper.job.job_id, TaskType.generic_crash_report, target_exe_blob_name, report_containers, duration=duration, vm_count=1, pool_name=pool_name, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, tags=helper.tags, check_asan_log=check_asan_log, check_debugger=not disable_check_debugger, check_retry_count=check_retry_count, prereq_tasks=[fuzzer_task.task_id], debug=debug, ) if helper.platform == OS.windows: if analyzer_exe is None: analyzer_exe = "cdb.exe" if analyzer_options is None: analyzer_options = [ "-c", "!analyze;q", "-logo", "{output_dir}\\{input_file_name_no_ext}.report", "{target_exe}", "{target_options}", ] self.logger.info("creating custom analysis") analysis_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.tools, tools), (ContainerType.analysis, helper.containers[ContainerType.analysis]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), ] self.onefuzz.tasks.create( helper.job.job_id, TaskType.generic_analysis, target_exe_blob_name, analysis_containers, duration=duration, pool_name=pool_name, vm_count=vm_count, reboot_after_setup=reboot_after_setup, target_options=target_options, target_env=target_env, analyzer_exe=analyzer_exe, analyzer_options=analyzer_options, analyzer_env=analyzer_env, tags=helper.tags, prereq_tasks=[fuzzer_task.task_id], debug=debug, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def basic( self, project: str, name: str, build: str, *, pool_name: str, target_exe: File = File("fuzz.exe"), setup_dir: Optional[Directory] = None, vm_count: int = 2, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, target_options: Optional[List[str]] = None, supervisor_exe: str = "{tools_dir}/afl-fuzz", supervisor_options: List[str] = [ "-d", "-i", "{input_corpus}", "-o", "{runtime_dir}", "--", "{target_exe}", "{target_options}", ], supervisor_env: Optional[Dict[str, str]] = None, supervisor_input_marker: str = "@@", tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, afl_container: Optional[Container] = None, existing_inputs: Optional[Container] = None, dryrun: bool = False, notification_config: Optional[NotificationConfig] = None, debug: Optional[List[TaskDebugFlag]] = None, ensemble_sync_delay: Optional[int] = None, ) -> Optional[Job]: """ Basic AFL job :param Container afl_container: Specify the AFL container to use in the job :param bool ensemble_sync_delay: Specify duration between syncing inputs during ensemble fuzzing (0 to disable). """ if existing_inputs: self.onefuzz.containers.get(existing_inputs) if dryrun: return None # disable ensemble sync if only one VM is used if ensemble_sync_delay is None and vm_count == 1: ensemble_sync_delay = 0 self.logger.info("creating afl from template") target_options = target_options or ["{input}"] helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.inputs] = existing_inputs else: helper.define_containers(ContainerType.inputs) helper.create_containers() helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) if (len( self.onefuzz.containers.files.list( helper.containers[ContainerType.inputs]).files) == 0): raise Exception("AFL requires at least one input") target_exe_blob_name = helper.target_exe_blob_name( target_exe, setup_dir) if afl_container is None: afl_container = Container("afl-linux" if helper.platform == OS.linux else "afl-windows") # verify the AFL container exists self.onefuzz.containers.get(afl_container) containers = [ (ContainerType.tools, afl_container), (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.inputs, helper.containers[ContainerType.inputs]), ] self.logger.info("creating afl fuzz task") fuzzer_task = self.onefuzz.tasks.create( helper.job.job_id, TaskType.generic_supervisor, target_exe_blob_name, containers, pool_name=pool_name, duration=duration, vm_count=vm_count, reboot_after_setup=reboot_after_setup, target_options=target_options, supervisor_exe=supervisor_exe, supervisor_options=supervisor_options, supervisor_env=supervisor_env, supervisor_input_marker=supervisor_input_marker, stats_file="{runtime_dir}/fuzzer_stats", stats_format=StatsFormat.AFL, task_wait_for_files=ContainerType.inputs, tags=helper.tags, debug=debug, ensemble_sync_delay=ensemble_sync_delay, ) report_containers = [ (ContainerType.setup, helper.containers[ContainerType.setup]), (ContainerType.crashes, helper.containers[ContainerType.crashes]), (ContainerType.reports, helper.containers[ContainerType.reports]), ( ContainerType.unique_reports, helper.containers[ContainerType.unique_reports], ), ] self.logger.info("creating generic_crash_report task") self.onefuzz.tasks.create( helper.job.job_id, TaskType.generic_crash_report, target_exe_blob_name, report_containers, pool_name=pool_name, duration=duration, vm_count=1, reboot_after_setup=reboot_after_setup, target_options=target_options, check_debugger=True, tags=tags, prereq_tasks=[fuzzer_task.task_id], debug=debug, ) self.logger.info("done creating tasks") helper.wait() return helper.job
def launch(self, path: str) -> None: """ Launch all of the fuzzing templates """ for target, config in TARGETS.items(): if target not in self.targets: continue if config.os not in self.os: continue self.logger.info("launching: %s", target) setup = Directory(os.path.join( path, target)) if config.use_setup else None target_exe = File(os.path.join(path, target, config.target_exe)) inputs = (Directory(os.path.join(path, target, config.inputs)) if config.inputs else None) job: Optional[Job] = None if config.template == TemplateType.libfuzzer: job = self.of.template.libfuzzer.basic( self.project, target, BUILD, self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=1, vm_count=1, ) elif config.template == TemplateType.radamsa: job = self.of.template.radamsa.basic( self.project, target, BUILD, pool_name=self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, check_asan_log=config.check_asan_log or False, disable_check_debugger=config.disable_check_debugger or False, duration=1, vm_count=1, ) elif config.template == TemplateType.afl: job = self.of.template.afl.basic( self.project, target, BUILD, pool_name=self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=1, vm_count=1, ) else: raise NotImplementedError if not job: raise Exception("missing job") self.containers[job.job_id] = [] for task in self.of.tasks.list(job_id=job.job_id): self.tasks[task.task_id] = job.job_id self.containers[job.job_id] += [ ContainerWrapper(self.of.containers.get(x.name).sas_url) for x in task.config.containers if x.type in TARGETS[job.config.name].wait_for_files ] self.jobs[job.job_id] = job self.target_jobs[job.job_id] = target
def launch(self, path: Directory, *, os_list: List[OS], targets: List[str], duration=int) -> None: """ Launch all of the fuzzing templates """ for target, config in TARGETS.items(): if target not in targets: continue if config.os not in os_list: continue self.logger.info("launching: %s", target) setup = Directory(os.path.join( path, target)) if config.use_setup else None target_exe = File(os.path.join(path, target, config.target_exe)) inputs = (Directory(os.path.join(path, target, config.inputs)) if config.inputs else None) if setup and config.nested_setup_dir: setup = Directory(os.path.join(setup, config.nested_setup_dir)) job: Optional[Job] = None if config.template == TemplateType.libfuzzer: job = self.of.template.libfuzzer.basic( self.project, target, BUILD, self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, reboot_after_setup=config.reboot_after_setup or False, ) elif config.template == TemplateType.libfuzzer_dotnet: if setup is None: raise Exception("setup required for libfuzzer_dotnet") job = self.of.template.libfuzzer.dotnet( self.project, target, BUILD, self.pools[config.os].name, target_harness=config.target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, ) elif config.template == TemplateType.libfuzzer_qemu_user: job = self.of.template.libfuzzer.qemu_user( self.project, target, BUILD, self.pools[config.os].name, inputs=inputs, target_exe=target_exe, duration=duration, vm_count=1, ) elif config.template == TemplateType.radamsa: job = self.of.template.radamsa.basic( self.project, target, BUILD, pool_name=self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, check_asan_log=config.check_asan_log or False, disable_check_debugger=config.disable_check_debugger or False, duration=duration, vm_count=1, ) elif config.template == TemplateType.afl: job = self.of.template.afl.basic( self.project, target, BUILD, pool_name=self.pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, ) else: raise NotImplementedError if not job: raise Exception("missing job")
def basic( self, project: str, name: str, build: str, pool_name: str, *, target_exe: File = File("fuzz.exe"), setup_dir: Optional[Directory] = None, vm_count: int = 2, inputs: Optional[Directory] = None, reboot_after_setup: bool = False, duration: int = 24, target_workers: Optional[int] = None, target_options: Optional[List[str]] = None, target_env: Optional[Dict[str, str]] = None, check_retry_count: Optional[int] = None, crash_report_timeout: Optional[int] = None, tags: Optional[Dict[str, str]] = None, wait_for_running: bool = False, wait_for_files: Optional[List[ContainerType]] = None, extra_files: Optional[List[File]] = None, existing_inputs: Optional[Container] = None, dryrun: bool = False, notification_config: Optional[NotificationConfig] = None, ) -> None: """ Basic libfuzzer job """ # verify containers exist if existing_inputs: self.onefuzz.containers.get(existing_inputs) if dryrun: return self.logger.info("creating libfuzzer from template") self._check_is_libfuzzer(target_exe) helper = JobHelper( self.onefuzz, self.logger, project, name, build, duration, pool_name=pool_name, target_exe=target_exe, ) helper.add_tags(tags) helper.define_containers( ContainerType.setup, ContainerType.inputs, ContainerType.crashes, ContainerType.reports, ContainerType.unique_reports, ContainerType.no_repro, ContainerType.coverage, ) if existing_inputs: self.onefuzz.containers.get(existing_inputs) helper.containers[ContainerType.inputs] = existing_inputs else: helper.define_containers(ContainerType.inputs) helper.create_containers() helper.setup_notifications(notification_config) helper.upload_setup(setup_dir, target_exe, extra_files) if inputs: helper.upload_inputs(inputs) helper.wait_on(wait_for_files, wait_for_running) target_exe_blob_name = helper.target_exe_blob_name( target_exe, setup_dir) self._create_tasks( job=helper.job, containers=helper.containers, pool_name=pool_name, target_exe=target_exe_blob_name, vm_count=vm_count, reboot_after_setup=reboot_after_setup, duration=duration, target_workers=target_workers, target_options=target_options, target_env=target_env, tags=helper.tags, crash_report_timeout=crash_report_timeout, check_retry_count=check_retry_count, ) self.logger.info("done creating tasks") helper.wait()
def test_path_resolution(self) -> None: helper = JobHelper( Onefuzz(), logging.getLogger(), "a", "b", "c", False, target_exe=File("README.md"), job=Job( job_id=UUID("0" * 32), state=JobState.init, config=JobConfig(project="a", name="a", build="a", duration=1), ), ) values = { (File("filename.txt"), None): "filename.txt", (File("dir/filename.txt"), None): "filename.txt", (File("./filename.txt"), None): "filename.txt", (File("./filename.txt"), Directory(".")): "filename.txt", (File("dir/filename.txt"), Directory("dir")): "filename.txt", (File("dir/filename.txt"), Directory("dir/")): "filename.txt", (File("dir/filename.txt"), Directory("./dir")): "filename.txt", (File("./dir/filename.txt"), Directory("./dir/")): "filename.txt", } expected = "filename.txt" if sys.platform == "linux": filename = File("/unused/filename.txt") values[(filename, None)] = expected values[(filename, Directory("/unused"))] = expected values[(filename, Directory("/unused/"))] = expected if sys.platform == "windows": for filename in [ File("c:/unused/filename.txt"), File("c:\\unused/filename.txt"), File("c:\\unused\\filename.txt"), ]: values[(filename, None)] = expected values[(filename, Directory("c:/unused"))] = expected values[(filename, Directory("c:/unused/"))] = expected values[(filename, Directory("c:\\unused\\"))] = expected values[(filename, Directory("c:\\unused\\"))] = expected for (args, expected) in values.items(): self.assertEqual(helper.target_exe_blob_name(*args), expected) with self.assertRaises(ValueError): helper.target_exe_blob_name(File("dir/filename.txt"), Directory("other_dir"))
def launch(self, path: Directory, *, os_list: List[OS], targets: List[str], duration=int) -> List[UUID]: """Launch all of the fuzzing templates""" pools = {} for pool in self.of.pools.list(): pools[pool.os] = pool job_ids = [] for target, config in TARGETS.items(): if target not in targets: continue if config.os not in os_list: continue if config.os not in pools.keys(): raise Exception( f"No pool for target: {target} ,os: {config.os}") self.logger.info("launching: %s", target) setup = Directory(os.path.join( path, target)) if config.use_setup else None target_exe = File(os.path.join(path, target, config.target_exe)) inputs = (Directory(os.path.join(path, target, config.inputs)) if config.inputs else None) if setup and config.nested_setup_dir: setup = Directory(os.path.join(setup, config.nested_setup_dir)) job: Optional[Job] = None if config.template == TemplateType.libfuzzer: job = self.of.template.libfuzzer.basic( self.project, target, BUILD, pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, reboot_after_setup=config.reboot_after_setup or False, target_options=config.target_options, ) elif config.template == TemplateType.libfuzzer_dotnet: if setup is None: raise Exception("setup required for libfuzzer_dotnet") job = self.of.template.libfuzzer.dotnet( self.project, target, BUILD, pools[config.os].name, target_harness=config.target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, target_options=config.target_options, ) elif config.template == TemplateType.libfuzzer_qemu_user: job = self.of.template.libfuzzer.qemu_user( self.project, target, BUILD, pools[config.os].name, inputs=inputs, target_exe=target_exe, duration=duration, vm_count=1, target_options=config.target_options, ) elif config.template == TemplateType.radamsa: job = self.of.template.radamsa.basic( self.project, target, BUILD, pool_name=pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, check_asan_log=config.check_asan_log or False, disable_check_debugger=config.disable_check_debugger or False, duration=duration, vm_count=1, ) elif config.template == TemplateType.afl: job = self.of.template.afl.basic( self.project, target, BUILD, pool_name=pools[config.os].name, target_exe=target_exe, inputs=inputs, setup_dir=setup, duration=duration, vm_count=1, target_options=config.target_options, ) else: raise NotImplementedError if config.inject_fake_regression and job is not None: self.of.debug.notification.job(job.job_id) if not job: raise Exception("missing job") job_ids.append(job.job_id) return job_ids