README.md 6.96 KB
Newer Older
1
# Continuous Fuzzing for Rust Example
2
3

This is an example of how to integrate your [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) targets with 
4
into GitLab CI/CD.
5
6
7

This example will show the following steps:
* [Building and running locally a simple cargo-fuzz target](#building--running-the-fuzzer)
8
* [Integrate the cargo-fuzz target via GitLab CI/CD](#running-cargo-fuzz-from-ci)
9
10

Result:
11
12
13
* 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.
14
15
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
are very unlikely to happen but some bugs can still have security implications.

18
19
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.
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

### 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
57
cargo fuzz run fuzz_parse_complex
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
```


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 '<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
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::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
    #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=
```

116
## Running cargo-fuzz from CI
117

118
The best way to integrate go-fuzz fuzzing with Gitlab CI/CD is by adding additional stage & step to your `.gitlab-ci.yml`.
119

120
121
122
```yaml
include:
  - template: Coverage-Fuzzing.gitlab-ci.yml
123

124
125
126
127
128
129
130
131
132
133
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
```
134

135
For each fuzz target you will have to create a step which extends .fuzz_base that runs the following:
136

137
138
139
140
- 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.