The proxy is tested via fuzzing by way of OSS-Fuzz and we rely on cargo-fuzz as our underlying fuzzing engine.
We place the fuzz tests into folders within the individual crates that the fuzz
tests target. For example, we have a fuzz test that that target the crate
/linkerd/addr
and the code in /linkerd/addr/src
and thus the fuzz test that
targets this crate is put in /linkerd/addr/fuzz
. The folder set up we use for
each of the fuzz tests is automatically generated by cargo fuzz init
(described here).
The general idea behind the fuzz tests is to follow the testing set up of the rest of the proxy. As such, we both have fuzz tests that resemble small unit-test-like code pieces, as well as fuzz tests that resemble larger integration-test-like code pieces.
The code in /linkerd/addr/fuzz/fuzz_targets/fuzz_target_1.rs
is an example of
a unit-test-like fuzzer:
#![no_main]
#[cfg(fuzzing)]
use libfuzzer_sys::fuzz_target;
#[cfg(fuzzing)]
fuzz_target!(|data: &[u8]| {
let _ = tracing_subscriber::fmt::try_init();
if let Ok(s) = std::str::from_utf8(data) {
tracing::info!(data = ?s, "running with input");
linkerd_addr::fuzz_logic::fuzz_addr_1(s);
}
});
fuzz_target
is the entrypoint of the fuzzer and is what the underlying fuzzing
engine cargo-fuzz will call with pseudo-random data in the data
argument. The
fuzzer further calls into code in the linkerd2_addr
module, which is defined
as follows:
#[cfg(fuzzing)]
pub mod fuzz_logic {
use super::*;
pub fn fuzz_addr_1(fuzz_data: &str) {
if let Ok(addr) = Addr::from_str(fuzz_data) {
addr.is_loopback();
addr.to_http_authority();
addr.is_loopback();
addr.socket_addr();
}
if let Ok(name_addr) = NameAddr::from_str_and_port(fuzz_data, 1234) {
name_addr.port();
name_addr.as_http_authority();
}
}
}
We use this indirection of having fuzzing-related code in the modules themselves to align with the scoping of the proxy code.
We wrap our fuzzing code in [#cfg(fuzzing)]
to avoid shipping the fuzzing code
when build the release binaries.
We compile and run the above fuzzer with the following commands:
# Build the fuzzer
cd linkerd/addr
cargo +nightly fuzz build
# Run fuzzer
./fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_target_1
This is also the sequence of commands to use for running the fuzzers locally.
The larger fuzzers we keep follow a similar structural set up as to the unit-test-like fuzzers, but are essentially just more substantial in nature. The idea behind these fuzzers is to test end-to-end concepts more so than individual components of the proxy.
The inbound fuzzer here is an example of this.