Skip to content

Commit 43f2fd6

Browse files
authored
RUST-939 Run BSON fuzzer in Evergreen (#343)
1 parent 786ff84 commit 43f2fd6

File tree

9 files changed

+90
-8
lines changed

9 files changed

+90
-8
lines changed

.evergreen/config.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ functions:
112112
${PREPARE_SHELL}
113113
.evergreen/check-clippy.sh
114114
115+
"run fuzzer":
116+
- command: shell.exec
117+
type: test
118+
params:
119+
shell: bash
120+
working_dir: "src"
121+
script: |
122+
${PREPARE_SHELL}
123+
.evergreen/install-fuzzer.sh
124+
.evergreen/run-fuzzer.sh
125+
115126
"check rustdoc":
116127
- command: shell.exec
117128
type: test
@@ -162,6 +173,10 @@ tasks:
162173
commands:
163174
- func: "check rustdoc"
164175

176+
- name: "run-fuzzer"
177+
commands:
178+
- func: "run fuzzer"
179+
165180
axes:
166181
- id: "extra-rust-versions"
167182
values:
@@ -200,3 +215,11 @@ buildvariants:
200215
- name: "check-clippy"
201216
- name: "check-rustfmt"
202217
- name: "check-rustdoc"
218+
219+
-
220+
name: "fuzz"
221+
display_name: "Raw BSON Fuzzer"
222+
run_on:
223+
- ubuntu1804-test
224+
tasks:
225+
- name: "run-fuzzer"

.evergreen/install-fuzzer.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
5+
. ~/.cargo/env
6+
7+
cargo install cargo-fuzz

.evergreen/run-fuzzer.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
5+
. ~/.cargo/env
6+
7+
cd fuzz
8+
9+
# each runs for a minute
10+
cargo +nightly fuzz run deserialize -- -rss_limit_mb=4096 -max_total_time=60
11+
cargo +nightly fuzz run raw_deserialize -- -rss_limit_mb=4096 -max_total_time=60
12+
cargo +nightly fuzz run iterate -- -rss_limit_mb=4096 -max_total_time=60

fuzz/Cargo.toml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,20 @@ cargo-fuzz = true
1111
[dependencies.bson]
1212
path = ".."
1313
[dependencies.libfuzzer-sys]
14-
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
14+
version = "0.4.0"
1515

1616
# Prevent this from interfering with workspaces
1717
[workspace]
1818
members = ["."]
1919

20-
[[bin]]
21-
name = "fuzz_target_1"
22-
path = "fuzz_targets/fuzz_target_1.rs"
23-
2420
[[bin]]
2521
name = "deserialize"
2622
path = "fuzz_targets/deserialize.rs"
23+
24+
[[bin]]
25+
name = "iterate"
26+
path = "fuzz_targets/iterate.rs"
27+
28+
[[bin]]
29+
name = "raw_deserialize"
30+
path = "fuzz_targets/raw_deserialize.rs"

fuzz/fuzz_targets/iterate.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![no_main]
2+
#[macro_use] extern crate libfuzzer_sys;
3+
extern crate bson;
4+
use bson::RawDocument;
5+
6+
fuzz_target!(|buf: &[u8]| {
7+
if let Ok(doc) = RawDocument::from_bytes(buf) {
8+
for _ in doc {}
9+
}
10+
});

fuzz/fuzz_targets/raw_deserialize.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![no_main]
2+
#[macro_use] extern crate libfuzzer_sys;
3+
extern crate bson;
4+
use bson::Document;
5+
6+
fuzz_target!(|buf: &[u8]| {
7+
let _ = bson::from_slice::<Document>(buf);
8+
});

src/de/raw.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ impl<'de> Deserializer<'de> {
171171
where
172172
F: FnOnce(DocumentAccess<'_, 'de>) -> Result<O>,
173173
{
174-
let mut length_remaining = read_i32(&mut self.bytes)? - 4;
174+
let mut length_remaining = read_i32(&mut self.bytes)?
175+
.checked_sub(4)
176+
.ok_or_else(|| Error::custom("invalid length, less than min document size"))?;
175177
let out = f(DocumentAccess {
176178
root_deserializer: self,
177179
length_remaining: &mut length_remaining,

src/raw/iter.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,14 @@ impl<'a> Iterator for Iter<'a> {
175175
ElementType::Binary => {
176176
let len = i32_from_slice(&self.doc.as_bytes()[valueoffset..])? as usize;
177177
let data_start = valueoffset + 4 + 1;
178-
self.verify_enough_bytes(valueoffset, len)?;
178+
179+
if len >= i32::MAX as usize {
180+
return Err(Error::new_without_key(ErrorKind::MalformedValue {
181+
message: format!("binary length exceeds maximum: {}", len),
182+
}));
183+
}
184+
185+
self.verify_enough_bytes(valueoffset + 4, len + 1)?;
179186
let subtype = BinarySubtype::from(self.doc.as_bytes()[valueoffset + 4]);
180187
let data = match subtype {
181188
BinarySubtype::BinaryOld => {

src/raw/mod.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ fn f64_from_slice(val: &[u8]) -> Result<f64> {
171171
/// Given a u8 slice, return an i32 calculated from the first four bytes in
172172
/// little endian order.
173173
fn i32_from_slice(val: &[u8]) -> Result<i32> {
174-
let arr = val
174+
let arr: [u8; 4] = val
175175
.get(0..4)
176176
.and_then(|s| s.try_into().ok())
177177
.ok_or_else(|| {
@@ -213,6 +213,15 @@ fn read_nullterminated(buf: &[u8]) -> Result<&str> {
213213
}
214214

215215
fn read_lenencoded(buf: &[u8]) -> Result<&str> {
216+
if buf.len() < 4 {
217+
return Err(Error::new_without_key(ErrorKind::MalformedValue {
218+
message: format!(
219+
"expected buffer with string to contain at least 4 bytes, but it only has {}",
220+
buf.len()
221+
),
222+
}));
223+
}
224+
216225
let length = i32_from_slice(&buf[..4])?;
217226
let end = checked_add(usize_try_from_i32(length)?, 4)?;
218227

0 commit comments

Comments
 (0)