Skip to content

Commit 8092fdc

Browse files
authored
Merge pull request #421 from filecoin-project/fuzz/rle-fuzz
Add RLE fuzzing and fix two bugs: - Don't panic when unsetting u64::MAX. - Don't allow bitfields with trailing "zero" ranges.
2 parents 5a1753c + 42c4f18 commit 8092fdc

File tree

19 files changed

+262
-12
lines changed

19 files changed

+262
-12
lines changed

.clusterfuzzlite/build.sh

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
cd "$SRC"
1919

20-
declare -a PROJECTS=(amt hamt)
20+
declare -a PROJECTS=(amt hamt common)
2121
declare -A PROJECT_PATHS=(
2222
[amt]="ref-fvm/ipld/amt/fuzz"
2323
[hamt]="ref-fvm/ipld/hamt/fuzz"
24+
[common]="ref-fvm/testing/common_fuzz/fuzz"
2425
)
2526

2627
export CARGO_TARGET_DIR="$SRC/target"
@@ -34,9 +35,9 @@ for project in "${PROJECTS[@]}"; do
3435

3536
for f in fuzz_targets/*.rs; do
3637
FUZZ_TARGET_NAME=$(basename "${f%.*}")
38+
FUZZ_TARGET_LOC="$FUZZ_TARGET_OUTPUT_DIR/$FUZZ_TARGET_NAME"
3739
FUZZ_OUT_NAME="${project}_${FUZZ_TARGET_NAME}"
38-
39-
cp "$FUZZ_TARGET_OUTPUT_DIR/$FUZZ_TARGET_NAME" "$OUT/$FUZZ_OUT_NAME"
40+
[ -f "$FUZZ_TARGET_LOC" ] && cp "$FUZZ_TARGET_LOC" "$OUT/$FUZZ_OUT_NAME"
4041
done
4142

4243
popd

ipld/amt/fuzz/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ edition = "2018"
99
cargo-fuzz = true
1010

1111
[dependencies]
12-
libfuzzer-sys = "0.3"
13-
arbitrary = { version = "0.4", features = ["derive"] }
12+
libfuzzer-sys = "0.4"
13+
arbitrary = { version = "1.1", features = ["derive"] }
1414
ahash = "0.7.6"
1515
itertools = "0.10.3"
1616

ipld/bitfield/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ serde = { version = "1.0", features = ["derive"] }
1313
serde_bytes = { package = "cs_serde_bytes", version = "0.12" }
1414
fvm_shared = { version = "0.3.1", path = "../../shared" }
1515
thiserror = "1.0.30"
16+
arbitrary = { version = "1.1.0", optional = true}
1617

1718
[dev-dependencies]
1819
rand_xorshift = "0.2.0"
@@ -22,6 +23,7 @@ serde_json = "1.0"
2223

2324
[features]
2425
json = []
26+
enable-arbitrary = ["arbitrary"]
2527

2628
[[bench]]
2729
name = "benchmarks"

ipld/bitfield/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ impl BitField {
170170

171171
/// Removes the bit at a given index from the bit field.
172172
pub fn unset(&mut self, bit: u64) {
173+
if bit == u64::MAX {
174+
return;
175+
}
173176
self.set.remove(&bit);
174177
self.unset.insert(bit);
175178
}

ipld/bitfield/src/rleplus/mod.rs

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ mod writer;
6767

6868
use std::borrow::Cow;
6969

70+
#[cfg(feature = "enable-arbitrary")]
71+
use arbitrary::{size_hint, Arbitrary, Unstructured};
7072
pub use error::Error;
7173
pub use reader::BitReader;
7274
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -106,6 +108,48 @@ impl<'de> Deserialize<'de> for BitField {
106108
Self::from_bytes(&bytes).map_err(serde::de::Error::custom)
107109
}
108110
}
111+
#[cfg(feature = "enable-arbitrary")]
112+
impl<'a> Arbitrary<'a> for BitField {
113+
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
114+
let mut next_value: bool = bool::arbitrary(u)?;
115+
let mut ranges = Vec::new();
116+
let mut index = 0u64;
117+
let mut total_len: u64 = 0;
118+
119+
let size = u.arbitrary_len::<(u64, u8)>()?;
120+
121+
for _ in 0..size {
122+
// 3 line crappy "power-law" distribution
123+
let len = u64::arbitrary(u)?;
124+
let shift = u.int_in_range(0..=63)?;
125+
let len = (len & (u64::MAX >> shift)).saturating_add(1);
126+
127+
let (new_total_len, ovf) = total_len.overflowing_add(len);
128+
if ovf {
129+
break;
130+
}
131+
total_len = new_total_len;
132+
let start = index;
133+
index += len;
134+
let end = index;
135+
136+
if next_value {
137+
ranges.push(start..end);
138+
}
139+
140+
next_value = !next_value;
141+
}
142+
143+
Ok(Self {
144+
ranges,
145+
..Default::default()
146+
})
147+
}
148+
149+
fn size_hint(depth: usize) -> (usize, Option<usize>) {
150+
size_hint::and(<usize as Arbitrary>::size_hint(depth), (0, None))
151+
}
152+
}
109153

110154
impl BitField {
111155
/// Decodes RLE+ encoded bytes into a bit field.
@@ -139,6 +183,12 @@ impl BitField {
139183
next_value = !next_value;
140184
}
141185

186+
// next_value equal true means we just read a run of zeros
187+
// which means that there is a trailing run of zeros
188+
if next_value {
189+
return Err(Error::NotMinimal);
190+
}
191+
142192
Ok(Self {
143193
ranges,
144194
..Default::default()
@@ -184,10 +234,9 @@ mod tests {
184234
use rand::{Rng, SeedableRng};
185235
use rand_xorshift::XorShiftRng;
186236

187-
use crate::iter::Ranges;
188-
189237
use super::super::{bitfield, ranges_from_bits};
190238
use super::{BitField, BitWriter, Error};
239+
use crate::iter::Ranges;
191240

192241
#[test]
193242
fn test() {
@@ -370,6 +419,45 @@ mod tests {
370419
],
371420
Err(Error::NotMinimal),
372421
),
422+
// tailing runs of zeros
423+
(
424+
vec![
425+
0, 0, // version
426+
0, // starts with 0
427+
1, // run of one
428+
],
429+
Err(Error::NotMinimal),
430+
),
431+
(
432+
vec![
433+
0, 0, // version
434+
0, // starts with 0
435+
0, 1, // fits into 4 bits
436+
0, 0, 1, 0,
437+
],
438+
Err(Error::NotMinimal),
439+
),
440+
(
441+
vec![
442+
0, 0, // version
443+
1, // starts with 1
444+
0, 1, // fits into 4 bits
445+
0, 0, 1, 0, // 2
446+
1, // trailing run of zeros
447+
],
448+
Err(Error::NotMinimal),
449+
),
450+
(
451+
vec![
452+
0, 0, // version
453+
0, // starts with 1
454+
1, //run of one
455+
0, 1, // fits into 4 bits
456+
0, 0, 1, 0, // 2
457+
0, 1, 0, 0, 1, 0, // 2 trailing zeros
458+
],
459+
Err(Error::NotMinimal),
460+
),
373461
]
374462
.into_iter()
375463
.enumerate()
@@ -421,6 +509,20 @@ mod tests {
421509
assert_eq!(2, last);
422510
}
423511

512+
#[test]
513+
fn test_unset_max() {
514+
// Create any bitfield
515+
let ranges: Vec<u64> = vec![0, 1, 2, 3];
516+
let iter = ranges_from_bits(ranges);
517+
let mut bf = BitField::from_ranges(iter);
518+
519+
// Unset u64::MAX
520+
bf.unset(u64::MAX);
521+
522+
let last = bf.ranges().last().unwrap();
523+
assert_eq!(0..4, last);
524+
}
525+
424526
#[test]
425527
fn test_zero_last() {
426528
let mut bf = BitField::new();

ipld/bitfield/src/unvalidated.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ use std::convert::TryFrom;
66
use fvm_shared::encoding::serde_bytes;
77
use serde::{Deserialize, Deserializer, Serialize};
88

9-
use crate::Error;
10-
119
use super::BitField;
10+
use crate::Error;
1211

1312
/// A trait for types that can produce a `&BitField` (or fail to do so).
1413
/// Generalizes over `&BitField` and `&mut UnvalidatedBitField`.

ipld/hamt/fuzz/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ edition = "2018"
99
cargo-fuzz = true
1010

1111
[dependencies]
12-
libfuzzer-sys = "0.3"
13-
arbitrary = { version = "0.4", features = ["derive"] }
12+
libfuzzer-sys = "0.4"
13+
arbitrary = { version = "1.1", features = ["derive"] }
1414
ahash = "0.7.6"
1515

1616
fvm_ipld_hamt = { path = ".." }

testing/common_fuzz/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "common_fuzz"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[workspace]
9+
members = ["."]
10+
11+
[dependencies]

testing/common_fuzz/fuzz/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
target
3+
artifacts
4+
Cargo.lock
5+
/fuzz-*

testing/common_fuzz/fuzz/Cargo.toml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[package]
2+
name = "common_fuzz"
3+
version = "0.0.0"
4+
authors = [ "Protocol Labs", "Filecoin Core Devs"]
5+
publish = false
6+
edition = "2021"
7+
8+
[package.metadata]
9+
cargo-fuzz = true
10+
11+
[dependencies]
12+
libfuzzer-sys = "0.4"
13+
arbitrary = { version = "1.1", features = ["derive"] }
14+
ahash = "0.7.6"
15+
itertools = "0.10.3"
16+
17+
cid = { version = "0.8.2", default-features = false, features = ["serde-codec"] }
18+
19+
fvm_ipld_bitfield = { path = "../../../ipld/bitfield", features = ["enable-arbitrary"] }
20+
#fvm_shared = { path = "../../../shared" }
21+
22+
# Prevent this from interfering with workspaces
23+
[workspace]
24+
members = ["."]
25+
26+
27+
[[bin]]
28+
name = "rle_ops"
29+
path = "fuzz_targets/rle_ops.rs"
30+
test = false
31+
doc = false
32+
33+
[[bin]]
34+
name = "rle_decode"
35+
path = "fuzz_targets/rle_decode.rs"
36+
test = false
37+
doc = false
38+
39+
[[bin]]
40+
name = "rle_encode"
41+
path = "fuzz_targets/rle_encode.rs"
42+
test = false
43+
doc = false

0 commit comments

Comments
 (0)