LibAFL custom fuzzer composition
Contract
| Field | Bound contract |
|---|---|
| Trigger | User needs a custom LibAFL fuzzer, observer, feedback, mutator, scheduler, or executor composition. |
| Authority | Reversible local: writes named local Rust source artifacts, corpus entries, and objective findings under the output directory; rollback is deleting the generated crate, the output directory, or reverting VCS changes. No remote mutation. |
| Side effect | Local-write: creates or modifies Rust source files implementing the composed fuzzing engine and target integration, plus corpus entries and crash/timeout findings under the output directory. |
| Done | The composed LibAFL fuzzer compiles, runs the target, records coverage feedback, and persists objective findings to disk. |
Inputs
- Target: the function, binary, or harness to fuzz. Required.
- Components: which LibAFL modules to compose, at minimum an executor, observer, feedback, and mutator. Optional; defaults to a coverage-guided in-process fuzzer with
StdScheduledMutatorandHitcountsMapObserver. - Objective: the stopping condition or crash classification (e.g., timeout, crash, custom
ExitKind). Optional; defaults to crash detection. - Output directory: where corpus and findings persist. Optional; defaults to
./fuzz_out.
Procedure
Identify the target boundary. Determine the function signature or binary entry point to fuzz. Confirm the target accepts byte-slice input (
&[u8]) or can be wrapped in aBytesInputharness. Done when: the target boundary is identified and confirmed compatible with byte-slice input.Select the executor. Choose
InProcessExecutorfor in-process fuzzing orForkserverExecutor/CommandExecutorfor out-of-process.InProcessExecutorhas no crash isolation in-process; when crash handling is needed, selectInProcessForkExecutor, which forks per execution and catches crashes in the parent. Done when: one executor is selected and crash handling is configured.Select the observer. Create a
HitcountsMapObserverbacked by aCoverageMap(typicallyAFL-styleshared memory). Wrap inMultiMapObserverif tracking multiple maps. Done when: one observer is selected and backed by a coverage map.Select the feedback. Combine
MaxMapFeedback(coverage novelty) withTimeoutFeedbackorCrashFeedbackfor objective classification. Usefeedback_or!/feedback_and!macros to compose. Done when: feedback is composed with coverage novelty and objective classification.Select the objective. Define the stopping condition using
CrashFeedback,TimeoutFeedback, or a customExitKind-based feedback. This determines what the fuzzer reports as a finding. Done when: one objective feedback is selected.Select the mutator. Choose
StdScheduledMutatorwith a stack of mutations (havoc_mutations,token_mutations, or custom). For structured inputs, implementHasMaxSizeand useencoded_mutations. Done when: one mutator is selected with its mutation stack.Select the scheduler. Use
QueueSchedulerfor corpus cycling,PowerScheduleSchedulerfor power scheduling, orMinimizerSchedulerwrapping another scheduler for corpus minimization. Done when: one scheduler is selected.Assemble the fuzzer. Create a
StdFuzzerwith the chosen scheduler and feedback. Wire the executor, observer, and objective. Done when: theStdFuzzeris assembled with all components wired.Set up the event manager. Use
SimpleEventManagerfor single-process orLlmpEventManagerfor multi-process. Connect to aStatsMonitor(e.g.,MultiMonitorprinting to stdout). Done when: the event manager is set up and connected to a stats monitor.Initialize state and corpus. Create
StdStatewith the initial corpus directory, the feedback, and the objective. Load seed inputs from the corpus directory or provide inlineBytesInputseeds. Done when:StdStateis initialized with corpus and seeds loaded.Run the fuzzer. Call
fuzzer.fuzz_loop(...). The fuzzer will mutate inputs, execute the target, observe coverage, and persist new corpus entries and objective findings to the output directory. Done when: the fuzz loop runs and persists corpus entries and findings.Verify findings. After the run, inspect the output directory for crash/timeout artifacts. Replay each finding against the target to confirm reproducibility. Done when: every finding artifact is inspected and replayed against the target.
Failure and recovery
- Compilation failure: LibAFL API changes between versions. Pin the
libaflcrate version inCargo.toml. If compilation fails, check the LibAFL changelog for breaking API changes and adjust component wiring. - No coverage observed: the observer map may not be linked to the target. Verify the
CoverageMapis shared with the executor (e.g., viaOwnedMutRawPtror forkserver shared memory). Fix the map linkage before re-running. - Target crash before fuzzing starts: the harness panics or aborts on initialization. Wrap the target in
catch_unwindor fix the harness. Do not widen scope to debug the target itself. - Empty corpus: no seed inputs loaded. Provide at least one valid input in the corpus directory or inline. The fuzzer cannot make progress without seeds.
- Non-convergent mutation: the mutator produces only invalid inputs that the target rejects immediately. Add a
MapFeedbackor customIsInterestingfeedback to guide toward valid input regions, or switch to a structured mutator.
Output
Compiled fuzzing engine binary in target/, live corpus in the output directory, persisted crash and timeout findings (each replayable against the target), and coverage statistics printed to the event manager monitor.