Add extra_output container, rename extra container (#3064)

## Summary of the Pull Request

- **Breaking** (but as far as I know this feature is not yet in use): rename the `extra_container` to `extra_setup_container`.
- **Add**: the `extra_output_container`, which pushes its outputs continually.
  - We may also want a type of container which both pushes & pulls? See discussion below.
- **Improved**: if `onefuzz-task` fails upon launch, we will log its output for diagnosis (might close #3113)

---

Some thoughts for the future:

We might want to redesign the containers so that we have something like the following which is passed to the agent, and the agent doesn't need to know the specifics of the containers supplied:

```jsonc
{
    // ...
    "containers": {
        "extra_setup_dir": {
            "mode": "pull",
            "container_name": "yyy",
        },
        "extra_output_dir": {
            "mode": "push",
            "continuous": true, // keep pushing while job is running
            "container_name": "xxx"
        }
    }
}
```

At the moment the agent needs to know what each container is for, for each task type. A more generic and flexible method might be simpler overall.
This commit is contained in:
George Pollard
2023-06-15 14:48:27 +12:00
committed by GitHub
parent 630b083f64
commit aa54a15427
50 changed files with 1541 additions and 1405 deletions

View File

@ -106,10 +106,14 @@ TARGETS: Dict[str, Integration] = {
ContainerType.unique_reports: 1,
ContainerType.coverage: 1,
ContainerType.inputs: 2,
ContainerType.extra_output: 1,
},
reboot_after_setup=True,
inject_fake_regression=True,
fuzzing_target_options=["--test:{extra_dir}"],
fuzzing_target_options=[
"--test:{extra_setup_dir}",
"--write_test_file={extra_output_dir}/test.txt",
],
),
"linux-libfuzzer-with-options": Integration(
template=TemplateType.libfuzzer,
@ -181,7 +185,7 @@ TARGETS: Dict[str, Integration] = {
os=OS.linux,
target_exe="fuzz_target_1",
wait_for_files={ContainerType.unique_reports: 1, ContainerType.coverage: 1},
fuzzing_target_options=["--test:{extra_dir}"],
fuzzing_target_options=["--test:{extra_setup_dir}"],
),
"linux-trivial-crash": Integration(
template=TemplateType.radamsa,
@ -209,9 +213,13 @@ TARGETS: Dict[str, Integration] = {
ContainerType.inputs: 2,
ContainerType.unique_reports: 1,
ContainerType.coverage: 1,
ContainerType.extra_output: 1,
},
inject_fake_regression=True,
fuzzing_target_options=["--test:{extra_dir}"],
fuzzing_target_options=[
"--test:{extra_setup_dir}",
"--write_test_file={extra_output_dir}/test.txt",
],
),
"windows-libfuzzer-linked-library": Integration(
template=TemplateType.libfuzzer,
@ -538,7 +546,7 @@ class TestOnefuzz:
) -> List[UUID]:
"""Launch all of the fuzzing templates"""
pools = {}
pools: Dict[OS, Pool] = {}
if unmanaged_pool is not None:
pools[unmanaged_pool.the_os] = self.of.pools.get(unmanaged_pool.pool_name)
else:
@ -559,12 +567,14 @@ class TestOnefuzz:
self.logger.info("launching: %s", target)
setup: Directory | str | None
if config.setup_dir is None:
setup = (
Directory(os.path.join(path, target)) if config.use_setup else None
)
if config.use_setup:
setup = Directory(os.path.join(path, target))
else:
setup = None
else:
setup = config.setup_dir
setup = Directory(config.setup_dir)
target_exe = File(os.path.join(path, target, config.target_exe))
inputs = (
@ -577,87 +587,9 @@ class TestOnefuzz:
setup = Directory(os.path.join(setup, config.nested_setup_dir))
job: Optional[Job] = None
if config.template == TemplateType.libfuzzer:
# building the extra container to test this variable substitution
extra = self.of.containers.create("extra")
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,
fuzzing_target_options=config.fuzzing_target_options,
extra_container=Container(extra.name),
)
elif config.template == TemplateType.libfuzzer_dotnet:
if setup is None:
raise Exception("setup required for libfuzzer_dotnet")
if config.target_class is None:
raise Exception("target_class required for libfuzzer_dotnet")
if config.target_method is None:
raise Exception("target_method required for libfuzzer_dotnet")
job = self.of.template.libfuzzer.dotnet(
self.project,
target,
BUILD,
pools[config.os].name,
target_dll=config.target_exe,
inputs=inputs,
setup_dir=setup,
duration=duration,
vm_count=1,
fuzzing_target_options=config.target_options,
target_class=config.target_class,
target_method=config.target_method,
)
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
job = self.build_job(
duration, pools, target, config, setup, target_exe, inputs
)
if config.inject_fake_regression and job is not None:
self.of.debug.notification.job(job.job_id)
@ -669,6 +601,101 @@ class TestOnefuzz:
return job_ids
def build_job(
self,
duration: int,
pools: Dict[OS, Pool],
target: str,
config: Integration,
setup: Optional[Directory],
target_exe: File,
inputs: Optional[Directory],
) -> Optional[Job]:
if config.template == TemplateType.libfuzzer:
# building the extra_setup & extra_output containers to test variable substitution
# and upload of files (in the case of extra_output)
extra_setup_container = self.of.containers.create("extra-setup")
extra_output_container = self.of.containers.create("extra-output")
return 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,
fuzzing_target_options=config.fuzzing_target_options,
extra_setup_container=Container(extra_setup_container.name),
extra_output_container=Container(extra_output_container.name),
)
elif config.template == TemplateType.libfuzzer_dotnet:
if setup is None:
raise Exception("setup required for libfuzzer_dotnet")
if config.target_class is None:
raise Exception("target_class required for libfuzzer_dotnet")
if config.target_method is None:
raise Exception("target_method required for libfuzzer_dotnet")
return self.of.template.libfuzzer.dotnet(
self.project,
target,
BUILD,
pools[config.os].name,
target_dll=File(config.target_exe),
inputs=inputs,
setup_dir=setup,
duration=duration,
vm_count=1,
fuzzing_target_options=config.target_options,
target_class=config.target_class,
target_method=config.target_method,
)
elif config.template == TemplateType.libfuzzer_qemu_user:
return 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:
return 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:
return 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
def check_task(
self, job: Job, task: Task, scalesets: List[Scaleset]
) -> TaskTestState:
@ -736,15 +763,18 @@ class TestOnefuzz:
job_tasks[job.job_id] = tasks
check_containers[job.job_id] = {}
for task in tasks:
for container in task.config.containers:
if container.type in TARGETS[job.config.name].wait_for_files:
count = TARGETS[job.config.name].wait_for_files[container.type]
check_containers[job.job_id][container.name] = (
ContainerWrapper(
self.of.containers.get(container.name).sas_url
),
count,
)
if task.config.containers:
for container in task.config.containers:
if container.type in TARGETS[job.config.name].wait_for_files:
count = TARGETS[job.config.name].wait_for_files[
container.type
]
check_containers[job.job_id][container.name] = (
ContainerWrapper(
self.of.containers.get(container.name).sas_url
),
count,
)
self.success = True
self.logger.info("checking %d jobs", len(jobs))
@ -861,16 +891,17 @@ class TestOnefuzz:
def get_job_crash_report(self, job_id: UUID) -> Optional[Tuple[Container, str]]:
for task in self.of.tasks.list(job_id=job_id, state=None):
for container in task.config.containers:
if container.type not in [
ContainerType.unique_reports,
ContainerType.reports,
]:
continue
if task.config.containers:
for container in task.config.containers:
if container.type not in [
ContainerType.unique_reports,
ContainerType.reports,
]:
continue
files = self.of.containers.files.list(container.name)
if len(files.files) > 0:
return (container.name, files.files[0])
files = self.of.containers.files.list(container.name)
if len(files.files) > 0:
return (container.name, files.files[0])
return None
def launch_repro(
@ -1044,12 +1075,13 @@ class TestOnefuzz:
container_names = set()
for job in jobs:
for task in self.of.tasks.list(job_id=job.job_id, state=None):
for container in task.config.containers:
if container.type in [
ContainerType.reports,
ContainerType.unique_reports,
]:
container_names.add(container.name)
if task.config.containers:
for container in task.config.containers:
if container.type in [
ContainerType.reports,
ContainerType.unique_reports,
]:
container_names.add(container.name)
for repro in self.of.repro.list():
if repro.config.container in container_names:

View File

@ -3,7 +3,37 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
// allow an argument --write_test_file=xxx.txt to be set
// which is useful for exercising some OneFuzz features in integration tests
int LLVMFuzzerInitialize(int *argc, char ***argv) {
const int num_args = *argc;
char** args = *argv;
for (int i = 0; i < num_args; ++i) {
// look for argument starting with --write_test_file=
const char* arg_name = "--write_test_file=";
if (strncmp(args[i], arg_name, strlen(arg_name)) == 0) {
// extract filename part
const char* file_name = args[i] + strlen(arg_name);
// write file
FILE* output = fopen(file_name, "a");
if (!output) {
perror("failed to open file");
return -1;
}
fputs("Hello from simple fuzzer\n", output);
fclose(output);
break;
}
}
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t len) {
int cnt = 0;