Hello,
I want to add a fork-server to libfuzzer. Unlike the -fork=1 option, the
fork-server will run each input in a freshly forked process (AFL-style)
with the purpose of clearing state after each run. As background, we
have already been doing this in QEMU for fuzzing virtual-devices in a
full VM process for some time. The basic idea for how this works:
1. We force the coverage counters and the FuzzerTracePC object into
a contiguous page-aligned region at build-time using a linker
script:
https://github.com/qemu/qemu/blob/master/tests/qtest/fuzz/fork_fuzz.ld
2. Before we fuzz, we mmap MAP_SHARED over this region.
https://github.com/qemu/qemu/blob/master/tests/qtest/fuzz/fork_fuzz.c
3.) Finally, in LLVMFuzzerTestOneInput, we just use a fork clause.
In the forked child we execute the target and call _Exit. The parent
wait()s on the child. Since the counters are shared, it can mutate
using the coverage collected in the child.
https://github.com/qemu/qemu/blob/master/tests/qtest/fuzz/virtio_net_fuzz.c#L130
This is quite a simple approach and it works with most libfuzzer
features such as merging, cmp tracing, jobs/workers (we mmap the region
on the first call to LLVMFuzzerTestOneInput, after the -workers forks
happen). We did this reproducibility is important to us, there is no
easy way to (quickly) clear QEMU state in between runs, and we wanted to
do this with libfuzzer.
I realize that the persistent fuzzing is one of the key benefits of
libfuzzer and fork() has come under some fire in the fuzzing-community
for performance reasons. While the community is working on other
snapshotting methods, until a snapshotting technique is widely deployed,
I believe it is useful to provide a fork option, so large targets that
tend to leak state can take advantage of projects such as libfuzzer and
oss-fuzz.
Here are a few things that don't work with our current forking method,
since we have not made any changes to libfuzzer:
- libfuzzer timeouts (we lose the timer across the fork call).
- leak detection (we never return to RunOne() in the child process)
- rss limit (we lose the rss watcher thread)
- target-specific threads that were started in LLVMFuzzerInitialize
I think most of these issues could be ironed out with a proper
integration into libfuzzer.
My question: Is there interest in having a proper per-testcase
forkserver in libfuzzer, or is this a non-starter? For QEMU, this would
be nice since we could remove the hacky linker script (which, among
other things, does not work with go.ld). If there are no objections, I
can work on such an integration.
-Alex