|
| 1 | +[](https://travis-ci.org/fuzzitdev/example-rust) |
| 2 | +[](https://fuzzit.dev) |
| 3 | + |
| 4 | +# Continuous Rust Fuzzing Example |
| 5 | + |
| 6 | +This is an example of how to integrate your [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) targets with |
| 7 | +[Fuzzit](https://fuzzit.dev) Continuous Fuzzing Platform (Rust support is currently in Alpha). |
| 8 | + |
| 9 | +This example will show the following steps: |
| 10 | +* [Building and running locally a simple cargo-fuzz target](#building--running-the-fuzzer) |
| 11 | +* [Integrate the cargo-fuzz target with Fuzzit via Travis-CI](#integrating-with-fuzzit-from-ci) |
| 12 | + |
| 13 | +Result: |
| 14 | +* Fuzzit will run the fuzz targets continuously on daily basis with the latest release. |
| 15 | +* Fuzzit will run regression tests on every pull-request with the generated corpus and crashes to catch bugs early on. |
| 16 | + |
| 17 | +Fuzzing for Rust can both help find complex bugs as well as correctness bugs. Rust is a safe language so memory corruption bugs |
| 18 | +are very unlikely to happen but some bugs can still have security implications. |
| 19 | + |
| 20 | +This tutorial is less about how to build cargo-fuzz targets but more about how to integrate the targets with Fuzzit. A lot of |
| 21 | +great information is available at the [cargo-fuzz](https://rust-fuzz.github.io/book/cargo-fuzz.html) repository. |
| 22 | + |
| 23 | +### Understanding the bug |
| 24 | + |
| 25 | +The bug is located at `src/lib.rs` with the following code |
| 26 | + |
| 27 | +```rust |
| 28 | + |
| 29 | +pub fn parse_complex(data: &[u8]) -> bool{ |
| 30 | + if data.len() == 5 { |
| 31 | + 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' { |
| 32 | + return true |
| 33 | + } |
| 34 | + } |
| 35 | + return true; |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +This is the simplest example to demonstrate a classic off-by-one/out-of-bound error which causes the program to crash. |
| 40 | +Instead of `len(data) == 5` the correct code will be `len(data) == 6`. |
| 41 | + |
| 42 | +### Understanding the fuzzer |
| 43 | + |
| 44 | +the fuzzer is located at `fuzz/fuzz_targets/fuzz_target_1.rs` with the following code: |
| 45 | + |
| 46 | +```rust |
| 47 | + |
| 48 | +fuzz_target!(|data: &[u8]| { |
| 49 | + let _ = example_rust::parse_complex(&data); |
| 50 | +}); |
| 51 | + |
| 52 | +``` |
| 53 | + |
| 54 | +### Building & Running the fuzzer |
| 55 | + |
| 56 | +Cargo fuzz required the nightly compiled ar describe in the cargo fuzz [book](https://rust-fuzz.github.io/book/cargo-fuzz.html) |
| 57 | + |
| 58 | +```bash |
| 59 | +cargo fuzz run fuzz_target_1 |
| 60 | +``` |
| 61 | + |
| 62 | + |
| 63 | +Will print the following output and stacktrace: |
| 64 | + |
| 65 | +```text |
| 66 | +INFO: Seed: 3265732669 |
| 67 | +INFO: Loaded 1 modules (480 guards): 480 [0x100da12d8, 0x100da1a58), |
| 68 | +INFO: 6 files found in /Users/yevgenyp/PycharmProjects/example-rust/fuzz/corpus/fuzz_target_1 |
| 69 | +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes |
| 70 | +INFO: seed corpus: files: 6 min: 1b max: 5b total: 26b rss: 27Mb |
| 71 | +#7 INITED cov: 87 ft: 87 corp: 5/21b lim: 4 exec/s: 0 rss: 27Mb |
| 72 | +#262144 pulse cov: 87 ft: 87 corp: 5/21b lim: 261 exec/s: 131072 rss: 51Mb |
| 73 | +thread '<unnamed>' 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 |
| 74 | +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. |
| 75 | +==84593== ERROR: libFuzzer: deadly signal |
| 76 | + #0 0x1025ae445 in __sanitizer_print_stack_trace (lib__rustc__clang_rt.asan_osx_dynamic.dylib:x86_64+0x4c445) |
| 77 | + #1 0x100d23b12 in fuzzer::PrintStackTrace() FuzzerUtil.cpp:206 |
| 78 | + #2 0x100d0756a in fuzzer::Fuzzer::CrashCallback() FuzzerLoop.cpp:237 |
| 79 | + #3 0x100d0750d in fuzzer::Fuzzer::StaticCrashSignalCallback() FuzzerLoop.cpp:209 |
| 80 | + #4 0x100d50a07 in fuzzer::CrashHandler(int, __siginfo*, void*) FuzzerUtilPosix.cpp:36 |
| 81 | + #5 0x7fff69804b5c in _sigtramp (libsystem_platform.dylib:x86_64+0x4b5c) |
| 82 | + #6 0x106db5b75 in dyld::fastBindLazySymbol(ImageLoader**, unsigned long) (dyld:x86_64+0x4b75) |
| 83 | + #7 0x7fff696be6a5 in abort (libsystem_c.dylib:x86_64+0x5b6a5) |
| 84 | + #8 0x100d79288 in panic_abort::__rust_start_panic::abort::h15c0489ebcc623d0 lib.rs:48 |
| 85 | + #9 0x100d79278 in __rust_start_panic lib.rs:44 |
| 86 | + #10 0x100d78b98 in rust_panic panicking.rs:526 |
| 87 | + #11 0x100d78b79 in std::panicking::rust_panic_with_hook::h111bdf4b9efb2f62 panicking.rs:497 |
| 88 | + #12 0x100d7858c in std::panicking::continue_panic_fmt::ha408c1f6b7a89584 panicking.rs:384 |
| 89 | + #13 0x100d78478 in rust_begin_unwind panicking.rs:311 |
| 90 | + #14 0x100d8b3d1 in core::panicking::panic_fmt::h22e65e952cbe8c74 panicking.rs:85 |
| 91 | + #15 0x100d8b388 in core::panicking::panic_bounds_check::h3ed7e9d8bf4f5005 panicking.rs:61 |
| 92 | + #16 0x100cf963e in example_rust::parse_complex::h2ee809da6efcf96d lib.rs:17 |
| 93 | + #17 0x100cf82aa in rust_fuzzer_test_input fuzz_target_1.rs:6 |
| 94 | + #18 0x100d048c5 in libfuzzer_sys::test_input_wrap::_$u7b$$u7b$closure$u7d$$u7d$::h39216f33af358cfa lib.rs:11 |
| 95 | + #19 0x100d0022c in std::panicking::try::do_call::h99bafe87b57c13d6 panicking.rs:296 |
| 96 | + #20 0x100d7926b in __rust_maybe_catch_panic lib.rs:28 |
| 97 | + #21 0x100cff9fc in std::panicking::try::he224cd8d43f275c5 panicking.rs:275 |
| 98 | + #22 0x100cfe3a5 in std::panic::catch_unwind::hdccdbf00115971fe panic.rs:394 |
| 99 | + #23 0x100d04419 in LLVMFuzzerTestOneInput lib.rs:9 |
| 100 | + #24 0x100d09111 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:576 |
| 101 | + #25 0x100d087b9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:485 |
| 102 | + #26 0x100d0ac78 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:713 |
| 103 | + #27 0x100d0bfb1 in fuzzer::Fuzzer::Loop(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, fuzzer::fuzzer_allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > const&) FuzzerLoop.cpp:844 |
| 104 | + #28 0x100d3f4bb in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:765 |
| 105 | + #29 0x100d61629 in main FuzzerMain.cpp:20 |
| 106 | + #30 0x7fff696193d4 in start (libdyld.dylib:x86_64+0x163d4) |
| 107 | +
|
| 108 | +NOTE: libFuzzer has rudimentary signal handlers. |
| 109 | + Combine libFuzzer with AddressSanitizer or similar for better crash reports. |
| 110 | +SUMMARY: libFuzzer: deadly signal |
| 111 | +MS: 1 ChangeByte-; base unit: e4fd1292391a997176aa1c86db666f2d5d48fb90 |
| 112 | +0x46,0x55,0x5a,0x5a,0x49, |
| 113 | +FUZZI |
| 114 | +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 |
| 115 | +Base64: RlVaWkk= |
| 116 | +``` |
| 117 | + |
| 118 | +## Integrating with Fuzzit from CI |
| 119 | + |
| 120 | +The best way to integrate with Fuzzit is by adding a two stages in your Contintous Build system |
| 121 | +(like Travis CI or Circle CI). |
| 122 | + |
| 123 | +Fuzzing stage: |
| 124 | + |
| 125 | +* build a fuzz target |
| 126 | +* download `fuzzit` cli |
| 127 | +* authenticate with `fuzzit auth` |
| 128 | +* create a fuzzing job by uploading fuzz target |
| 129 | + |
| 130 | +Regression stage |
| 131 | +* build a fuzz target |
| 132 | +* download `fuzzit` cli |
| 133 | +* authenticate with `fuzzit auth` |
| 134 | +* create a local regression fuzzing job - This will pull all the generated corpus and run them through |
| 135 | +the fuzzing binary. If new bugs are introduced this will fail the CI and alert |
| 136 | + |
| 137 | +here is the relevant snippet from the [./ci/fuzzit.sh](https://github.com/fuzzitdev/example-rust/blob/master/ci/fuzzit.sh) |
| 138 | +which is being run by [.travis.yml](https://github.com/fuzzitdev/example-rust/blob/master/.travis.yml) |
| 139 | + |
| 140 | +```bash |
| 141 | +wget -q -O fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.0.0/fuzzit_Linux_x86_64 |
| 142 | +chmod a+x fuzzit |
| 143 | +./fuzzit auth ${FUZZIT_API_KEY} |
| 144 | +./fuzzit create job --type $1 --branch $TRAVIS_BRANCH --revision $TRAVIS_COMMIT $TARGET_ID ./fuzzer |
| 145 | +``` |
| 146 | + |
| 147 | +NOTE: In production it is advised to download a pinned version of the [CLI](https://github.com/fuzzitdev/fuzzit) |
| 148 | +like in the example. In development you can use latest version: |
| 149 | +https://github.com/fuzzitdev/fuzzit/releases/latest/download/fuzzit_${OS}_${ARCH}. |
| 150 | +Valid values for `${OS}` are: `Linux`, `Darwin`, `Windows`. |
| 151 | +Valid values for `${ARCH}` are: `x86_64` and `i386`. |
| 152 | + |
| 153 | +The steps are: |
| 154 | +* Authenticate with the API key (you should keep this secret) you can find in the fuzzit settings dashboard. |
| 155 | +* Upload the fuzzer via create job command and create the fuzzing job. In This example we use two type of jobs: |
| 156 | + * Fuzzing job which is run on every push to master which continuous the previous job just with the new release. |
| 157 | + Continuous means all the current corpus is kept and the fuzzer will try to find new paths in the newly added code |
| 158 | + * In a Pull-Request the fuzzer will run a quick "sanity" test running the fuzzer through all the generated corpus |
| 159 | + and crashes to see if the Pull-Request doesnt introduce old or new crashes. This will be alred via the configured |
| 160 | + channel in the dashboard |
| 161 | +* The Target is not a secret. This ID can be retrieved from the dashboard after your create the appropriate target in the dashboard. |
| 162 | +Each target has it's own corpus and crashes. |
0 commit comments