The fuzzer bridge is the layer between Agentic-AFL and the running AFL++ process. It has two jobs: detecting when AFL++ has stalled on a coverage plateau, and writing solved payloads back into AFL++‘s input corpus. All communication is strictly filesystem-based — no shared memory, no pipes, no sockets. This design guarantees that the agent’s latency (LLM API calls, Ghidra analysis, Z3 solving) never affects AFL++‘s execution throughput.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/AdithyaaSivamal/Agentic-AFL/llms.txt
Use this file to discover all available pages before exploring further.
StallDetector
StallDetector polls AFL++‘s output directory for evidence that coverage has stopped growing. It is called by AgentLoop.run() on every iteration of the main loop.
Parsing fuzzer_stats
On each detect() call, the detector reads {afl_output_dir}/default/fuzzer_stats (or the first subdirectory containing a fuzzer_stats file in multi-instance setups). It parses the edges_found and cycles_done fields.
Two detection modes:
Cycle-based (default)
Triggers when
edges_found has not increased for min_stall_cycles consecutive polls. min_stall_cycles must be ≥ 50 to avoid spurious detections. This is the standard mode for batch fuzzing campaigns.Time-based
When
min_stall_time_seconds > 0, triggers when wall-clock time since the last new edge exceeds the threshold. Useful for end-to-end fuzzing campaigns where the cycle counter is less meaningful. When this mode is active, cycle-based detection is bypassed entirely.StallSeverity based on how long the plateau has persisted:
| Severity | Condition |
|---|---|
CRITICAL | cycles_stalled ≥ min_stall_cycles × 4 — coverage completely blocked |
HIGH | cycles_stalled ≥ min_stall_cycles × 2 |
MEDIUM | cycles_stalled ≥ min_stall_cycles |
LOW | cycles_stalled < min_stall_cycles (edge case) |
stall_address_override is set (recommended when the stall address is known from static analysis), StallDetector returns that address directly. In autonomous mode, it uses a GDB-based frontier discovery strategy: the detector reads function symbols from the binary with nm, generates a GDB Python script that tracks the maximum call stack depth across all breakpoints, runs the binary under GDB with a candidate seed, and reports the function at the deepest call depth as the math wall. Results are cached by seed content hash to avoid redundant GDB runs.
Deduplication
The same stall address is reported at most once. Once a stall is detected, its address is added to _known_stalls. When a payload is successfully injected for that address, AgentLoop calls resolve_stall(), which additionally adds the address to _resolved_stalls — permanently suppressing re-detection even if coverage later plateaus at the same point.
Multi-seed probe
StallDetector selects up to 5 AFL++ queue items for GDB probing, prioritizing original corpus seeds (orig: in filename) and sorting by file size descending. Larger seeds are more likely to reach deeper code paths. The detector tries each seed in order and stops on the first one that successfully hits the frontier function’s breakpoint.
PayloadInjector
PayloadInjector writes SolvedPayload objects to AFL++‘s sync directory. AFL++ natively monitors external sync directories and ingests new files on its next execution cycle — no AFL++ restart or signaling is required.
Atomic write pattern
Every payload write follows a temp-file-then-rename protocol:
- A
NamedTemporaryFileis created in the same directory as the final destination (same filesystem, same mount point) - The payload bytes are written and flushed
os.rename(tmp_path, final_path)is called — this is atomic on POSIX systems because source and destination are on the same filesystem
spec_id_prefix is the first 8 characters of the VulnerabilitySpec’s SHA-256-derived spec_id. This makes payloads traceable back to the stall site that produced them.
DiversityGenerator
DiversityGenerator is called automatically by AgentLoop after each successful payload injection. Its purpose is to maximize post-bypass coverage exploration by flooding AFL++‘s sync directory with structurally diverse valid frames for all supported ICS protocol frame types.
Why diversity injection matters
When the Z3 solver produces a valid payload for one frame type (e.g., a Process Data read-coils frame with correct CRC-32), AFL++ gets one new starting point. But the post-CRC state machine has many handlers — one per frame type. Without diversity injection, AFL++ would need to mutate from a single valid frame and rediscover each handler independently. Diversity injection gives AFL++ a valid entry point for every handler simultaneously, producing a coverage spike proportional to the number of frame types.
Frame construction
DiversityGenerator builds ICS CRC-32 frames using the _make_ics_frame() function:
PayloadInjector. Filenames follow the format:
generate_ics_crc32_variants() returns the count of successfully injected payloads, which AgentLoop adds to its payloads_injected metric.