Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.35
0.1.36
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "skribe"
version = "0.1.35"
version = "0.1.36"
description = "Property testing for Stylus smart contracts"
readme = "README.md"
requires-python = "~=3.10"
Expand Down
37 changes: 35 additions & 2 deletions skribe-fuzz-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions skribe-fuzz-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ version = "0.1.0"
edition = "2024"

[workspace.dependencies]
alloy-dyn-abi = "1.5.7"
alloy-dyn-abi = { version = "1.5.7", features = ["arbitrary"] }
alloy-json-abi = "1.5.7"
alloy-primitives = "1.5.7"
arbitrary = "1.4.2"
hex-literal = "1.1.0"
kframework = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "1e7ee3a" }
kframework_ffi = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "1e7ee3a" }
kframework = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "73c4986" }
kframework_ffi = { git = "https://github.com/runtimeverification/kframework-rs.git", rev = "73c4986" }
libfuzzer-sys = "0.4"
pico-args = "0.5.0"
serde = "1.0.228"
Expand All @@ -26,6 +27,7 @@ edition.workspace = true
[dependencies]
alloy-json-abi.workspace = true
alloy-dyn-abi.workspace = true
arbitrary.workspace = true
serde.workspace = true
serde_json.workspace = true
kframework.workspace = true
Expand Down
1 change: 1 addition & 0 deletions skribe-fuzz-rs/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition.workspace = true
cargo-fuzz = true

[dependencies]
arbitrary.workspace = true
libfuzzer-sys.workspace = true
pico-args = { workspace = true, features = ["eq-separator"] }

Expand Down
120 changes: 107 additions & 13 deletions skribe-fuzz-rs/fuzz/fuzz_targets/fuzz_target_1.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,122 @@
#![no_main]
use std::cell::Cell;

use arbitrary::Unstructured;
use libfuzzer_sys::fuzz_target;

use pico_args::Arguments;

use skribe_fuzz_rs::{kllvm, fuzz_specs_from_json, make_dv};
use skribe_fuzz_rs::{
FuzzSpec, Signature, SignatureAbi, fuzz_specs_from_json, get_exit_code,
kllvm::{self, MarshalError, Marshaller, VarHandler},
kore,
};

struct FuzzConfig {
template: kore::Pattern,
abi: SignatureAbi,
}

struct SignatureFuzzer(Vec<u8>);

impl VarHandler for SignatureFuzzer {
fn substitute(
&mut self,
name: &str,
_sort: &kore::Sort,
) -> Result<kore::Pattern, kllvm::MarshalError> {
let sort = kore::Sort::App {
id: kore::Id::new("SortBytes".to_string()).unwrap(),
args: vec![],
};
let value = kore::Str(self.0.iter().map(|&b| b as char).collect());
match name {
"VarCALLDATA" => Ok(kore::Pattern::Dv { sort, value }),
_ => Err(MarshalError::Unsupported(
"Encountered a variable that isn't CALLDATA",
)),
}
}
}

// Persistent data across iterations.
//
// FUZZ_CONFIG - The fuzz spec + contract/function names to fuzz. Parsed from the command line
// MARSHALLER - The marshaller for moving terms over to kllvm. Keeps parts of the template
// configuration cached.
thread_local! {
static FUZZ_CONFIG: Cell<Option<FuzzConfig>> = Cell::new(None);
static MARSHALLER: Cell<Option<Marshaller<SignatureFuzzer>>> = Cell::new(Some(Marshaller::new(None)))
}

fuzz_target!( init: {
kllvm::init();
let mut args = Arguments::from_env();

let fuzz_spec_file: Option<String> = args
// You must pass this option as `--fuzz-spec=<specfile>` with the
// equals sign, otherwise libfuzzer treats it as a positional argument
.opt_value_from_str("--fuzz-spec")
// Parse arguments
//
// You must pass these options as `--xxx=<val>` with the equals sign,
// otherwise libfuzzer treats `<val>` as a positional argument.
let mut args = Arguments::from_env();
let fuzz_spec_file: String = args
.value_from_str("--fuzz-spec")
.unwrap();
let contract_name: String = args
.value_from_str("--contract-name")
.unwrap();
let function_name: String = args
.value_from_str("--function-name")
.unwrap();

if let Some(file) = fuzz_spec_file {
let contents = std::fs::read_to_string(file).unwrap();
let specs = fuzz_specs_from_json(&contents).unwrap();
println!("{:?}", specs);
}
// Parse fuzz spec
let contents = std::fs::read_to_string(fuzz_spec_file).unwrap();
let specs = fuzz_specs_from_json(&contents).unwrap();
let (template_str, signature) = extract_template_and_signature(specs, &contract_name, &function_name).unwrap();

let mut parser = kore::Parser::new(&template_str).unwrap();
let template = parser.pattern().unwrap();

let abi = SignatureAbi::from_signature(signature).unwrap();

FUZZ_CONFIG.replace(Some(FuzzConfig { template, abi }));
},
|data: &[u8]| {
let _ = make_dv();
// fuzzed code goes here
let mut marshaller_cell: Option<Marshaller<_>> = MARSHALLER.take();
let marshaller = marshaller_cell.as_mut().unwrap();
let config_cell = FUZZ_CONFIG.take();
let config = config_cell.as_ref().unwrap();

// Marshal over to kllvm with the CALLDATA variable substituted
let mut u = Unstructured::new(data);
let input = config.abi.arbitrary_input(&mut u).unwrap();
let sig = SignatureFuzzer(input);
marshaller.set_handler(sig);
let template = &config.template;
let kllvm_pattern: kllvm::Pattern = marshaller.marshal(template).unwrap();

// Execute the semantics
let mut block: kllvm::Block = kllvm_pattern.into();
block.take_steps(-1);

// Check the exit code
let exit_code = get_exit_code(&block);
if exit_code != 0 {
println!("panic!");
}

FUZZ_CONFIG.replace(config_cell);
MARSHALLER.replace(marshaller_cell);
});

pub fn extract_template_and_signature(
specs: Vec<FuzzSpec>,
contract_name: &str,
function_name: &str,
) -> Option<(String, Signature)> {
specs.into_iter().find_map(|spec| {
let template = spec.template;
spec.signatures
.into_iter()
.find(|sig| sig.contract_name == contract_name && sig.name == function_name)
.map(|sig| (template.clone(), sig))
})
}
30 changes: 26 additions & 4 deletions skribe-fuzz-rs/src/abi.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::Signature;
use alloy_dyn_abi::{DynSolType, DynSolValue, JsonAbiExt};
use alloy_json_abi::Function;
use arbitrary::Unstructured;
use std::fmt::Debug;

#[derive(Debug)]
Expand Down Expand Up @@ -30,11 +31,18 @@ impl SignatureAbi {
Ok(SignatureAbi { types, function })
}

pub fn types(&self) -> &[DynSolType] {
&self.types
pub fn arbitrary_input(&self, u: &mut Unstructured<'_>) -> arbitrary::Result<Vec<u8>> {
let values = self
.types
.iter()
.map(|ty| DynSolValue::arbitrary_from_type(ty, u))
.collect::<arbitrary::Result<Vec<_>>>()?;

self.encode_input(&values)
.map_err(|_| arbitrary::Error::IncorrectFormat)
}

pub fn encode_input(&self, values: &[DynSolValue]) -> Result<Vec<u8>, String> {
fn encode_input(&self, values: &[DynSolValue]) -> Result<Vec<u8>, String> {
self.function
.abi_encode_input(values)
.map_err(|e| e.to_string())
Expand Down Expand Up @@ -70,7 +78,7 @@ mod tests {
];

// When
let actual = abi.types();
let actual = abi.types;

// Then
assert_eq!(actual, expected);
Expand Down Expand Up @@ -119,6 +127,20 @@ mod tests {
assert_eq!(actual, expected);
}

#[test]
fn test_arbitrary_input() {
// Given
let abi = signature_abi();
let raw = vec![0u8; 256];
let mut u = Unstructured::new(&raw);

// When
let result = abi.arbitrary_input(&mut u);

// Then
assert!(result.is_ok());
}

fn signature_abi() -> SignatureAbi {
SignatureAbi::from_signature(signature()).unwrap()
}
Expand Down
Loading
Loading