Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add a first infrastructure for fuzzing #1216

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ version = "0.53.0"

[workspace.dependencies]
Inflector = "0.11.4"
arbitrary = { version = "1", features = ["derive"] }
async-trait = { version = "0.1.73", default-features = false }
bech32 = "0.9.1"
bytes = { version = "1.5.0", default-features = false }
Expand Down
4 changes: 4 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
target
corpus
artifacts
coverage
46 changes: 46 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
[package]
name = "fuels-rs-fuzz"
version = "0.0.0"
publish = false

[package.metadata]
cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
fuels = { path = "../packages/fuels", features = ["arbitrary"] }
fuels-accounts = { path = "../packages/fuels-accounts", features = ["arbitrary"] }
fuels-code-gen = { path = "../packages/fuels-code-gen", features = ["arbitrary"] }
fuels-core = { path = "../packages/fuels-core", features = ["arbitrary"] }
fuels-macros = { path = "../packages/fuels-macros", features = ["arbitrary"] }
fuels-programs = { path = "../packages/fuels-programs", features = ["arbitrary"] }
fuels-test-helpers = { path = "../packages/fuels-test-helpers", features = ["arbitrary"] }


# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[profile.release]
debug = 1

[[bin]]
name = "abi_decoder"
path = "fuzz_targets/abi_decoder.rs"
test = false
doc = false
[[bin]]
name = "abi_encoder"
path = "fuzz_targets/abi_encoder.rs"
test = false
doc = false
[[bin]]
name = "something"
path = "fuzz_targets/something.rs"
test = false
doc = false
[[bin]]
name = "param_type"
path = "fuzz_targets/param_type.rs"
test = false
doc = false
1 change: 1 addition & 0 deletions packages/fuels-accounts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rust-version = { workspace = true }
description = "Fuel Rust SDK accounts."

[dependencies]
arbitrary = { workspace = true, optional = true, features = ["derive"] }
async-trait = { workspace = true, default-features = false }
chrono = { workspace = true }
elliptic-curve = { workspace = true, default-features = false }
Expand Down
1 change: 1 addition & 0 deletions packages/fuels-code-gen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rust-version = { workspace = true }
description = "Used for code generation in the Fuel Rust SDK"

[dependencies]
arbitrary = { workspace = true, optional = true, features = ["derive"] }
Inflector = { workspace = true }
fuel-abi-types = { workspace = true }
itertools = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions packages/fuels-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ rust-version = { workspace = true }
description = "Fuel Rust SDK core."

[dependencies]
arbitrary = { version = "1", optional = true, features = ["derive"] }
async-trait = { workspace = true, default-features = false }
bech32 = { workspace = true }
chrono = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion packages/fuels-core/src/codec/abi_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ mod tests {
},
&[],
);
assert!(matches!(result, Err(Error::InvalidData(_))));
assert!(matches!(result, Err(Error::InvalidType(_))));
}

#[test]
Expand Down
35 changes: 17 additions & 18 deletions packages/fuels-core/src/codec/abi_decoder/bounded_decoder.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{convert::TryInto, str};

use crate::{
checked_round_up_to_word_alignment,
codec::DecoderConfig,
constants::WORD_SIZE,
round_up_to_word_alignment,
traits::Tokenizable,
types::{
enum_variants::EnumVariants,
Expand Down Expand Up @@ -151,7 +151,7 @@ impl BoundedDecoder {

for param_type in param_types.iter() {
// padding has to be taken into account
bytes_read = round_up_to_word_alignment(bytes_read);
bytes_read = checked_round_up_to_word_alignment(bytes_read)?;
let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?;
bytes_read += res.bytes_read;
tokens.push(res.token);
Expand All @@ -170,7 +170,7 @@ impl BoundedDecoder {

for param_type in param_types.iter() {
// padding has to be taken into account
bytes_read = round_up_to_word_alignment(bytes_read);
bytes_read = checked_round_up_to_word_alignment(bytes_read)?;
let res = self.decode_param(param_type, skip(bytes, bytes_read)?)?;
bytes_read += res.bytes_read;
tokens.push(res.token);
Expand Down Expand Up @@ -249,7 +249,7 @@ impl BoundedDecoder {
let decoded = str::from_utf8(encoded_str)?;
let result = Decoded {
token: Token::StringArray(StaticStringToken::new(decoded.into(), Some(length))),
bytes_read: round_up_to_word_alignment(length),
bytes_read: checked_round_up_to_word_alignment(length)?,
};
Ok(result)
}
Expand Down Expand Up @@ -333,24 +333,23 @@ impl BoundedDecoder {
/// * `data`: slice of encoded data on whose beginning we're expecting an encoded enum
/// * `variants`: all types that this particular enum type could hold
fn decode_enum(&mut self, bytes: &[u8], variants: &EnumVariants) -> Result<Decoded> {
let enum_width_in_bytes = variants
.compute_enum_width_in_bytes()
.ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?;
let enum_width_in_bytes = variants.compute_enum_width_in_bytes()?;

let discriminant = peek_u64(bytes)?;
let selected_variant = variants.param_type_of_variant(discriminant)?;

let skip_extra_in_bytes = variants
.heap_type_variant()
.and_then(|(heap_discriminant, heap_type)| {
(heap_discriminant == discriminant).then_some(heap_type.compute_encoding_in_bytes())
})
.unwrap_or_default()
.unwrap_or_default();
let bytes_to_skip = enum_width_in_bytes
- selected_variant
.compute_encoding_in_bytes()
.ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?
let skip_extra_in_bytes = match variants.heap_type_variant() {
Some((heap_type_discriminant, heap_type)) => {
if heap_type_discriminant == discriminant {
heap_type.compute_encoding_in_bytes()?
} else {
0
}
}
None => 0,
};

let bytes_to_skip = enum_width_in_bytes - selected_variant.compute_encoding_in_bytes()?
+ skip_extra_in_bytes;

let enum_content_bytes = skip(bytes, bytes_to_skip)?;
Expand Down
9 changes: 6 additions & 3 deletions packages/fuels-core/src/codec/abi_encoder.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use fuel_types::bytes::padded_len_usize;

use crate::{
checked_round_up_to_word_alignment,
constants::WORD_SIZE,
round_up_to_word_alignment,
types::{
errors::Result,
pad_u16, pad_u32,
Expand Down Expand Up @@ -50,8 +50,11 @@ impl ABIEncoder {
data.append(&mut new_data);

if word_aligned {
let padding =
vec![0u8; round_up_to_word_alignment(offset_in_bytes) - offset_in_bytes];
let padding = vec![
0u8;
checked_round_up_to_word_alignment(offset_in_bytes)?
- offset_in_bytes
];
if !padding.is_empty() {
offset_in_bytes += padding.len();
data.push(Data::Inline(padding));
Expand Down
2 changes: 2 additions & 0 deletions packages/fuels-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub type Selector = ByteArray;
pub type EnumSelector = (u64, Token, EnumVariants);

#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct StaticStringToken {
data: String,
expected_len: Option<usize>,
Expand Down Expand Up @@ -72,6 +73,7 @@ impl TryFrom<StaticStringToken> for String {
}

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Token {
// Used for unit type variants in Enum. An "empty" enum is not represented as Enum<empty box>,
// because this way we can have both unit and non-unit type variants.
Expand Down
1 change: 1 addition & 0 deletions packages/fuels-core/src/types/core/u256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{

construct_uint! {
/// 256-bit unsigned integer.
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct U256(4);
}

Expand Down
24 changes: 10 additions & 14 deletions packages/fuels-core/src/types/enum_variants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ use crate::{
errors::{error, Result},
param_types::ParamType,
},
utils::round_up_to_word_alignment,
utils::checked_round_up_to_word_alignment,
};

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct EnumVariants {
param_types: Vec<ParamType>,
}
Expand Down Expand Up @@ -48,32 +49,27 @@ impl EnumVariants {
}

/// Calculates how many bytes are needed to encode an enum.
pub fn compute_enum_width_in_bytes(&self) -> Option<usize> {
pub fn compute_enum_width_in_bytes(&self) -> Result<usize> {
if self.only_units_inside() {
return Some(ENUM_DISCRIMINANT_BYTE_WIDTH);
return Ok(ENUM_DISCRIMINANT_BYTE_WIDTH);
}

let width = self.param_types().iter().try_fold(0, |a, p| {
let size = p.compute_encoding_in_bytes()?;
Some(a.max(size))
Result::<usize>::Ok(a.max(size))
})?;

Some(round_up_to_word_alignment(width) + ENUM_DISCRIMINANT_BYTE_WIDTH)
checked_round_up_to_word_alignment(width)?
.checked_add(ENUM_DISCRIMINANT_BYTE_WIDTH)
.ok_or_else(|| error!(InvalidType, "Enum variants are too wide"))
}

/// Determines the padding needed for the provided enum variant (based on the width of the
/// biggest variant) and returns it.
pub fn compute_padding_amount_in_bytes(&self, variant_param_type: &ParamType) -> Result<usize> {
let enum_width = self
.compute_enum_width_in_bytes()
.ok_or(error!(InvalidData, "Error calculating enum width in bytes"))?;
let enum_width = self.compute_enum_width_in_bytes()?;
let biggest_variant_width = enum_width - ENUM_DISCRIMINANT_BYTE_WIDTH;
let variant_width = variant_param_type
.compute_encoding_in_bytes()
.ok_or(error!(
InvalidData,
"Error calculating padding amount in bytes"
))?;
let variant_width = variant_param_type.compute_encoding_in_bytes()?;
Ok(biggest_variant_width - variant_width)
}
}
Expand Down
Loading