# Continuous Fuzzing for Rust Example This is an example of how to integrate your [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) targets with into GitLab CI/CD. This example will show the following steps: * [Building and running locally a simple cargo-fuzz target](#building--running-the-fuzzer) * [Integrate the cargo-fuzz target via GitLab CI/CD](#running-cargo-fuzz-from-ci) Result: * The cargo-fuzz targets will run continuously on the master branch. * The libFuzzer targets will run regression tests on every pull-request (and every other branch) with the generated corpus and crashes to catch bugs early on. Fuzzing for Rust can both help find complex bugs as well as correctness bugs. Rust is a safe language so memory corruption bugs are very unlikely to happen but some bugs can still have security implications. This tutorial is less about how to build cargo-fuzz targets but more about how to integrate the targets with GitLab. A lot of great information is available at the [cargo-fuzz](https://rust-fuzz.github.io/book/cargo-fuzz.html) repository. ### Understanding the bug The bug is located at `src/lib.rs` with the following code ```rust pub fn parse_complex(data: &[u8]) -> bool{ if data.len() == 5 { if data[0] == b'F' && data[1] == b'U' && data[2] == b'Z' && data[3] == b'Z' && data[4] == b'I' && data[5] == b'T' { return true } } return true; } ``` This is the simplest example to demonstrate a classic off-by-one/out-of-bound error which causes the program to crash. Instead of `len(data) == 5` the correct code will be `len(data) == 6`. ### Understanding the fuzzer the fuzzer is located at `fuzz/fuzz_targets/fuzz_target_1.rs` with the following code: ```rust fuzz_target!(|data: &[u8]| { let _ = example_rust::parse_complex(&data); }); ``` ### Building & Running the fuzzer Cargo fuzz required the nightly compiled ar describe in the cargo fuzz [book](https://rust-fuzz.github.io/book/cargo-fuzz.html) ```bash cargo fuzz run fuzz_parse_complex ``` Will print the following output and stacktrace: ```text INFO: Seed: 3265732669 INFO: Loaded 1 modules (480 guards): 480 [0x100da12d8, 0x100da1a58), INFO: 6 files found in /Users/yevgenyp/PycharmProjects/example-rust/fuzz/corpus/fuzz_target_1 INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: seed corpus: files: 6 min: 1b max: 5b total: 26b rss: 27Mb #7 INITED cov: 87 ft: 87 corp: 5/21b lim: 4 exec/s: 0 rss: 27Mb #262144 pulse cov: 87 ft: 87 corp: 5/21b lim: 261 exec/s: 131072 rss: 51Mb thread '' panicked at 'index out of bounds: the len is 5 but the index is 5', /Users/yevgenyp/PycharmProjects/example-rust/src/lib.rs:17:101 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. ==84593== ERROR: libFuzzer: deadly signal #0 0x1025ae445 in __sanitizer_print_stack_trace (lib__rustc__clang_rt.asan_osx_dynamic.dylib:x86_64+0x4c445) #1 0x100d23b12 in fuzzer::PrintStackTrace() FuzzerUtil.cpp:206 #2 0x100d0756a in fuzzer::Fuzzer::CrashCallback() FuzzerLoop.cpp:237 #3 0x100d0750d in fuzzer::Fuzzer::StaticCrashSignalCallback() FuzzerLoop.cpp:209 #4 0x100d50a07 in fuzzer::CrashHandler(int, __siginfo*, void*) FuzzerUtilPosix.cpp:36 #5 0x7fff69804b5c in _sigtramp (libsystem_platform.dylib:x86_64+0x4b5c) #6 0x106db5b75 in dyld::fastBindLazySymbol(ImageLoader**, unsigned long) (dyld:x86_64+0x4b75) #7 0x7fff696be6a5 in abort (libsystem_c.dylib:x86_64+0x5b6a5) #8 0x100d79288 in panic_abort::__rust_start_panic::abort::h15c0489ebcc623d0 lib.rs:48 #9 0x100d79278 in __rust_start_panic lib.rs:44 #10 0x100d78b98 in rust_panic panicking.rs:526 #11 0x100d78b79 in std::panicking::rust_panic_with_hook::h111bdf4b9efb2f62 panicking.rs:497 #12 0x100d7858c in std::panicking::continue_panic_fmt::ha408c1f6b7a89584 panicking.rs:384 #13 0x100d78478 in rust_begin_unwind panicking.rs:311 #14 0x100d8b3d1 in core::panicking::panic_fmt::h22e65e952cbe8c74 panicking.rs:85 #15 0x100d8b388 in core::panicking::panic_bounds_check::h3ed7e9d8bf4f5005 panicking.rs:61 #16 0x100cf963e in example_rust::parse_complex::h2ee809da6efcf96d lib.rs:17 #17 0x100cf82aa in rust_fuzzer_test_input fuzz_target_1.rs:6 #18 0x100d048c5 in libfuzzer_sys::test_input_wrap::_$u7b$$u7b$closure$u7d$$u7d$::h39216f33af358cfa lib.rs:11 #19 0x100d0022c in std::panicking::try::do_call::h99bafe87b57c13d6 panicking.rs:296 #20 0x100d7926b in __rust_maybe_catch_panic lib.rs:28 #21 0x100cff9fc in std::panicking::try::he224cd8d43f275c5 panicking.rs:275 #22 0x100cfe3a5 in std::panic::catch_unwind::hdccdbf00115971fe panic.rs:394 #23 0x100d04419 in LLVMFuzzerTestOneInput lib.rs:9 #24 0x100d09111 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:576 #25 0x100d087b9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:485 #26 0x100d0ac78 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:713 #27 0x100d0bfb1 in fuzzer::Fuzzer::Loop(std::__1::vector, std::__1::allocator >, fuzzer::fuzzer_allocator, std::__1::allocator > > > const&) FuzzerLoop.cpp:844 #28 0x100d3f4bb in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:765 #29 0x100d61629 in main FuzzerMain.cpp:20 #30 0x7fff696193d4 in start (libdyld.dylib:x86_64+0x163d4) NOTE: libFuzzer has rudimentary signal handlers. Combine libFuzzer with AddressSanitizer or similar for better crash reports. SUMMARY: libFuzzer: deadly signal MS: 1 ChangeByte-; base unit: e4fd1292391a997176aa1c86db666f2d5d48fb90 0x46,0x55,0x5a,0x5a,0x49, FUZZI artifact_prefix='/Users/yevgenyp/PycharmProjects/example-rust/fuzz/artifacts/fuzz_target_1/'; Test unit written to /Users/yevgenyp/PycharmProjects/example-rust/fuzz/artifacts/fuzz_target_1/crash-df779ced6b712c5fca247e465de2de474d1d23b9 Base64: RlVaWkk= ``` ## Running cargo-fuzz from CI The best way to integrate go-fuzz fuzzing with Gitlab CI/CD is by adding additional stage & step to your `.gitlab-ci.yml`. ```yaml include: - template: Coverage-Fuzzing.gitlab-ci.yml my_fuzz_target: extends: .fuzz_base script: - apt-get update -qq && apt-get install -y -qq git make clang cmake - export CC=`which clang` - export CXX=`which clang++` - cargo install cargo-fuzz - cargo fuzz run fuzz_parse_complex -- -runs=0 - ./gitlab-cov-fuzz run --regression=$REGRESSION -- ./fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_parse_complex ``` For each fuzz target you will have to create a step which extends .fuzz_base that runs the following: - Builds the fuzz target. - Runs the fuzz target via `gitlab-cov-fuzz` CLI. - For $CI_DEFAULT_BRANCH (can be override by $COV_FUZZING_BRANCH) will run fully fledged fuzzing sessions. For everything else including MRs will run fuzzing regression with the accumulated corpus and fixed crashes.