Files
onefuzz/src/cli/onefuzz/templates/libfuzzer.py
bmc-msft d9eda00f12 disable expect_crash_on_failure by default in more places (#807)
dotnet, as well as the _create_tasks helper still used the old default for requiring crash files on libFuzzer exiting non-zero
2021-04-15 20:19:10 +00:00

729 lines
25 KiB
Python

#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import os
import tempfile
from enum import Enum
from typing import Dict, List, Optional
from onefuzztypes.enums import OS, ContainerType, TaskDebugFlag, TaskType
from onefuzztypes.models import Job, NotificationConfig
from onefuzztypes.primitives import Container, Directory, File, PoolName
from onefuzz.api import Command
from . import JobHelper
LIBFUZZER_MAGIC_STRING = b"ERROR: libFuzzer"
class QemuArch(Enum):
aarch64 = "aarch64"
class Libfuzzer(Command):
""" Pre-defined Libfuzzer job """
def _check_is_libfuzzer(self, target_exe: File) -> None:
""" Look for a magic string """
self.logger.debug(
"checking %s for %s", repr(target_exe), repr(LIBFUZZER_MAGIC_STRING)
)
with open(target_exe, "rb") as handle:
data = handle.read()
if LIBFUZZER_MAGIC_STRING not in data:
raise Exception("not a libfuzzer binary: %s" % target_exe)
def _create_tasks(
self,
*,
job: Job,
containers: Dict[ContainerType, Container],
pool_name: PoolName,
target_exe: str,
vm_count: int = 2,
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,
tags: Optional[Dict[str, str]] = None,
check_retry_count: Optional[int] = None,
crash_report_timeout: Optional[int] = 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,
) -> None:
regression_containers = [
(ContainerType.setup, containers[ContainerType.setup]),
(ContainerType.crashes, containers[ContainerType.crashes]),
(ContainerType.unique_reports, containers[ContainerType.unique_reports]),
(
ContainerType.regression_reports,
containers[ContainerType.regression_reports],
),
]
self.logger.info("creating libfuzzer_regression task")
regression_task = self.onefuzz.tasks.create(
job.job_id,
TaskType.libfuzzer_regression,
target_exe,
regression_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,
check_fuzzer_help=check_fuzzer_help,
debug=debug,
colocate=colocate_all_tasks or colocate_secondary_tasks,
minimized_stack_depth=minimized_stack_depth,
)
fuzzer_containers = [
(ContainerType.setup, containers[ContainerType.setup]),
(ContainerType.crashes, containers[ContainerType.crashes]),
(ContainerType.inputs, containers[ContainerType.inputs]),
]
self.logger.info("creating libfuzzer task")
# disable ensemble sync if only one VM is used
if ensemble_sync_delay is None and vm_count == 1:
ensemble_sync_delay = 0
fuzzer_task = self.onefuzz.tasks.create(
job.job_id,
TaskType.libfuzzer_fuzz,
target_exe,
fuzzer_containers,
pool_name=pool_name,
reboot_after_setup=reboot_after_setup,
duration=duration,
vm_count=vm_count,
target_options=target_options,
target_env=target_env,
target_workers=target_workers,
tags=tags,
debug=debug,
ensemble_sync_delay=ensemble_sync_delay,
colocate=colocate_all_tasks,
check_fuzzer_help=check_fuzzer_help,
expect_crash_on_failure=expect_crash_on_failure,
)
prereq_tasks = [fuzzer_task.task_id, regression_task.task_id]
coverage_containers = [
(ContainerType.setup, containers[ContainerType.setup]),
(ContainerType.coverage, containers[ContainerType.coverage]),
(ContainerType.readonly_inputs, containers[ContainerType.inputs]),
]
self.logger.info("creating libfuzzer_coverage task")
self.onefuzz.tasks.create(
job.job_id,
TaskType.libfuzzer_coverage,
target_exe,
coverage_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=prereq_tasks,
debug=debug,
colocate=colocate_all_tasks or colocate_secondary_tasks,
check_fuzzer_help=check_fuzzer_help,
)
report_containers = [
(ContainerType.setup, containers[ContainerType.setup]),
(ContainerType.crashes, containers[ContainerType.crashes]),
(ContainerType.reports, containers[ContainerType.reports]),
(ContainerType.unique_reports, containers[ContainerType.unique_reports]),
(ContainerType.no_repro, containers[ContainerType.no_repro]),
]
self.logger.info("creating libfuzzer_crash_report task")
self.onefuzz.tasks.create(
job.job_id,
TaskType.libfuzzer_crash_report,
target_exe,
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=prereq_tasks,
target_timeout=crash_report_timeout,
check_retry_count=check_retry_count,
check_fuzzer_help=check_fuzzer_help,
debug=debug,
colocate=colocate_all_tasks or colocate_secondary_tasks,
minimized_stack_depth=minimized_stack_depth,
)
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,
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,
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,
) -> 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 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)
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,
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,
)
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.target_exe_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 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,
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)
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=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 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,
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.target_exe_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.target_exe_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)
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=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