7.10. Fuzzing barebox¶
As described in the Security Considerations chapter, some parts of barebox need to deal with untrusted inputs. To aid in finding and fixing issues that might be exploited, barebox can be built with LLVM’s libfuzzer to exercise these security-critical parsers.
7.10.1. Building¶
The barebox sandbox architecture has support for libfuzzer when compiled with
LLVM. The libfuzzer_defconfig enables it as well as different hardening
options to crash barebox on detection of memory safety issues:
$ export LLVM=1 # or e.g. LLVM=-19, if clang is called clang-19
$ make libfuzzer_defconfig
$ make -j$(nproc)
# [snip]
images built:
barebox
fuzz-dtb
fuzz-dtb-overlay
fuzz-fat
fuzz-fdt-compatible
fuzz-filetype
fuzz-fit
fuzz-jwt
fuzz-partitions
fuzz-partitions-4k
fuzz-pe
fuzz-state-direct
fuzz-tlv
fuzz-uncompress
All fuzzers generated are symlinks to the same barebox executable. barebox
will detect that it was invoked via symlink and switch to fuzzing mode.
images/barebox --list-fuzzers prints the fuzzers of a given build.
Which fuzzers are built depends on the configuration: each one is tied to the option enabling the code it exercises, so a fuzzer for a disabled subsystem is not generated.
7.10.2. Fuzzing¶
Fuzzers can be run directly or by invoking the main barebox binary with the
--fuzz option. The latter is mostly useful for debugging.
Examples of running the fuzzers:
# Just run the fuzzer with no corpus
images/fuzz-filetype
# Multi-threaded fuzzing is recommended as is using a corpus
images/fuzz-dtb -rss_limit_mb=10000 -max_len=51200 -jobs=64 \
../barebox-fuzz-corpora/dtb
# Run fuzz-fit with a time limit
images/fuzz-fit -max_total_time=600 -rss_limit_mb=20000 -max_len=128000
# Debug a crash
gdb --args images/fuzz-fit crash-$HASH
When a crash is detected, libfuzzer will create a crash-$HASH file
that can be passed instead of the corpus directory to run the fuzz test
once.
7.10.3. Corpora¶
We maintain a corpus for every fuzz test on Github.
This helps bootstrap the fuzzer, so it can exercise new paths more quickly.
7.10.4. Determining Source Code Coverage¶
Note
Coverage instrumentation is currently only supported with LLVM and sandbox.
To collect coverage information, barebox must be built with CONFIG_GCOV=y.
The linking process will take much longer than usual, but once done, running
barebox will produce coverage information.
images/fuzz-filetype -max_total_time=60 -max_len=2048
After the process exits regularly (i.e., not aborted with ctrl+C!),
it will produce a default.profraw file in its working directory,
which needs to be further processed:
make coverage-html
Note that every run of an instrumented binary overwrites the profile, so
collect it before running anything else, images/barebox --list-fuzzers
included.
This will produce a ${KBUILD_OUTPUT}/barebox.coverage_html/ directory,
which can be inspected by a web browser:
firefox barebox.coverage_html/index.html
7.10.5. Automated Coverage Checking¶
For CI or scripted workflows, the coverage-check Makefile target can verify
that coverage is 100% for a given set of functions without manual inspection:
make coverage-check \
COVERAGE_SOURCES="common/filetype.c" \
COVERAGE_FUNCTIONS="file_detect|fuzz_filetype|fat_valid|is_fat|is_gpt|pmbr_part"
COVERAGE_SOURCES lists the source files to check. COVERAGE_FUNCTIONS is
an optional regex that filters which functions are considered. This is important
when a source file contains functions not exercised by the fuzzer — without the
filter, those would show up as uncovered and fail the check.
On success, the target prints a per-file summary and exits with 0. On failure,
it lists each function that has uncovered regions or lines along with their
counts and exits with 1. Use make coverage-html to inspect the specific
source lines that were not hit.
7.10.6. Adding a fuzzer¶
The barebox integration of libfuzzer is a bit unusual; barebox supplies
its own main() and calls into libfuzzer instead of the other way round.
This allows us to write fuzz tests naturally inline without having to set up things beforehand as barebox will have already executed all of its initcalls for example.
To add a new fuzz test, just add a function next to the parser that parses a memory buffer:
#include <fuzz.h>
static int fuzz_dtb(const u8 *data, size_t size)
{
struct device_node *np;
np = of_unflatten_dtb_const(data, size);
if (!IS_ERR(np))
of_delete_node(np);
return 0;
}
fuzz_test("dtb", fuzz_dtb);
Note
Fuzz tests should not leak memory, otherwise the fuzzing process may abort eventually due to memory exhaustion.
Anything the test needs only once, like registering a device to run the
parser against, does not belong into that function: it would be executed
while the first input is measured and its coverage would be credited to
whatever input happens to run first. Register such a test with
fuzz_test_init() instead and do the setup in the callback:
static void fuzz_dtb_init(void)
{
/* runs once, before the first input */
}
fuzz_test_init("dtb", fuzz_dtb, fuzz_dtb_init);
The callback may be called more than once, e.g. once per invocation of the
fuzz command, so it has to cope with being called again. The wrappers
that back a test with a ramdisk take such a callback as their last argument
and run it after creating the device.
This function then needs to be registered by name in
images/Makefile.sandbox:
fuzzer-$(CONFIG_OFTREE) += dtb
Searching the source tree for fuzz_test will show more examples,
e.g. how to wrap the received buffer in a ramdisk to interface
with code that requires block devices.
When adding a new fuzzing test, please also submit a pullrequest with a corpus.
7.10.7. OSS-Fuzz¶
scripts/oss-fuzz.sh builds the fuzzers the way OSS-Fuzz expects. It works outside of
OSS-Fuzz as well, e.g. to reproduce its builds locally:
./scripts/oss-fuzz.sh
./oss-fuzz-work/out/fuzz-filetype
SANITIZER selects address, undefined, coverage, none or
the default all, which combines ASan and UBSan in one build. Seed
corpora are packaged from the barebox-fuzz-corpora checkout next to the
barebox tree, or from CORPORA if set. New fuzzers are picked up via
--list-fuzzers, so the script needs no changes for them. Only
libFuzzer is supported as fuzzing engine.