Skip to content

Commit

Permalink
Comments
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-valerio committed Aug 30, 2024
1 parent 0f434bd commit 9c9cbc0
Show file tree
Hide file tree
Showing 21 changed files with 178 additions and 102 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# 🐙 Phink

<div align="center">

![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)
</div>

<br>
**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.
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./target/afl/debug/phink
Original file line number Diff line number Diff line change
@@ -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'
Empty file.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000000
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00000000
42 changes: 42 additions & 0 deletions output_test_assert_output_created_when_fuzzing/phink/logs/afl.log
Original file line number Diff line number Diff line change
@@ -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
3 changes: 1 addition & 2 deletions src/cli/ziggy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
18 changes: 7 additions & 11 deletions src/contract/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Value>(&contents) {
if let Ok(spec) =
serde_json::from_value::<Spec>(v["spec"].clone())
{
if let Ok(spec) = serde_json::from_value::<Spec>(v["spec"].clone()) {
let selectors = Self::parse_selectors(&spec);
all_selectors.extend(selectors);
}
Expand Down Expand Up @@ -73,8 +71,7 @@ impl PayloadCrafter {
/// # Arguments
/// * `json_data`: The JSON specs of the smart-contract
pub fn extract_invariants(json_data: &str) -> Option<Vec<Selector>> {
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"]
Expand Down Expand Up @@ -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()));
Expand All @@ -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(),
Expand Down
3 changes: 1 addition & 2 deletions src/contract/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
8 changes: 2 additions & 6 deletions src/cover/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<usize>() {
if self.hit_lines.contains(&num) {
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 1 addition & 3 deletions src/fuzzer/bug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
13 changes: 5 additions & 8 deletions src/fuzzer/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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(),
Expand All @@ -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()
Expand Down
3 changes: 1 addition & 2 deletions src/fuzzer/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
);
Expand Down
19 changes: 6 additions & 13 deletions src/instrumenter/instrumentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -294,17 +289,15 @@ 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;

let insert_expr: Expr = parse_quote! {
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
Expand Down
15 changes: 4 additions & 11 deletions src/instrumenter/instrumented_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -67,17 +65,12 @@ impl InstrumentedPath {
Ok(())
}

fn get_dirs_to_remove(
tmp_dir: &Path,
pattern: &str,
) -> Result<Vec<PathBuf>, io::Error> {
fn get_dirs_to_remove(tmp_dir: &Path, pattern: &str) -> Result<Vec<PathBuf>, 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
Expand Down
8 changes: 2 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 9c9cbc0

Please sign in to comment.