├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── ci └── fuzzit.sh ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz_parse_complex.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | language: rust 3 | rust: 4 | - nightly 5 | 6 | services: 7 | - docker 8 | 9 | env: 10 | global: 11 | # This is FUZZIT_API_KEY. see https://docs.travis-ci.com/user/environment-variables/#encrypting-environment-variables for how to create secrets in Travis. 12 | secure: "cInWOLG78+lZXfr6IsAT8CBixXVFdjW2eIS9gJuRWsNlkc8IR3UTzqMP1BaoeEcXIPKGmZALO0oYWi0NueSfKIh0D8RsSb7jCNU76sdPJh1RyJcKIQYWDzTVGj4W/jEq25DupFs8I5HZD/ItAs3vVu7Cl2Yn+Tjh9Gbb/dxEhkt6VvN48uEN1ZtYxKBualZATGHcFSt2Kv8o0cV0gDPg+RapmUmRS+l7XbUl/vlvyrTcbDLtsaftqHIrV0UWtvu13ZKSbG2iT/LIRBsIeSzzVG5C5TzwZMk7ASpDEfcEw4pnjw/KKBIdXiKmYMDl6RrPLRC3+kecyRJhlyfo8fLIEan0Bc8ZVhCEUeavXO9Nk1cMjTgpPuB9H8nbgFR6En5bpFo1VaFSGFiVCHre0p4JY5/tDQVo2An+sC2sJsRXYqo65k33fxyazjLXHH7uLpNAMro5IZGK30QHshsH0aoMGeEtRYlelqhg88P49drcyRCzwy6AA4Tm94Wm4K9fkKgJMpoZ/9JaoeBcIumTmHB+M8DmK86xLDdm8yiVqkz/Ve5jl8yRcdgyUYqOPzmAKNvzggLYh1Bsnx2yq9N+C+jgDJgaUZXV/XWLSfVm3i6NOIH8YfpJ7yoDr22IwZEtcRjLRGjQJyyHcu+ZC6Qm/xzMmpUhRhMXXjQZCOgaKhztc7Y=" 13 | 14 | jobs: 15 | include: 16 | - stage: Build, Unit-Tests & Fuzz-Tests 17 | go: 1.12.x 18 | script: 19 | - cargo install cargo-fuzz 20 | - ./ci/fuzzit.sh regression 21 | 22 | - stage: Fuzzit (Fuzzing) 23 | if: branch = master AND type IN (push) 24 | go: 1.12.x 25 | script: 26 | - cargo install cargo-fuzz 27 | - ./ci/fuzzit.sh fuzzing -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example-rust" 3 | version = "0.1.0" 4 | authors = ["Yevgeny Pats "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fuzzit.dev was [acquired](https://about.gitlab.com/press/releases/2020-06-11-gitlab-acquires-peach-tech-and-fuzzit-to-expand-devsecops-offering.html) by GitLab and the new home for this repo is [here](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) 2 | 3 | [![Build Status](https://travis-ci.org/fuzzitdev/example-go.svg?branch=master)](https://travis-ci.org/fuzzitdev/example-rust) 4 | [![fuzzit](https://app.fuzzit.dev/badge?org_id=fuzzitdev&branch=master)](https://fuzzit.dev) 5 | 6 | # Continuous Rust Fuzzing Example 7 | 8 | This is an example of how to integrate your [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) targets with 9 | [Fuzzit](https://fuzzit.dev) Continuous Fuzzing Platform (Rust support is currently in Alpha). 10 | 11 | This example will show the following steps: 12 | * [Building and running locally a simple cargo-fuzz target](#building--running-the-fuzzer) 13 | * [Integrate the cargo-fuzz target with Fuzzit via Travis-CI](#integrating-with-fuzzit-from-ci) 14 | 15 | Result: 16 | * Fuzzit will run the fuzz targets continuously on daily basis with the latest release. 17 | * Fuzzit will run regression tests on every pull-request with the generated corpus and crashes to catch bugs early on. 18 | 19 | Fuzzing for Rust can both help find complex bugs as well as correctness bugs. Rust is a safe language so memory corruption bugs 20 | are very unlikely to happen but some bugs can still have security implications. 21 | 22 | This tutorial is less about how to build cargo-fuzz targets but more about how to integrate the targets with Fuzzit. A lot of 23 | great information is available at the [cargo-fuzz](https://rust-fuzz.github.io/book/cargo-fuzz.html) repository. 24 | 25 | ### Understanding the bug 26 | 27 | The bug is located at `src/lib.rs` with the following code 28 | 29 | ```rust 30 | 31 | pub fn parse_complex(data: &[u8]) -> bool{ 32 | if data.len() == 5 { 33 | 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' { 34 | return true 35 | } 36 | } 37 | return true; 38 | } 39 | ``` 40 | 41 | This is the simplest example to demonstrate a classic off-by-one/out-of-bound error which causes the program to crash. 42 | Instead of `len(data) == 5` the correct code will be `len(data) == 6`. 43 | 44 | ### Understanding the fuzzer 45 | 46 | the fuzzer is located at `fuzz/fuzz_targets/fuzz_target_1.rs` with the following code: 47 | 48 | ```rust 49 | 50 | fuzz_target!(|data: &[u8]| { 51 | let _ = example_rust::parse_complex(&data); 52 | }); 53 | 54 | ``` 55 | 56 | ### Building & Running the fuzzer 57 | 58 | Cargo fuzz required the nightly compiled ar describe in the cargo fuzz [book](https://rust-fuzz.github.io/book/cargo-fuzz.html) 59 | 60 | ```bash 61 | cargo fuzz run fuzz_target_1 62 | ``` 63 | 64 | 65 | Will print the following output and stacktrace: 66 | 67 | ```text 68 | INFO: Seed: 3265732669 69 | INFO: Loaded 1 modules (480 guards): 480 [0x100da12d8, 0x100da1a58), 70 | INFO: 6 files found in /Users/yevgenyp/PycharmProjects/example-rust/fuzz/corpus/fuzz_target_1 71 | INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes 72 | INFO: seed corpus: files: 6 min: 1b max: 5b total: 26b rss: 27Mb 73 | #7 INITED cov: 87 ft: 87 corp: 5/21b lim: 4 exec/s: 0 rss: 27Mb 74 | #262144 pulse cov: 87 ft: 87 corp: 5/21b lim: 261 exec/s: 131072 rss: 51Mb 75 | 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 76 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. 77 | ==84593== ERROR: libFuzzer: deadly signal 78 | #0 0x1025ae445 in __sanitizer_print_stack_trace (lib__rustc__clang_rt.asan_osx_dynamic.dylib:x86_64+0x4c445) 79 | #1 0x100d23b12 in fuzzer::PrintStackTrace() FuzzerUtil.cpp:206 80 | #2 0x100d0756a in fuzzer::Fuzzer::CrashCallback() FuzzerLoop.cpp:237 81 | #3 0x100d0750d in fuzzer::Fuzzer::StaticCrashSignalCallback() FuzzerLoop.cpp:209 82 | #4 0x100d50a07 in fuzzer::CrashHandler(int, __siginfo*, void*) FuzzerUtilPosix.cpp:36 83 | #5 0x7fff69804b5c in _sigtramp (libsystem_platform.dylib:x86_64+0x4b5c) 84 | #6 0x106db5b75 in dyld::fastBindLazySymbol(ImageLoader**, unsigned long) (dyld:x86_64+0x4b75) 85 | #7 0x7fff696be6a5 in abort (libsystem_c.dylib:x86_64+0x5b6a5) 86 | #8 0x100d79288 in panic_abort::__rust_start_panic::abort::h15c0489ebcc623d0 lib.rs:48 87 | #9 0x100d79278 in __rust_start_panic lib.rs:44 88 | #10 0x100d78b98 in rust_panic panicking.rs:526 89 | #11 0x100d78b79 in std::panicking::rust_panic_with_hook::h111bdf4b9efb2f62 panicking.rs:497 90 | #12 0x100d7858c in std::panicking::continue_panic_fmt::ha408c1f6b7a89584 panicking.rs:384 91 | #13 0x100d78478 in rust_begin_unwind panicking.rs:311 92 | #14 0x100d8b3d1 in core::panicking::panic_fmt::h22e65e952cbe8c74 panicking.rs:85 93 | #15 0x100d8b388 in core::panicking::panic_bounds_check::h3ed7e9d8bf4f5005 panicking.rs:61 94 | #16 0x100cf963e in example_rust::parse_complex::h2ee809da6efcf96d lib.rs:17 95 | #17 0x100cf82aa in rust_fuzzer_test_input fuzz_target_1.rs:6 96 | #18 0x100d048c5 in libfuzzer_sys::test_input_wrap::_$u7b$$u7b$closure$u7d$$u7d$::h39216f33af358cfa lib.rs:11 97 | #19 0x100d0022c in std::panicking::try::do_call::h99bafe87b57c13d6 panicking.rs:296 98 | #20 0x100d7926b in __rust_maybe_catch_panic lib.rs:28 99 | #21 0x100cff9fc in std::panicking::try::he224cd8d43f275c5 panicking.rs:275 100 | #22 0x100cfe3a5 in std::panic::catch_unwind::hdccdbf00115971fe panic.rs:394 101 | #23 0x100d04419 in LLVMFuzzerTestOneInput lib.rs:9 102 | #24 0x100d09111 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:576 103 | #25 0x100d087b9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:485 104 | #26 0x100d0ac78 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:713 105 | #27 0x100d0bfb1 in fuzzer::Fuzzer::Loop(std::__1::vector, std::__1::allocator >, fuzzer::fuzzer_allocator, std::__1::allocator > > > const&) FuzzerLoop.cpp:844 106 | #28 0x100d3f4bb in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:765 107 | #29 0x100d61629 in main FuzzerMain.cpp:20 108 | #30 0x7fff696193d4 in start (libdyld.dylib:x86_64+0x163d4) 109 | 110 | NOTE: libFuzzer has rudimentary signal handlers. 111 | Combine libFuzzer with AddressSanitizer or similar for better crash reports. 112 | SUMMARY: libFuzzer: deadly signal 113 | MS: 1 ChangeByte-; base unit: e4fd1292391a997176aa1c86db666f2d5d48fb90 114 | 0x46,0x55,0x5a,0x5a,0x49, 115 | FUZZI 116 | 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 117 | Base64: RlVaWkk= 118 | ``` 119 | 120 | ## Integrating with Fuzzit from CI 121 | 122 | The best way to integrate with Fuzzit is by adding a two stages in your Contintous Build system 123 | (like Travis CI or Circle CI). 124 | 125 | Fuzzing stage: 126 | 127 | * Build a fuzz target 128 | * Download `fuzzit` cli 129 | * Authenticate via passing `FUZZIT_API_KEY` environment variable 130 | * Create a fuzzing job by uploading fuzz target 131 | 132 | Regression stage 133 | * Build a fuzz target 134 | * Download `fuzzit` cli 135 | * Authenticate via passing `FUZZIT_API_KEY` environment variable OR defining the corpus as public. This way 136 | No authentication would be require and regression can be used for [forked PRs](https://docs.travis-ci.com/user/pull-requests#pull-requests-and-security-restrictions) as well 137 | * create a local regression fuzzing job - This will pull all the generated corpus and run them through 138 | the fuzzing binary. If new bugs are introduced this will fail the CI and alert 139 | 140 | here is the relevant snippet from the [./ci/fuzzit.sh](https://github.com/fuzzitdev/example-rust/blob/master/ci/fuzzit.sh) 141 | which is being run by [.travis.yml](https://github.com/fuzzitdev/example-rust/blob/master/.travis.yml) 142 | 143 | ```bash 144 | wget -q -O https://github.com/fuzzitdev/fuzzit/releases/latest/download/fuzzit_Linux_x86_64 145 | chmod a+x fuzzit 146 | if [ $1 == "fuzzing" ]; then 147 | ./fuzzit create job fuzzitdev/rust-parse-complex ./fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_parse_complex 148 | else 149 | ./fuzzit create job --type local-regression fuzzitdev/rust-parse-complex ./fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_parse_complex 150 | fi 151 | ``` 152 | 153 | NOTE: the url for Fuzzit works as follows (all the relase can be access through github releases) 154 | https://github.com/fuzzitdev/fuzzit/releases/latest/download/fuzzit_${OS}_${ARCH}. 155 | Valid values for `${OS}` are: `Linux`, `Darwin`, `Windows`. 156 | Valid values for `${ARCH}` are: `x86_64` and `i386`. 157 | 158 | The steps are: 159 | * Authenticate with the API key (you should keep this secret) you can find in the fuzzit settings dashboard. 160 | * Upload the fuzzer via create job command and create the fuzzing job. In This example we use two type of jobs: 161 | * Fuzzing job which is run on every push to master which continuous the previous job just with the new release. 162 | Continuous means all the current corpus is kept and the fuzzer will try to find new paths in the newly added code 163 | * In a Pull-Request the fuzzer will run a quick "sanity" test running the fuzzer through all the generated corpus 164 | and crashes to see if the Pull-Request doesnt introduce old or new crashes. This will be alred via the configured 165 | channel in the dashboard 166 | * The Target is not a secret. This ID can be retrieved from the dashboard after your create the appropriate target in the dashboard. 167 | Each target has it's own corpus and crashes. 168 | -------------------------------------------------------------------------------- /ci/fuzzit.sh: -------------------------------------------------------------------------------- 1 | set -xe 2 | 3 | ## build fuzzer 4 | cargo fuzz run fuzz_parse_complex -- -runs=0 5 | 6 | wget -q -O fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.29/fuzzit_Linux_x86_64 7 | chmod a+x fuzzit 8 | 9 | if [ $1 == "fuzzing" ]; then 10 | ./fuzzit create job fuzzitdev/rust-parse-complex ./fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_parse_complex 11 | else 12 | ./fuzzit create job --type local-regression fuzzitdev/rust-parse-complex ./fuzz/target/x86_64-unknown-linux-gnu/debug/fuzz_parse_complex 13 | fi -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "example-rust-fuzz" 4 | version = "0.0.1" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies.example-rust] 12 | path = ".." 13 | [dependencies.libfuzzer-sys] 14 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "fuzz_parse_complex" 22 | path = "fuzz_targets/fuzz_parse_complex.rs" 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_parse_complex.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate example_rust; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = example_rust::parse_complex(&data); 7 | }); 8 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use crate::parse_complex; 4 | 5 | #[test] 6 | fn it_works() { 7 | assert_eq!(2 + 2, 4); 8 | } 9 | 10 | fn it_works2() { 11 | assert_eq!(parse_complex(&[]), true); 12 | } 13 | } 14 | 15 | pub fn parse_complex(data: &[u8]) -> bool{ 16 | if data.len() == 5 { 17 | 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' { 18 | return true 19 | } 20 | } 21 | return true; 22 | } --------------------------------------------------------------------------------