Skip to content

Commit

Permalink
✨ integrate new fuzzer
Browse files Browse the repository at this point in the history
  • Loading branch information
lukacan authored and lukacan committed Jan 10, 2024
1 parent 3c67ed4 commit de40781
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 28 deletions.
2 changes: 1 addition & 1 deletion crates/cli/src/command/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub async fn fuzz(root: Option<String>, subcmd: FuzzCommand) {
target,
crash_file_path,
} => {
Commander::run_fuzzer_debug(target, crash_file_path, root).await?;
Commander::run_fuzzer_debug(target, crash_file_path).await?;
}
FuzzCommand::Add => {
let mut generator = WorkspaceBuilder::new_with_root(root);
Expand Down
9 changes: 7 additions & 2 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ description = "The trdelnik_client crate helps you build and deploy an Anchor pr
fuzzing = [
"dep:solana-program-test",
"dep:honggfuzz",
"arbitrary/derive",
"quinn-proto/arbitrary",
"dep:solana-program-runtime",
]
poctesting = ["dep:rstest", "dep:serial_test", "dep:trdelnik-test"]
[build-dependencies]
Expand All @@ -22,6 +22,9 @@ anyhow = { version = "1.0.45", features = ["std"], default-features = false }
pretty_assertions = "1.1.0"

[dependencies]
trdelnik-derive-displayix = { path = "./derive/display_ix" }
trdelnik-derive-fuzz-deserialize = { path = "./derive/fuzz_deserialize" }
trdelnik-derive-fuzz-test-executor = { path = "./derive/fuzz_test_executor" }
solana-sdk = { workspace = true }
solana-cli-output = { workspace = true }
solana-transaction-status = { workspace = true }
Expand Down Expand Up @@ -51,9 +54,11 @@ trdelnik-test = { workspace = true, optional = true }
serial_test = { version = "2.0.0", optional = true }
rstest = { version = "0.18.2", optional = true }
honggfuzz = { version = "0.5.55", optional = true }
arbitrary = { version = "1.3.0", optional = true }
arbitrary = { version = "1.3.0", features = ["derive"] }
solana-program-test = { version = "1.16.9", optional = true }
quinn-proto = { version = "0.9.4", optional = true }
shellexpand = { workspace = true }
pathdiff = "0.2.1"
indicatif = "0.17.7"
solana-program-runtime = { version = "1.16.17", optional = true }
proc-macro2 = { workspace = true }
53 changes: 33 additions & 20 deletions crates/client/src/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,42 @@ impl Commander {
}

#[throws]
pub async fn build_program_packages(packages: &[cargo_metadata::Package]) -> Idl {
pub async fn build_program_packages(
packages: &[cargo_metadata::Package],
) -> (Idl, Vec<(String, cargo_metadata::camino::Utf8PathBuf)>) {
let shared_mutex = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
let shared_mutex_fuzzer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));

for package in packages.iter() {
let mutex = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
let c_mutex = std::sync::Arc::clone(&mutex);

let name = package.name.clone();

let mut libs = package.targets.iter().filter(|&t| t.is_lib());
let lib_path = libs
.next()
.ok_or(Error::ReadProgramCodeFailed(
"Cannot find program library path.".into(),
))?
.src_path
.clone();

let c_shared_mutex = std::sync::Arc::clone(&shared_mutex);
let c_shared_mutex_fuzzer = std::sync::Arc::clone(&shared_mutex_fuzzer);

let cargo_thread = std::thread::spawn(move || -> Result<(), Error> {
let output = Self::build_package(&name);

if output.status.success() {
let code = String::from_utf8(output.stdout).unwrap();

let idl_program = Idl::parse_to_idl_program(&name, &code).unwrap();
let mut vec = c_shared_mutex.lock().unwrap();
let mut vec_fuzzer = c_shared_mutex_fuzzer.lock().unwrap();

vec.push(idl_program);
vec_fuzzer.push((code, lib_path));

c_mutex.store(false, std::sync::atomic::Ordering::SeqCst);
Ok(())
Expand All @@ -195,13 +215,17 @@ impl Commander {
cargo_thread.join().unwrap()?;
}
let idl_programs = shared_mutex.lock().unwrap().to_vec();
let codes_libs_pairs = shared_mutex_fuzzer.lock().unwrap().to_vec();

if idl_programs.is_empty() {
throw!(Error::NoProgramsFound);
} else {
Idl {
programs: idl_programs,
}
(
Idl {
programs: idl_programs,
},
codes_libs_pairs,
)
}
}
#[throws]
Expand Down Expand Up @@ -413,28 +437,17 @@ impl Commander {

/// Runs fuzzer on the given target.
#[throws]
pub async fn run_fuzzer_debug(target: String, crash_file_path: String, root: String) {
let root = std::path::Path::new(&root);

let cur_dir = root.join(TESTS_WORKSPACE_DIRECTORY);
let crash_file = std::env::current_dir()?.join(crash_file_path);

if !cur_dir.try_exists()? {
throw!(Error::NotInitialized);
}

if !crash_file.try_exists()? {
println!("The crash file {:?} not found!", crash_file);
throw!(Error::CrashFileNotFound);
}
pub async fn run_fuzzer_debug(target: String, crash_file_path: String) {
let cargo_target_dir =
std::env::var("CARGO_TARGET_DIR").unwrap_or(CARGO_TARGET_DIR_DEFAULT.to_string());

// using exec rather than spawn and replacing current process to avoid unflushed terminal output after ctrl+c signal
std::process::Command::new("cargo")
.current_dir(cur_dir)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.arg("hfuzz")
.arg("run-debug")
.arg(target)
.arg(crash_file)
.arg(crash_file_path)
.exec();

eprintln!("cannot execute \"cargo hfuzz run-debug\" command");
Expand Down
28 changes: 28 additions & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,36 @@
#[cfg(feature = "fuzzing")]
pub mod fuzzing {
pub use super::fuzzer::accounts_storage::*;
pub use super::fuzzer::data_builder::build_ix_fuzz_data;
pub use super::fuzzer::data_builder::*;
pub use super::fuzzer::program_test_client_blocking::ProgramTestClientBlocking;
pub use super::fuzzer::snapshot::Snapshot;
pub use super::fuzzer::*;
pub use anchor_lang;
pub use arbitrary;
pub use arbitrary::Arbitrary;
pub use honggfuzz::fuzz;
pub use solana_program_test;
pub use solana_sdk;
pub use solana_sdk::pubkey::Pubkey;
pub use solana_sdk::signer::Signer;

pub use std::cell::RefCell;
pub use trdelnik_derive_displayix::DisplayIx;
pub use trdelnik_derive_fuzz_deserialize::FuzzDeserialize;
pub use trdelnik_derive_fuzz_test_executor::FuzzTestExecutor;

// -----
pub use anchor_lang::solana_program::instruction::AccountMeta;
pub use anchor_lang::{InstructionData, ToAccountMetas};
pub use solana_sdk::account::Account;
pub use solana_sdk::instruction::Instruction;
pub use solana_sdk::signer::keypair::Keypair;
pub use solana_sdk::transaction::Transaction;

pub use super::temp_clone::TempClone;
pub use solana_program_test::processor;
}

pub mod prelude {
Expand Down Expand Up @@ -73,6 +96,9 @@ use commander::Commander;
mod idl;
use idl::{Idl, IdlError};

mod fuzzer;
pub use fuzzer::*;

mod program_client_generator;

mod workspace_builder;
Expand All @@ -90,6 +116,8 @@ mod constants {
pub const LIB: &str = "lib.rs";
pub const SRC: &str = "src";

pub const ACCOUNTS_SNAPSHOTS_FILE_NAME: &str = "accounts_snapshots.rs";
pub const FUZZ_INSTRUCTIONS_FILE_NAME: &str = "fuzz_instructions.rs";
pub const TESTS_WORKSPACE_DIRECTORY: &str = "trdelnik-tests";
pub const POC_TEST_DIRECTORY: &str = "poc_tests";
pub const TESTS: &str = "tests";
Expand Down
57 changes: 52 additions & 5 deletions crates/client/src/workspace_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum Error {
NoProgramsFound,
#[error("parsing Cargo.toml dependencies failed")]
ParsingCargoTomlDependenciesFailed,
#[error("read program code failed: '{0}'")]
ReadProgramCodeFailed(String),
}

macro_rules! construct_path {
Expand All @@ -51,6 +53,7 @@ pub struct WorkspaceBuilder {
idl: Idl,
use_tokens: Vec<ItemUse>,
packages: Vec<Package>,
codes_libs_pairs: Vec<(String, cargo_metadata::camino::Utf8PathBuf)>,
}
impl Default for WorkspaceBuilder {
fn default() -> Self {
Expand All @@ -67,6 +70,7 @@ impl WorkspaceBuilder {
idl: Idl::default(),
use_tokens: vec![syn::parse_quote! { use trdelnik_client::prelude::*; }],
packages: vec![],
codes_libs_pairs: vec![],
}
}
/// ## Build program packages, accordingly updates program client
Expand Down Expand Up @@ -146,7 +150,8 @@ impl WorkspaceBuilder {
#[throws]
async fn build_packages(&mut self) {
self.packages = Commander::collect_program_packages().await?;
self.idl = Commander::build_program_packages(&self.packages).await?;
(self.idl, self.codes_libs_pairs) =
Commander::build_program_packages(&self.packages).await?;
self.use_tokens = Commander::build_program_client().await?;
}

Expand Down Expand Up @@ -244,6 +249,37 @@ impl WorkspaceBuilder {

self.create_directory(&new_fuzz_test_dir).await?;

// TODO IDL HAS TO BE CHECKED
let fuzz_instructions = crate::fuzzer::fuzzer_generator::generate_source_code(&self.idl);
let fuzz_instructions = Commander::format_program_code(&fuzz_instructions).await?;

let fuzzer_snapshots =
crate::snapshot_generator::generate_snapshots_code(&self.codes_libs_pairs)
.map_err(Error::ReadProgramCodeFailed)?;
let fuzzer_snapshots = Commander::format_program_code(&fuzzer_snapshots).await?;

let fuzz_instructions_path = construct_path!(
self.root,
TESTS_WORKSPACE_DIRECTORY,
FUZZ_TEST_DIRECTORY,
&new_fuzz_test,
FUZZ_INSTRUCTIONS_FILE_NAME
);

let fuzz_snapshots_path = construct_path!(
self.root,
TESTS_WORKSPACE_DIRECTORY,
FUZZ_TEST_DIRECTORY,
&new_fuzz_test,
ACCOUNTS_SNAPSHOTS_FILE_NAME
);

self.create_file(&fuzz_instructions_path, &fuzz_instructions)
.await?;

self.create_file(&fuzz_snapshots_path, &fuzzer_snapshots)
.await?;

let fuzz_test_path = construct_path!(
self.root,
TESTS_WORKSPACE_DIRECTORY,
Expand All @@ -252,14 +288,25 @@ impl WorkspaceBuilder {
FUZZ_TEST
);

let fuzz_test_content = load_template("/src/templates/trdelnik-tests/fuzz_test.tmpl.rs")?;
let fuzz_test_content = load_template("/src/templates/trdelnik-tests/fuzz_target.rs")?;

let use_entry = format!("use {}::entry;\n", program_name);
let use_instructions = format!("use program_client::{}_instruction::*;\n", program_name);
let mut template = format!("{use_entry}{use_instructions}{fuzz_test_content}");
template = template.replace("###PROGRAM_NAME###", program_name);
let use_fuzz_instructions = format!(
"use fuzz_instructions::{}_fuzz_instructions::FuzzInstruction;\n",
program_name
);
let template =
format!("{use_entry}{use_instructions}{use_fuzz_instructions}{fuzz_test_content}");
let fuzz_test_content = template.replace("###PROGRAM_NAME###", program_name);

self.create_file(&fuzz_test_path, &template).await?;
// let use_entry = format!("use {}::entry;\n", program_name);
// let use_instructions = format!("use program_client::{}_instruction::*;\n", program_name);
// let mut template = format!("{use_entry}{use_instructions}{fuzz_test_content}");
// template = template.replace("###PROGRAM_NAME###", program_name);

self.create_file(&fuzz_test_path, &fuzz_test_content)
.await?;

let cargo_path = construct_path!(
self.root,
Expand Down

0 comments on commit de40781

Please sign in to comment.