From de40781d0e379dbfbd663c4278875e447a7f6ac1 Mon Sep 17 00:00:00 2001 From: lukacan Date: Wed, 10 Jan 2024 22:07:32 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20integrate=20new=20fuzzer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/cli/src/command/fuzz.rs | 2 +- crates/client/Cargo.toml | 9 +++- crates/client/src/commander.rs | 53 +++++++++++++++--------- crates/client/src/lib.rs | 28 +++++++++++++ crates/client/src/workspace_builder.rs | 57 +++++++++++++++++++++++--- 5 files changed, 121 insertions(+), 28 deletions(-) diff --git a/crates/cli/src/command/fuzz.rs b/crates/cli/src/command/fuzz.rs index 2cbc303f4..961d5f7fd 100644 --- a/crates/cli/src/command/fuzz.rs +++ b/crates/cli/src/command/fuzz.rs @@ -61,7 +61,7 @@ pub async fn fuzz(root: Option, 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); diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index b8d6d2bb7..c10897e89 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -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] @@ -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 } @@ -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 } diff --git a/crates/client/src/commander.rs b/crates/client/src/commander.rs index 3af99ffdd..9cd5ea4dc 100644 --- a/crates/client/src/commander.rs +++ b/crates/client/src/commander.rs @@ -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(()) @@ -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] @@ -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"); diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs index 8127c2e9f..bb4126016 100644 --- a/crates/client/src/lib.rs +++ b/crates/client/src/lib.rs @@ -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 { @@ -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; @@ -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"; diff --git a/crates/client/src/workspace_builder.rs b/crates/client/src/workspace_builder.rs index 5ba5c864d..266d5681a 100644 --- a/crates/client/src/workspace_builder.rs +++ b/crates/client/src/workspace_builder.rs @@ -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 { @@ -51,6 +53,7 @@ pub struct WorkspaceBuilder { idl: Idl, use_tokens: Vec, packages: Vec, + codes_libs_pairs: Vec<(String, cargo_metadata::camino::Utf8PathBuf)>, } impl Default for WorkspaceBuilder { fn default() -> Self { @@ -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 @@ -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?; } @@ -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, @@ -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,