canfuzz 0.6.0

A coverage-guided fuzzing framework for Internet Computer canisters, built on `libafl` and `pocket-ic`
Documentation
# Canister Fuzzing Framework

A coverage-guided fuzzer for Internet Computer canisters, built on `libafl` and `pocket-ic`. It finds bugs by instrumenting canister Wasm, emulating the IC with `pocket-ic`, and using `libafl` to explore code paths.

## Building a new fuzzer

To build a fuzzer, one must implement the `FuzzerOrchestrator` trait. This involves two main parts: an `init` function to set up the canisters and an `execute` function that runs for each input.

```rust
// my_fuzzer/src/main.rs
use canfuzz::fuzzer::{CanisterBuilder, FuzzerBuilder, FuzzerState, WasmPath};
use canfuzz::orchestrator::FuzzerOrchestrator;
use canfuzz::util::parse_canister_result_for_trap;
use canfuzz::libafl::executors::ExitKind;
use canfuzz::libafl::inputs::BytesInput;
use candid::Principal;

// 1. Define a struct for the fuzzer state using the macro
canfuzz::define_fuzzer_state!(MyFuzzer);

// 2. Implement the core fuzzing logic
impl FuzzerOrchestrator for MyFuzzer {
    /// Sets up the IC environment and installs canisters.
    fn init(&mut self) {
        // A helper that automatically initializes PocketIc and installs canisters.
        // Canisters are expected to be instrumented here.
        self.as_mut().setup_canisters();
    }

    /// Executes one test case with a given input.
    fn execute(&self, input: BytesInput) -> ExitKind {
        let payload: Vec<u8> = input.into();
        let target_canister = self.get_coverage_canister_id();
        let pic = self.get_state_machine();

        // Call a method on the target canister
        let result = pic.update_call(
            target_canister,
            Principal::anonymous(),
            "my_canister_method",
            payload,
        );

        // Check for traps (panics). This is a common crash condition.
        parse_canister_result_for_trap(result)

        // For other bugs, add custom checks and return ExitKind::Crash if found.
        // if is_bug_detected() { return ExitKind::Crash; }
        // ExitKind::Ok
    }
}

// 4. The main function to configure and run the fuzzer
fn main() {
    // Define the canisters for the test environment.
    // The `Coverage` canister will be instrumented automatically.
    
    // For complex builds, you can use a build.rs and .with_wasm_env()
    let target = CanisterBuilder::new("my_target_canister")
        .with_wasm_path("path/to/your/canister.wasm")
        .as_coverage()
        .build();

    let state = FuzzerBuilder::new()
        .name("my_fuzzer")
        .with_canister(target)
        // .with_canister(other_canister)
        .build();

    let mut fuzzer = MyFuzzer(state);
    fuzzer.run();
}
```

## Running an example

### Prerequisites

1.  **Rust**:
    ```sh
    rustup default stable
    rustup target add wasm32-unknown-unknown wasm32-wasip1
    ```
2.  **wasi2ic** (for WASI-based canisters like `rusqlite_db`):
    ```sh
    cargo install wasi2ic
    ```
3.  **DFX**: [Installation guide](https://internetcomputer.org/docs/current/developer-docs/getting-started/install/index.html) (for Motoko canisters).
4.  **Mops**: `npm install -g mops` (for Motoko canisters).


The `examples/` directory contains sample fuzzers. To run the `stable_memory_ops` example:

1.  **Build and Run:**
    ```sh
    cargo run --release -p stable_memory_ops
    ```

2.  **Check Output:**
    The fuzzer will start and display a status screen. Results, including new inputs (`corpus`) and crashes, are saved to a timestamped directory inside `artifacts/`. The exact path is printed at startup.

## Reproduce a Crash

When a crash is found, the input is saved to the `artifacts/.../crashes/` directory. Use the `test_one_input` method to reproduce it for debugging.

1.  **Find the Crash File:** Copy the path to a crash input file from the fuzzer's output directory.

2.  **Modify `main` to Test One Input:**
    ```rust
    use std::fs;

    fn main() {
        // ... (fuzzer and canister setup from above)
        let mut fuzzer = MyFuzzer(state);

        // fuzzer.run(); // Comment out the main fuzzing loop

        // Use test_one_input to reproduce a specific crash
        let crash_input = fs::read("path/to/your/crash/file").unwrap();
        fuzzer.test_one_input(crash_input);
    }
    ```

## How It Works

The framework connects three components:

1. **`pocket-ic` (IC Emulator)** — Runs canisters locally in-process. The fuzzer installs instrumented Wasm into `pocket-ic` and makes canister calls for each test input.

2. **Wasm Instrumentation** — Before deployment, the target canister's Wasm module is transformed to provide execution feedback:

   * **Branch coverage**: An AFL-style instrumentation pass injects code at every basic block and branch. Each instrumentation point updates a shared coverage map using XOR-based edge hashing with a configurable history size.

   * **Coverage export**: A special method (`__export_coverage_for_afl`) is added to the Wasm module so the fuzzer can retrieve the coverage map after each execution.

   * **Instruction count maximization** *(optional)*: When `instrument_instruction_count: true` is set, wrapper functions are injected around each `canister_update` export. The wrappers read `ic0.performance_counter` after the original method returns and subtract the estimated AFL instrumentation overhead. A separate export (`__export_instruction_count_for_afl`) lets the fuzzer retrieve the count. Combined with `instruction_config()` returning `InstructionConfig { enabled: true, .. }` in `FuzzerOrchestrator`, this guides the fuzzer toward inputs that consume the most IC instructions — no changes to the target canister's source code required. Each new maximum is logged with a timestamp, instruction count, and input hex preview, and the input is saved to the corpus directory for replay. Setting `max_instruction_count` to a threshold will treat inputs that exceed it as crashes. See the `decode_candid_by_instructions` example.

3. **`libafl` (Fuzzing Engine)** — Drives the main loop: generating inputs, executing them via `pocket-ic`, collecting coverage (and optionally instruction count) feedback, and managing the corpus. The framework also includes a **Candid-aware mutator** that can parse `.did` files and perform structure-aware mutations on Candid-encoded inputs.

## License

This project is licensed under the Apache-2.0 License.