An introduction to LLVM libFuzzer
By Kamil Frankowicz, Kamil Rytarowski
- 15 minutes read - 3136 wordsFuzzing is a software testing method that involves passing malformed data as input to the program and monitoring it for misbehavior. Today, fuzzing is one of the most effective ways to find software security problems. In 2014, Michał Zalewski presented American Fuzzy Lop, the first coverage guided fuzzer. This started the modern world of fuzzing solutions and techniques on the market.
In this article, we will discuss libFuzzer, an LLVM utility that allows you to integrate fuzzing methodology into your libraries, and briefly introduce techniques to maximize the effectiveness of catching problems.
Introduction to libFuzzer
libFuzzer is part of the LLVM package. It allows you to integrate the coverage-guided fuzzer logic into your C / C++ application. A crucial feature of libFuzzer is its close integration with Sanitizer Coverage and bug detecting sanitizers, namely: Address Sanitizer, Leak Sanitizer, Memory Sanitizer, Thread Sanitizer and Undefined Behavior Sanitizer. The use of these projects ensures that a wide range of memory corruption bugs and undesired application behavior are detected. A few examples of these are: Heap/Stack/Global Out Of Bounds, Use After Free, Use After Return, Uninitialized Memory Reads, Memory Leaks or Uninitialized Mutex Use.
Everything is provided with a relatively low performance and memory overhead. The project requires merely a functional LLVM 5.0 toolchain or newer (release date: 07 Sep 2017).
The differences between AFL and libFuzzer
AFL has been on the market since 2014 and has been able to detect over 1000 different types of software errors. When the development of the original AFL repository stalled for a long time, the community created a fork called AFL++ in 2019. The development of Google hosted project was eventually resumed by a different team of Google employees (as Michał Zalewski changed his workplace). Therefore, there are two independent versions of AFL in active development today.
The design of AFL is primarily based on code coverage through tracing code path execution (with SanitizerCoverage), then providing feedback to the fuzzer (again with SanitizerCoverage callbacks), and then using genetic algorithms to craft new inputs (using standard input) to widen the code coverage. The simplified algorithm of AFL is as follows:
- Load user-supplied initial test cases into the queue,
- Take the next input file from the queue,
- Attempt to trim the test case to the smallest size that doesn’t alter the measured behavior of the program,
- Repeatedly mutate the file using a balanced and well-researched variety of traditional fuzzing strategies,
- If any of the generated mutations results in a transition to a new state recorded by the instrumentation, add mutated output as a new entry in the queue.
- Go to point 2.
The readers might ask why create a new fuzzer?
First of all, AFL was incapable of handling different types of coverage, such as
tracking the evaluation of comparison instructions (in C ==
, >
, <
, !=
, etc.).
Next, AFL was unaware of tracking the evaluation of standard (and non-standard)
functions that return certain values depending on the input, such as
the functions for comparing strings (in C strcmp()
, strncmp()
, etc.).
Finally, AFL was not sufficiently flexible to integrate in environments that
accept input from a different source than the standard input
.
The Google employees created a new fuzzer to overcome all the mentioned limitations and take advantage of the bug detecting features that were already present in LLVM. The libFuzzer philosophy was to create a tool that operates similarly to unit testing. We write a small fuzzing program (called the “harness”) and create a programming environment to quickly integrate it into projects that consist of a callable set of functions, typically API of a library. The fuzzer input is provided as parameters to a regular C function (instead of stdin), and if possible the fuzzing process is run in persistent mode that avoids respawning it for every input.
The simplest integration of libFuzzer is as follows:
// fuzz_API.cpp
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
DoSomethingWithMyAPI(Data, Size);
return 0;
}
There are two arguments to the LLVMFuzzerTestOneInput()
function, Data and Size.
This is the buffer of a fixed length that then has to be processed by our
unit testing-like program and passed on to our API.
That said, libFuzzer is tailored towards:
- Fuzzing libraries and their APIs, rather than standalone programs.
- The behavior should be as deterministic as possible. The same input must result in the same output.
- The called library should avoid exiting (by
exit()
or raising signals) for valid code paths. - It should avoid mutating the global state as otherwise it will confuse the fuzzer.
- It should evaluate as quickly as possible, returning to the fuzzer.
- It may use threads, but all newly spawned threads should be joined before returning to libFuzzer.
- Collecting diverse sources of coverage as productive as possible.
AFL is better for one type of projects and libFuzzer for another one. For example, projects that are not designed as libraries are hard to integrate with libFuzzer, while entry barrier to AFL testing can be lower. It is a matter of rebuilding the software and running the fuzzer as-is.
As we presented above libFuzzer operates entirely inside the memory while AFL feeds the fuzzed program with specially crafted files and passes them to its input.
Fuzzing corpora and libFuzzer
The fuzzing process begins with the construction of test corpora. In the case of libFuzzer, this is not necessarily true - it can generate entirely random input. Of course, this implies practically no code coverage at the start, and many hours of work before the first input passing initial integrity checks is produced. Therefore, it is worth having even a few/odd test cases to start with.
The goal of minimizing the test files is to get as much code coverage as possible with as small input file size as possible. The relation is simple: the smaller the file, the more executions per second can be achieved. It is worth adding that there is also a process of minimizing a single input file. This operation aims to remove any redundant data from the test case causing the crash to capture what exactly happened. According to the libFuzzer documentation, the minimum speed at which it is worthwhile to start meaningful fuzzing with a prepared body is 1000 iterations per second.
Crash management usually boils down to deduplicating a set of test cases by comparing the function call stack or a stack trace. Due to limited fuzzer information during a crash, there are different approaches to solve the problem here. libFuzzer does not have a module for managing test cases, and every new fuzzing job is a “clean card” for it. The user must automate this process by writing the report parser himself/herself.
Readers are encouraged to create and maintain their test corpora - in the long run, and this is much better than using the “ready-made” ones found on the Internet or, as mentioned earlier, generating one from scratch. It is worth categorizing the corpus per application (in case of a single application) or per file type - if we test many different programs, using the same file formats.
The more diverse the initial test data, the better. The corpus continuously evolves during fuzzing: you find inputs that pass through previously unknown paths in the code. During the operation, smaller files push large files out of the corpus (if they provide equivalent code coverage). Of course, you find test cases that cause the program to terminate. The process of merging the corpus is nothing more than minimizing the sum of the sets: the corpora that started fuzzing and the newly created test cases.
Integrate libFuzzer into your code
As mentioned earlier, to use libFuzzer in your projects, you need to prepare a lightweight library entry code.
Let’s look at a real world example from the LLVM libFuzzer test-suite, an example called SingleStrcmpTest.cpp
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Simple test for a fuzzer. The fuzzer must find a particular string.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if (Size >= 7) {
char Copy[7];
memcpy(Copy, Data, 6);
Copy[6] = 0;
if (!strcmp(Copy, "qwerty")) {
fprintf(stderr, "BINGO\n");
exit(1);
}
}
return 0;
}
In this example, we have two conditions for triggering the bug (in our case, calling exit(2)
and exiting):
- Size of the input buffer is larger or equal to 7 bytes.
- The first 6 bytes store the sequence
"qwerty"
, followed by\0
. - The rest of the input buffer is ignored.
We note that there is no main()
function in the program, as it is provided directly by the
libFuzzer library. Besides that, the code contains SanCov instrumentation that feedbacks the fuzzer.
This fuzzer uses the strcmp(3)
function interceptors that are integrated through Address Sanitizer
into the coverage reporting mechanism in libFuzzer. Thus, there are two ways of executing the program,
with Address Sanitizer enabled and without, and we can see the difference. This also illustrates that
even if the code path is relatively simple, AFL would be totally incapable of guessing the code
to break strcmp(3)
conditionals in the code and passing random inputs, possibly even never breaking
the program in a finite time.
This happens inside sanitizer_common_interceptors.inc:
DECLARE_WEAK_INTERCEPTOR_HOOK(__sanitizer_weak_hook_strcmp, uptr called_pc,
const char *s1, const char *s2, int result)
INTERCEPTOR(int, strcmp, const char *s1, const char *s2) {
void *ctx;
COMMON_INTERCEPTOR_ENTER(ctx, strcmp, s1, s2);
unsigned char c1, c2;
uptr i;
for (i = 0;; i++) {
c1 = (unsigned char)s1[i];
c2 = (unsigned char)s2[i];
if (c1 != c2 || c1 == '\0') break;
}
COMMON_INTERCEPTOR_READ_STRING(ctx, s1, i + 1);
COMMON_INTERCEPTOR_READ_STRING(ctx, s2, i + 1);
int result = CharCmpX(c1, c2);
CALL_WEAK_INTERCEPTOR_HOOK(__sanitizer_weak_hook_strcmp, GET_CALLER_PC(), s1,
s2, result);
return result;
}
To put it simply, the sanitizer runtime implements regular sanity checks to catch bugs
through overloaded standard functions and in case of some of them, they feedback
libFuzzer through the __sanitizer_weak_hook_strcmp
hook, reporting the:
- Code position (Program Country).
- Input string
s1
(left-side). - Input string
s2
(right-side). - The result of the lexicographical comparison of nul-terminated strings
s1
ands2
.
Our testing environment is NetBSD/amd64 9.99.69, with Clang/LLVM 9.0.0.
$ clang++ -fsanitize=fuzzer SingleStrcmpTest.cpp
And done, everything is shipped with LLVM!
In order to start the fuzzed program, we simply start the compiled program.
We will restrict the number of iterations, we will pass the -runs=1000000
argument.
After starting the program, the fuzzer will either:
- Probe a number of inputs lower than the limit, trigger the exit code path, save the reproducer and exit.
- Probe 1000000 inputs without breaking the checks and exit.
$ ./a.out -runs=1000000
INFO: Seed: 567124526
INFO: Loaded 1 modules (4 inline 8-bit counters): 4 [0x67d594, 0x67d598),
INFO: Loaded 1 PC tables (4 PCs): 4 [0x46abc8,0x46ac08),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 41Mb
#415 NEW cov: 3 ft: 3 corp: 2/9b lim: 8 exec/s: 0 rss: 41Mb L: 8/8 MS: 3 InsertByte-InsertByte-CrossOver-
#464 REDUCE cov: 3 ft: 3 corp: 2/8b lim: 8 exec/s: 0 rss: 41Mb L: 7/7 MS: 4 ShuffleBytes-EraseBytes-InsertByte-InsertByte-
#1000000 DONE cov: 3 ft: 3 corp: 2/8b lim: 4096 exec/s: 1000000 rss: 41Mb
Done 1000000 runs in 1 second(s)
Nothing has been detected!
The most important output values for the user are:
- cov - code coverage, total number of code blocks that our corpus reaches.
- corp - number of files in the corpus and their total size.
- exec/s - fuzzing iterations per second (useful in longer runs than <= 1sec ones).
- rss - amount of process memory that resides in physical memory.
Let’s try with Address Sanitizer and its builtin interceptors, including the one for strcmp(3)
.
$ clang++ -fsanitize=fuzzer,address SingleStrcmpTest.cpp
$ ./a.out -runs=1000000
INFO: Seed: 2511417575
INFO: Loaded 1 modules (4 inline 8-bit counters): 4 [0x767e94, 0x767e98),
INFO: Loaded 1 PC tables (4 PCs): 4 [0x5413c8,0x541408),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 41Mb
#414 NEW cov: 3 ft: 3 corp: 2/9b lim: 8 exec/s: 0 rss: 41Mb L: 8/8 MS: 2 ChangeBit-CrossOver-
#427 REDUCE cov: 3 ft: 3 corp: 2/8b lim: 8 exec/s: 0 rss: 41Mb L: 7/7 MS: 3 CopyPart-ChangeBit-EraseBytes-
BINGO
==29652== ERROR: libFuzzer: fuzz target exited
#0 0x4feda1 in __sanitizer_print_stack_trace /public/llvm/projects/compiler-rt/lib/asan/asan_stack.cc:86:3
#1 0x459676 in fuzzer::PrintStackTrace() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerUtil.cpp:205:5
#2 0x43d48c in fuzzer::Fuzzer::ExitCallback() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:251:3
#3 0x7f7ff634dfe5 in __cxa_finalize /usr/src/lib/libc/stdlib/atexit.c:222:7
#4 0x7f7ff634dbe2 in exit /usr/src/lib/libc/stdlib/exit.c:60:2
#5 0x523727 in LLVMFuzzerTestOneInput (/tmp/./a.out+0x523727)
#6 0x43e924 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:553:15
#7 0x43e045 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:471:3
#8 0x4405fb in fuzzer::Fuzzer::MutateAndTestOne() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:695:19
#9 0x4414b5 in fuzzer::Fuzzer::Loop(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:840:5
#10 0x42df05 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:787:6
#11 0x459f52 in main /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
#12 0x422eba in ___start (/tmp/./a.out+0x422eba)
SUMMARY: libFuzzer: fuzz target exited
MS: 3 ShuffleBytes-ChangeBit-CMP- DE: "qwerty"-; base unit: 655969223db839614fb5b3146504e1009c930fb3
0x71,0x77,0x65,0x72,0x74,0x79,0x0,
qwerty\x00
artifact_prefix='./'; Test unit written to ./crash-1489c3a435b0fd3b98f42947c07e26fb7b425f08
Base64: cXdlcnR5AA==
$ hexdump -C ./crash-1489c3a435b0fd3b98f42947c07e26fb7b4
00000000 71 77 65 72 74 79 00 |qwerty.|
00000007
BINGO!
Let’s try a more elaborated example StrstrTest.cpp:
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Test strstr and strcasestr hooks.
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <string>
// Windows does not have strcasestr and memmem, so we are not testing them.
#ifdef _WIN32
#define strcasestr strstr
#define memmem(a, b, c, d) true
#endif
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
if (Size < 4) return 0;
std::string s(reinterpret_cast<const char*>(Data), Size);
if (strstr(s.c_str(), "FUZZ") &&
strcasestr(s.c_str(), "aBcD") &&
memmem(s.data(), s.size(), "kuku", 4)
) {
fprintf(stderr, "BINGO\n");
exit(1);
}
return 0;
}
And build it with libFuzzer+
$ clang++ -fsanitize=fuzzer,address StrstrTest.cpp
$ ./a.out -runs=1000000
INFO: Seed: 2285393126
INFO: Loaded 1 modules (12 inline 8-bit counters): 12 [0x768034, 0x768040),
INFO: Loaded 1 PC tables (12 PCs): 12 [0x5419f8,0x541ab8),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 2 ft: 2 corp: 1/1b exec/s: 0 rss: 39Mb
#9 NEW cov: 3 ft: 3 corp: 2/5b lim: 4 exec/s: 0 rss: 39Mb L: 4/4 MS: 2 InsertByte-CrossOver-
#20 NEW cov: 5 ft: 5 corp: 3/9b lim: 4 exec/s: 0 rss: 39Mb L: 4/4 MS: 1 CMP- DE: "FUZZ"-
#1129 NEW cov: 7 ft: 7 corp: 4/21b lim: 14 exec/s: 0 rss: 39Mb L: 12/12 MS: 4 PersAutoDict-PersAutoDict-PersAutoDict-CMP- DE: "FUZZ"-"FUZZ"-"FUZZ"-"aBcD"-
#3693 REDUCE cov: 7 ft: 7 corp: 4/20b lim: 38 exec/s: 0 rss: 39Mb L: 11/11 MS: 4 InsertByte-CMP-EraseBytes-PersAutoDict- DE: "\x00\x00\x00\x00"-"FUZZ"-
#3727 REDUCE cov: 7 ft: 7 corp: 4/19b lim: 38 exec/s: 0 rss: 39Mb L: 10/10 MS: 4 ChangeBinInt-ChangeBit-EraseBytes-CMP- DE: "aBcD"-
BINGO
==22896== ERROR: libFuzzer: fuzz target exited
#0 0x4ff141 in __sanitizer_print_stack_trace /public/llvm/projects/compiler-rt/lib/asan/asan_stack.cc:86:3
#1 0x459a16 in fuzzer::PrintStackTrace() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerUtil.cpp:205:5
#2 0x43d82c in fuzzer::Fuzzer::ExitCallback() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:251:3
#3 0x7f7ff634dfe5 in __cxa_finalize /usr/src/lib/libc/stdlib/atexit.c:222:7
#4 0x7f7ff634dbe2 in exit /usr/src/lib/libc/stdlib/exit.c:60:2
#5 0x523bb5 in LLVMFuzzerTestOneInput (/tmp/./a.out+0x523bb5)
#6 0x43ecc4 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:553:15
#7 0x43e3e5 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:471:3
#8 0x44099b in fuzzer::Fuzzer::MutateAndTestOne() /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:695:19
#9 0x441855 in fuzzer::Fuzzer::Loop(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&, std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, fuzzer::fuzzer_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > const&) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:840:5
#10 0x42e2a5 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:787:6
#11 0x45a2f2 in main /public/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19:10
#12 0x42325a in ___start (/tmp/./a.out+0x42325a)
SUMMARY: libFuzzer: fuzz target exited
MS: 1 CMP- DE: "kuku"-; base unit: 0a7fef0a810c46280344bced8e56085553bb8433
0x61,0x42,0x63,0x44,0x6b,0x75,0x6b,0x75,0xc,0x46,0x55,0x5a,0x5a,0x5a,
aBcDkuku\x0cFUZZZ
artifact_prefix='./'; Test unit written to ./crash-6b6aaae17687509c585ca5cf11e9188510ac2d1c
Base64: YUJjRGt1a3UMRlVaWlo=
The fuzzer is incredibly fast and smart!
Integrate libFuzzer into your project
For real-life examples of the integration you can check the
Yara example
of fuzzing the yt_compiler
API.
Yara
is a pattern matching swiss knife for malware researchers and uses libFuzzer for unit-like testing.
This example uses LLVMFuzzerInitialize()
,
which performs the one-time initialization at the start.
We leave the code analysis, building it and running it as an exercise to the readers.
/*
Copyright (c) 2017. The YARA Authors. All Rights Reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <yara.h>
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
{
yr_initialize();
return 0;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
YR_RULES* rules;
YR_COMPILER* compiler;
char* buffer = (char*) malloc(size + 1);
if (!buffer)
return 0;
strncpy(buffer, (const char *) data, size);
buffer[size] = 0;
if (yr_compiler_create(&compiler) != ERROR_SUCCESS)
{
free(buffer);
return 0;
}
if (yr_compiler_add_string(compiler, (const char*) buffer, NULL) == 0)
{
if (yr_compiler_get_rules(compiler, &rules) == ERROR_SUCCESS)
yr_rules_destroy(rules);
}
yr_compiler_destroy(compiler);
free(buffer);
return 0;
}
Summary
Readers should now be aware of the design differences between AFL and libFuzzer.
We have presented a brief introduction to libFuzzer, its integration with ASan and a small chunk of the internals of the LLVM code.
It’s worth mentioning that there are more libFuzzer-like projects available, one of them is LibAFL, a project derived from AFL and another one is Honggfuzz that was developed independently by Robert Święcki.
If you have any questions or requests, feel free to reach the Moritz Systems team, which actively takes part in the development of toolchains and fuzzers (including libFuzzer, honggfuzz and AFL++) and authored the port of the whole software stack of sanitizers and fuzzers to NetBSD.