How Phink works

Phink is built on top of AFL++, leveraging its capabilities to provide effective fuzz testing for ink! smart contracts. Here’s an overview of how the fuzzer operates.

AFL++ integration

Phink utilizes AFL++ through two key components:

  • ziggy: A multifuzzing crate that enables integration with multiple fuzzers.
  • afl.rs: A crate that spawns AFL++ fuzzers, facilitating seamless mutation and coverage tracking.

AFL++ mechanics

AFL++ mutates the input bytes and evaluates whether these mutations increase code coverage. If a mutation results in new execution paths, the modified seed is retained in the corpus. This iterative process enhances the likelihood of discovering hidden vulnerabilities.

Monitoring execution

Users can monitor the execution logs using familiar AFL++ tools. For instance, by using tail, you can view real-time fuzzer logs and activity:

tail -f output/phink/logs/afl.log
tail -f output/phink/logs/afl_1.log #if multi-threaded

Additionally, tools like afl_showmap allow developers to debug and visualize the coverage maps.

Coverage-guided strategy

Currently, Phink employs a partially coverage-guided approach. While full coverage feedback from low-level instrumentation is not available yet, plans are underway to integrate this capability via WASMI or PolkaVM in future releases.

Execution and validation

For each generated seed, Phink executes the associated input on a mock-emulated ‘node’. This setup ensures that invariants are verified: known selectors are checked to ensure that invariants hold across different message calls.

Contract instrumentation

Phink instruments contracts using the syn crate, allowing for precise modification and analysis of the smart contract code. For each high-level Rust instructions, a feedback is returned via the debug_message map to the fuzzing engine, mapping each instruction to a unique u64 identifier. This map is then “expanded”, instrumented by AFL++ compiler, and ultimately updated the AFL++ shared map everytime a new edge is hit.