From 9c9cbc00239256a2e2c60abb2914041795c51e6f Mon Sep 17 00:00:00 2001 From: Kevin Valerio Date: Fri, 30 Aug 2024 10:06:55 +0200 Subject: [PATCH] Comments --- README.md | 4 ++ .../phink/afl/mainaflfuzzer/.cur_input | 0 .../phink/afl/mainaflfuzzer/cmdline | 1 + .../phink/afl/mainaflfuzzer/fuzzer_setup | 20 ++++++ .../phink/afl/mainaflfuzzer/is_main_node | 0 .../phink/afl/mainaflfuzzer/plot_data | 1 + .../queue/id:000000,time:0,execs:0,orig:init | 1 + .../phink/corpus/init | 1 + .../phink/logs/afl.log | 42 ++++++++++++ src/cli/ziggy.rs | 3 +- src/contract/payload.rs | 18 ++--- src/contract/remote.rs | 3 +- src/cover/report.rs | 8 +-- src/fuzzer/bug.rs | 4 +- src/fuzzer/fuzz.rs | 13 ++-- src/fuzzer/parser.rs | 3 +- src/instrumenter/instrumentation.rs | 19 ++---- src/instrumenter/instrumented_path.rs | 15 ++--- src/lib.rs | 8 +-- tests/cli_fuzz_integration_test.rs | 50 ++++++++++---- tests/shared/mod.rs | 66 ++++++++++++------- 21 files changed, 178 insertions(+), 102 deletions(-) create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/.cur_input create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/cmdline create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/fuzzer_setup create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/is_main_node create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/plot_data create mode 100644 output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/queue/id:000000,time:0,execs:0,orig:init create mode 100644 output_test_assert_output_created_when_fuzzing/phink/corpus/init create mode 100644 output_test_assert_output_created_when_fuzzing/phink/logs/afl.log diff --git a/README.md b/README.md index 4c6744d..c809ff7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # πŸ™ Phink +
+ ![Build Status](https://github.com/srlabs/phink/actions/workflows/rust.yml/badge.svg) [![License](https://img.shields.io/github/license/srlabs/phink)](https://github.com/srlabs/phink/blob/main/LICENSE) [![dependency status](https://deps.rs/repo/github/srlabs/phink/status.svg)](https://deps.rs/repo/github/srlabs/phink) [![Discord](https://img.shields.io/discord/1276519988349374587.svg?label=&logo=discord&logoColor=ffffff&color=7289DA&labelColor=2C2F33)](https://discord.gg/gAahQMGE) +
+
**Phink** is a blazing-fast⚑, property-based, coverage-guided fuzzer for ink! smart contracts. It enables developers to embed inviolable properties into their smart contract testing workflows, equipping them with automatic tools to detect vulnerabilities and ensure contract reliability before deployment. diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/.cur_input b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/.cur_input new file mode 100644 index 0000000..e69de29 diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/cmdline b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/cmdline new file mode 100644 index 0000000..6d287f4 --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/cmdline @@ -0,0 +1 @@ +./target/afl/debug/phink diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/fuzzer_setup b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/fuzzer_setup new file mode 100644 index 0000000..7cdb02d --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/fuzzer_setup @@ -0,0 +1,20 @@ +# environment variables: +AFL_AUTORESUME=1 +AFL_CMPLOG_ONLY_NEW=1 +AFL_CUSTOM_INFO_PROGRAM=./target/afl/debug/phink +AFL_CUSTOM_INFO_PROGRAM_ARGV= +AFL_CUSTOM_INFO_OUT=output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer +AFL_DEBUG=1 +AFL_DISABLE_TRIM=1 +AFL_FAST_CAL=1 +AFL_FINAL_SYNC=1 +AFL_FORCE_UI=1 +AFL_FUZZER_STATS_UPDATE_INTERVAL=10 +AFL_FORKSRV_INIT_TMOUT=10000000 +AFL_IGNORE_SEED_PROBLEMS=1 +AFL_IGNORE_UNKNOWN_ENVS=1 +AFL_IMPORT_FIRST=1 +AFL_NO_WARN_INSTABILITY=1 +AFL_TESTCACHE_SIZE=100 +# command line: +'/Users/kevinvalerio/.local/share/afl.rs/rustc-1.82.0-nightly-91376f4/afl.rs-0.15.10/afl/bin/afl-fuzz' '-c0' '-Mmainaflfuzzer' '-ioutput_test_assert_output_created_when_fuzzing/phink/corpus/' '-pexplore' '-ooutput_test_assert_output_created_when_fuzzing/phink/afl' '-g4' '-G1048576' '-c-' '-P600' '-x./output/phink/selectors.dict' './target/afl/debug/phink' diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/is_main_node b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/is_main_node new file mode 100644 index 0000000..e69de29 diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/plot_data b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/plot_data new file mode 100644 index 0000000..58e8358 --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/plot_data @@ -0,0 +1 @@ +# relative_time, cycles_done, cur_item, corpus_count, pending_total, pending_favs, map_size, saved_crashes, saved_hangs, max_depth, execs_per_sec, total_execs, edges_found diff --git a/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/queue/id:000000,time:0,execs:0,orig:init b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/queue/id:000000,time:0,execs:0,orig:init new file mode 100644 index 0000000..5b290d0 --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/afl/mainaflfuzzer/queue/id:000000,time:0,execs:0,orig:init @@ -0,0 +1 @@ +00000000 diff --git a/output_test_assert_output_created_when_fuzzing/phink/corpus/init b/output_test_assert_output_created_when_fuzzing/phink/corpus/init new file mode 100644 index 0000000..5b290d0 --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/corpus/init @@ -0,0 +1 @@ +00000000 diff --git a/output_test_assert_output_created_when_fuzzing/phink/logs/afl.log b/output_test_assert_output_created_when_fuzzing/phink/logs/afl.log new file mode 100644 index 0000000..c627654 --- /dev/null +++ b/output_test_assert_output_created_when_fuzzing/phink/logs/afl.log @@ -0,0 +1,42 @@ + +If you see an error message like `shmget() failed` above, try running the following command: + + cargo-afl afl system-config + +Note: You might be prompted to enter your password as root privileges are required and hence sudo is run within this command. +ironment variable AFL_DEBUG with value 1 +[+] Enabled environment variable AFL_FAST_CAL with value 1 +[+] Enabled environment variable AFL_FINAL_SYNC with value 1 +[+] Enabled environment variable AFL_FORCE_UI with value 1 +[+] Enabled environment variable AFL_FORKSRV_INIT_TMOUT with value 10000000 +[+] Enabled environment variable AFL_FUZZER_STATS_UPDATE_INTERVAL with value 10 +[+] Enabled environment variable AFL_IGNORE_SEED_PROBLEMS with value 1 +[+] Enabled environment variable AFL_IMPORT_FIRST with value 1 +[+] Enabled environment variable AFL_NO_WARN_INSTABILITY with value 1 +[+] Enabled environment variable AFL_TESTCACHE_SIZE with value 100 +afl-fuzz++4.21c based on afl by Michal Zalewski and a large online community +[*] Disabling cmplog again because of '-c -'. +[+] AFL++ is maintained by Marc "van Hauser" Heuse, Dominik Maier, Andrea Fioraldi and Heiko "hexcoder" Eißfeldt +[+] AFL++ is open source, get it at https://github.com/AFLplusplus/AFLplusplus +[+] NOTE: AFL++ >= v3 has changed defaults and behaviours - see README.md +[+] Enabled environment variable AFL_DISABLE_TRIM with value 1 +[*] Getting to work... +[+] Using exploration-based constant power schedule (EXPLORE) +[+] Enabled testcache with 100 MB +[+] Generating fuzz data with a length of min=4 max=1048576 +[*] Checking CPU scaling governor... +[!] WARNING: Could not check CPU min frequency +[+] Looks like we're not running on a tty, so I'll be a bit less verbose. +[+] You have 8 CPU cores and 13 runnable tasks (utilization: 162%). +[!] WARNING: System under apparent load, performance may be spotty. +[*] Setting up output directories... +[*] Scanning 'output_test_assert_output_created_when_fuzzing/phink/corpus/'... +[+] Loaded a total of 1 seeds. +[*] Creating hard links for all input files... +[*] Validating target binary... +[+] Persistent mode binary detected. +[+] Deferred forkserver binary detected. +[?25h +[-] SYSTEM ERROR : shmget() failed, try running afl-system-config + Stop location : afl_shm_init(), src/afl-sharedmem.c:284 + OS message : Invalid argument diff --git a/src/cli/ziggy.rs b/src/cli/ziggy.rs index fbce202..d0d292d 100644 --- a/src/cli/ziggy.rs +++ b/src/cli/ziggy.rs @@ -119,8 +119,7 @@ impl ZiggyConfig { } pub fn parse(config_str: String) -> Self { - let config: Self = - serde_json::from_str(&config_str).expect("❌ Failed to parse config"); + let config: Self = serde_json::from_str(&config_str).expect("❌ Failed to parse config"); if config.config.verbose { println!("πŸ–¨οΈ Using PHINK_START_FUZZING_WITH_CONFIG = {}", config_str); } diff --git a/src/contract/payload.rs b/src/contract/payload.rs index 8ab00bf..ed3c69a 100644 --- a/src/contract/payload.rs +++ b/src/contract/payload.rs @@ -40,9 +40,7 @@ impl PayloadCrafter { if entry.path().extension().map_or(false, |ext| ext == "json") { if let Ok(contents) = fs::read_to_string(entry.path()) { if let Ok(v) = serde_json::from_str::(&contents) { - if let Ok(spec) = - serde_json::from_value::(v["spec"].clone()) - { + if let Ok(spec) = serde_json::from_value::(v["spec"].clone()) { let selectors = Self::parse_selectors(&spec); all_selectors.extend(selectors); } @@ -73,8 +71,7 @@ impl PayloadCrafter { /// # Arguments /// * `json_data`: The JSON specs of the smart-contract pub fn extract_invariants(json_data: &str) -> Option> { - let data: Value = - serde_json::from_str(json_data).expect("JSON was not well-formatted"); + let data: Value = serde_json::from_str(json_data).expect("JSON was not well-formatted"); Some( data["spec"]["messages"] @@ -245,8 +242,8 @@ mod test { } let hash_two: [u8; 32] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 2, ]; println!("{:?}", hex::encode(hash_two.encode())); @@ -257,10 +254,9 @@ mod test { let metadata_path = Path::new("sample/dns/target/ink/dns.json"); let transcoder = ContractMessageTranscoder::load(metadata_path).unwrap(); - let encoded_bytes = hex::decode( - "229b553f9400000000000000000027272727272727272700002727272727272727272727", - ) - .unwrap(); + let encoded_bytes = + hex::decode("229b553f9400000000000000000027272727272727272700002727272727272727272727") + .unwrap(); let hex = transcoder.decode_contract_message(&mut &encoded_bytes[..]); assert_eq!( hex.unwrap().to_string(), diff --git a/src/contract/remote.rs b/src/contract/remote.rs index 0cf9c4c..b913925 100644 --- a/src/contract/remote.rs +++ b/src/contract/remote.rs @@ -71,8 +71,7 @@ pub struct ContractBridge { } impl ContractBridge { - pub const DEFAULT_GAS_LIMIT: Weight = - Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); + pub const DEFAULT_GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); pub const DEFAULT_DEPLOYER: AccountId32 = AccountId32::new([1u8; 32]); /// Create a proper genesis storage, deploy and instantiate a given ink! diff --git a/src/cover/report.rs b/src/cover/report.rs index e6ee30e..8b36b0e 100644 --- a/src/cover/report.rs +++ b/src/cover/report.rs @@ -45,9 +45,7 @@ impl CoverageTracker { for (i, line) in lines.iter().enumerate() { let trimmed = line.trim(); - if let Some(cov_num) = - trimmed.strip_prefix("ink::env::debug_println!(\"COV={}\", ") - { + if let Some(cov_num) = trimmed.strip_prefix("ink::env::debug_println!(\"COV={}\", ") { if let Some(cov_num) = cov_num.strip_suffix(");") { if let Ok(num) = cov_num.parse::() { if self.hit_lines.contains(&num) { @@ -202,9 +200,7 @@ impl CoverageTracker { let filtered_lines: Vec<&str> = lines .into_iter() - .filter(|line| { - !(line.contains("ink::env::debug_println!") && line.contains("COV=")) - }) + .filter(|line| !(line.contains("ink::env::debug_println!") && line.contains("COV="))) .collect(); *html = filtered_lines.join("\n"); diff --git a/src/fuzzer/bug.rs b/src/fuzzer/bug.rs index 1cbc2a9..7d9e3ec 100644 --- a/src/fuzzer/bug.rs +++ b/src/fuzzer/bug.rs @@ -130,9 +130,7 @@ impl BugManager { } pub fn is_contract_trapped(&self, contract_response: &FullContractResponse) -> bool { - if let Err(DispatchError::Module(ModuleError { message, .. })) = - contract_response.result - { + if let Err(DispatchError::Module(ModuleError { message, .. })) = contract_response.result { if message == Some("ContractTrapped") { return true; } diff --git a/src/fuzzer/fuzz.rs b/src/fuzzer/fuzz.rs index ece0bc5..8667c3e 100644 --- a/src/fuzzer/fuzz.rs +++ b/src/fuzzer/fuzz.rs @@ -247,8 +247,7 @@ fn write_dict_entry(dict_file: &mut fs::File, selector: &Selector) { write!(&mut acc, "\\x{:02X}", b).unwrap(); acc }); - writeln!(dict_file, "\"{}\"", selector_string) - .expect("πŸ˜… Failed to write to dict_file"); + writeln!(dict_file, "\"{}\"", selector_string).expect("πŸ˜… Failed to write to dict_file"); } fn execute_messages( @@ -295,8 +294,7 @@ fn check_invariants( bug_manager.display_trap(decoded_msgs.messages[0].clone(), response.clone()); }); - if let Err(invariant_tested) = bug_manager.are_invariants_passing(decoded_msgs.origin) - { + if let Err(invariant_tested) = bug_manager.are_invariants_passing(decoded_msgs.origin) { bug_manager.display_invariant( all_msg_responses.to_vec(), decoded_msgs.clone(), @@ -320,10 +318,9 @@ mod tests { .expect("Failed to load ContractMessageTranscoder"), ); - let encoded_bytes = hex::decode( - "229b553f9400000000000000000027272727272727272700002727272727272727272727", - ) - .expect("Failed to decode hex string"); + let encoded_bytes = + hex::decode("229b553f9400000000000000000027272727272727272700002727272727272727272727") + .expect("Failed to decode hex string"); let hex = transcoder .lock() diff --git a/src/fuzzer/parser.rs b/src/fuzzer/parser.rs index 0650a86..9a7ddc8 100644 --- a/src/fuzzer/parser.rs +++ b/src/fuzzer/parser.rs @@ -159,8 +159,7 @@ pub fn parse_input( { let is_payable: bool = is_message_payable( &Selector::from( - <&[u8] as TryInto<[u8; 4]>>::try_into(&encoded_message[0..4]) - .unwrap(), + <&[u8] as TryInto<[u8; 4]>>::try_into(&encoded_message[0..4]).unwrap(), ), transcoder.get_mut().unwrap().metadata(), ); diff --git a/src/instrumenter/instrumentation.rs b/src/instrumenter/instrumentation.rs index ce4f0f2..ed022ed 100644 --- a/src/instrumenter/instrumentation.rs +++ b/src/instrumenter/instrumentation.rs @@ -83,9 +83,7 @@ impl Instrumenter { })? .filter_map(|entry| { let path = entry.ok()?.path(); - if path.is_file() - && path.extension().and_then(OsStr::to_str) == Some("wasm") - { + if path.is_file() && path.extension().and_then(OsStr::to_str) == Some("wasm") { Some(path) } else { None @@ -94,8 +92,7 @@ impl Instrumenter { .next() .ok_or("πŸ™… No .wasm file found in target directory")?; - let specs_path = - PathBuf::from(wasm_path.to_str().unwrap().replace(".wasm", ".json")); + let specs_path = PathBuf::from(wasm_path.to_str().unwrap().replace(".wasm", ".json")); Ok(InkFilesPath { wasm_path, @@ -221,10 +218,8 @@ impl ContractInstrumenter for Instrumenter { contract_cov_manager ); - let modified_code = - Self::parse_and_visit(&code, contract_cov_manager).map_err(|_| { - format!("πŸ™… Failed to parse and visit code in {}", path.display()) - })?; + let modified_code = Self::parse_and_visit(&code, contract_cov_manager) + .map_err(|_| format!("πŸ™… Failed to parse and visit code in {}", path.display()))?; Self::save_and_format(modified_code, PathBuf::from(path)).map_err(|e| { format!( @@ -294,8 +289,7 @@ mod instrument { // borrowing issues let mut stmts = std::mem::take(&mut block.stmts); for mut stmt in stmts.drain(..) { - let line_lit = - LitInt::new(self.line_id.to_string().as_str(), Span::call_site()); + let line_lit = LitInt::new(self.line_id.to_string().as_str(), Span::call_site()); self.line_id = self.line_id + 1; @@ -303,8 +297,7 @@ mod instrument { ink::env::debug_println!("COV={}", #line_lit) }; // Convert this expression into a statement - let pre_stmt: Stmt = - Stmt::Expr(insert_expr, Some(Token![;](Span::call_site()))); + let pre_stmt: Stmt = Stmt::Expr(insert_expr, Some(Token![;](Span::call_site()))); new_stmts.push(pre_stmt); // Use recursive visitation to handle nested blocks and other // statement types diff --git a/src/instrumenter/instrumented_path.rs b/src/instrumenter/instrumented_path.rs index 7f835bb..e356dd7 100644 --- a/src/instrumenter/instrumented_path.rs +++ b/src/instrumenter/instrumented_path.rs @@ -43,10 +43,8 @@ impl InstrumentedPath { } pub fn clean() -> Result<(), io::Error> { - let dirs_to_remove = Self::get_dirs_to_remove( - Path::new("/tmp"), - DEFAULT_PATH_PATTERN_INSTRUMENTEDPATH, - )?; + let dirs_to_remove = + Self::get_dirs_to_remove(Path::new("/tmp"), DEFAULT_PATH_PATTERN_INSTRUMENTEDPATH)?; if dirs_to_remove.is_empty() { println!("❌ No directories found matching the pattern '{}'. There's nothing to be cleaned :)", DEFAULT_PATH_PATTERN_INSTRUMENTEDPATH); @@ -67,17 +65,12 @@ impl InstrumentedPath { Ok(()) } - fn get_dirs_to_remove( - tmp_dir: &Path, - pattern: &str, - ) -> Result, io::Error> { + fn get_dirs_to_remove(tmp_dir: &Path, pattern: &str) -> Result, io::Error> { Ok(fs::read_dir(tmp_dir)? .filter_map(|entry| { let entry = entry.ok()?; let path = entry.path(); - if path.is_dir() - && path.file_name()?.to_string_lossy().starts_with(pattern) - { + if path.is_dir() && path.file_name()?.to_string_lossy().starts_with(pattern) { Some(path) } else { None diff --git a/src/lib.rs b/src/lib.rs index d9b8e57..9b92a5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,8 +89,7 @@ pub fn main() { // We execute `handle_cli()` first, then re-enter into `main()` if let Ok(config_str) = var("PHINK_START_FUZZING_WITH_CONFIG") { if var("PHINK_FROM_ZIGGY").is_ok() { - Fuzzer::execute_harness(Fuzz, ZiggyConfig::parse(config_str.clone())) - .unwrap(); + Fuzzer::execute_harness(Fuzz, ZiggyConfig::parse(config_str.clone())).unwrap(); } } else { handle_cli(); @@ -140,10 +139,7 @@ fn handle_cli() { .unwrap(); } Commands::Coverage(contract_path) => { - CoverageTracker::generate(ZiggyConfig::new( - config, - contract_path.contract_path, - )); + CoverageTracker::generate(ZiggyConfig::new(config, contract_path.contract_path)); } Commands::Clean => { InstrumentedPath::clean().unwrap(); diff --git a/tests/cli_fuzz_integration_test.rs b/tests/cli_fuzz_integration_test.rs index c283ffd..172c29f 100644 --- a/tests/cli_fuzz_integration_test.rs +++ b/tests/cli_fuzz_integration_test.rs @@ -11,6 +11,7 @@ mod tests { get_corpus_files, instrument, samples::Sample, + try_cleanup_fuzzoutput, with_modified_phink_config, }; use anyhow::{ @@ -28,46 +29,67 @@ mod tests { }; #[test] - fn test_assert_output_created_when_fuzzing() { - let fuzz_output = PathBuf::from("output_for_integration_test"); + fn test_fuzz_assert_output_created_when_fuzzing() { + let fuzz_output = PathBuf::from("output_test_assert_output_created_when_fuzzing"); let config = Configuration { instrumented_contract_path: Some(InstrumentedPath::new(PathBuf::from( - "just_a_random_path", + "test_assert_output_created_when_fuzzing_instrumented", ))), fuzz_output: Some(fuzz_output.clone()), cores: Some(1), ..Default::default() }; - let corpus_path = &fuzz_output.join("phink").join("corpus"); + let corpus_path = &fuzz_output.join("phink").join("corpus"); // That's the default Ziggy corpus path with_modified_phink_config(&config, || { instrument(Sample::Dummy); - // Defaut Ziggy corpus location let mut initial_corpus_files = 0_usize; - // While fuzzing - let test_passed = - ensure_while_fuzzing(&config, Duration::from_secs(30), || { + // While fuzzing, let's perform the tests + ensure!( + ensure_while_fuzzing(&config, Duration::from_secs(120), || { // We check that the output is properly created let fuzz_created = fs::metadata(fuzz_output.clone()).is_ok(); - ensure!(false); // todo why the fuck is that passing + println!("Fuzz output created yet : {:?}", fuzz_created); + ensure!(fuzz_created, "Fuzz output directory wasn't created"); + // // ensure!(false); // todo why the fuck is that passing if fuzz_created { initial_corpus_files = get_corpus_files(corpus_path).len(); + println!("initial_corpus_files: {:?}", initial_corpus_files); + + ensure!( + !initial_corpus_files != 0, + "After created, the corpus files wasn't empty{:?}", + initial_corpus_files + ); + + ensure!( + fs::metadata(&fuzz_output.join("phink").join("selectors.dict")).is_ok(), + "Selectors.dict doesn't exist" + ); + + ensure!( + fs::metadata(&fuzz_output.join("phink").join("allowlist.txt")).is_ok(), + "ALLOWLIST for AFL doesn't exist" + ); } - // We check that the corpus isn't empty - ensure!(!initial_corpus_files != 0); Ok(()) - }); + }) + .is_ok(), + "ensure_while_fuzzing failed to pass" + ); - ensure!(test_passed.is_ok()); // Check that new files have been added to the corpus during fuzzing ensure!( - get_corpus_files(&fuzz_output).len() > initial_corpus_files, + get_corpus_files(&corpus_path).len() > initial_corpus_files, "There was no new corpus while fuzzing" ); + + try_cleanup_fuzzoutput(&config); + Ok(()) }) .unwrap(); diff --git a/tests/shared/mod.rs b/tests/shared/mod.rs index 587020e..5a67e3c 100644 --- a/tests/shared/mod.rs +++ b/tests/shared/mod.rs @@ -43,8 +43,8 @@ pub const DEFAULT_TEST_PHINK_TOML: &str = "phink_temp_test.toml"; /// # Arguments /// /// * `config`: A `Configuration` struct, the same one used for CLI -/// * `executed_test`: The function being executed that effectively performs the tests, -/// i.e functions containing `ensure!` +/// * `executed_test`: The function being executed that effectively performs the tests, i.e +/// functions containing `ensure!` /// # Examples /// /// ``` @@ -53,20 +53,11 @@ pub const DEFAULT_TEST_PHINK_TOML: &str = "phink_temp_test.toml"; /// Ok(()) /// }); /// ``` -pub fn with_modified_phink_config( - config: &Configuration, - executed_test: F, -) -> Result<()> +pub fn with_modified_phink_config(config: &Configuration, executed_test: F) -> Result<()> where F: FnOnce() -> Result<()>, { - let _ = fs::remove_dir_all( - &config - .instrumented_contract_path - .clone() - .unwrap_or_default() - .path, - ); + try_cleanup_instrumented(config); let _ = fs::remove_dir_all(&config.fuzz_output.clone().unwrap_or_default()); config.save_as_toml(DEFAULT_TEST_PHINK_TOML); @@ -77,13 +68,7 @@ where // We remove the temp config file let _ = fs::remove_file(DEFAULT_TEST_PHINK_TOML); // We clean the instrumented path - let _ = fs::remove_dir_all( - &config - .instrumented_contract_path - .clone() - .unwrap_or_default() - .path, - ); + try_cleanup_instrumented(config); let _ = fs::remove_dir_all(config.fuzz_output.clone().unwrap_or_default()); test_result @@ -97,10 +82,10 @@ where /// # Arguments /// /// * `config`: A `Configuration` struct, the same one used for CLI -/// * `timeout`: A timeout where the test would be considered as failed if the conditions -/// inside `executed_test` couldn't be met (i.e, it couldn't `Ok(())`) -/// * `executed_test`: The function being executed that effectively performs the tests, -/// i.e functions containing `ensure` +/// * `timeout`: A timeout where the test would be considered as failed if the conditions inside +/// `executed_test` couldn't be met (i.e, it couldn't `Ok(())`) +/// * `executed_test`: The function being executed that effectively performs the tests, i.e +/// functions containing `ensure` /// /// returns: Result<(), Error> /// @@ -121,6 +106,8 @@ pub fn ensure_while_fuzzing( where F: FnMut() -> Result<()>, { + try_cleanup_instrumented(config); + // We start the fuzzer let mut child = fuzz( config @@ -136,20 +123,46 @@ where loop { if let Ok(_) = executed_test() { child.kill().context("Failed to kill Ziggy")?; + try_cleanup_instrumented(config); return Ok(()); } if start_time.elapsed() > timeout { child.kill().context("Failed to kill Ziggy")?; + try_cleanup_instrumented(config); // If we haven't return `Ok(())` early on, we `Err()` because we timeout. return Err(anyhow!( "Couldn't check the assert within the given timeout" )); } + // We perform the tests every `1` second thread::sleep(Duration::from_secs(1)); } } + +/// Try to clean up the path where the instrumented contract is. If it fails, it doesn't matter +pub fn try_cleanup_instrumented(config: &Configuration) { + let _ = fs::remove_dir_all(config.clone().instrumented_contract_path.unwrap().path); +} + +/// Try to clean up the path where the output of the fuzzing campaign is. If it fails, it doesn't +/// matter +pub fn try_cleanup_fuzzoutput(config: &Configuration) { + let output = config.clone().fuzz_output.unwrap_or_default(); + match fs::remove_dir_all(&output) { + Ok(_) => { + println!("Removed {}", output.display()) + } + Err(_) => { + println!("**DIDN'T** removed {}", output.display()) + } + }; +} + +/// Simple `phink` bin pop from cargo to instrument `contract_path` +/// ** Important ** +/// This should only be used in test ! pub fn instrument(contract_path: Sample) { let mut cmd = Command::cargo_bin("phink").unwrap(); let _binding = cmd @@ -160,6 +173,9 @@ pub fn instrument(contract_path: Sample) { .success(); } +/// Simple `phink` bin pop from cargo to fuzz `path_instrumented_contract` +/// ** Important ** +/// This should only be used in test ! pub fn fuzz(path_instrumented_contract: InstrumentedPath) -> Child { let mut child = NativeCommand::new("cargo") .arg("run") @@ -175,6 +191,7 @@ pub fn fuzz(path_instrumented_contract: InstrumentedPath) -> Child { child } +/// Return `true` if `target` is found in any `*.rs` file of `dir`, otherwise `false` pub fn find_string_in_rs_files(dir: &Path, target: &str) -> bool { fn file_contains_string(file_path: &Path, target: &str) -> bool { let mut file = fs::File::open(file_path).expect("Unable to open file"); @@ -202,6 +219,7 @@ pub fn find_string_in_rs_files(dir: &Path, target: &str) -> bool { false } +/// A function to get all entries from the corpus directory pub fn get_corpus_files(corpus_path: &PathBuf) -> HashSet { println!("Got corpus files in: {:?}", corpus_path); fs::read_dir(corpus_path)