DynamicFuzz addresses a failure that is easy to overlook in directed greybox fuzzing: the fuzzer can follow its guidance correctly and still spend hours pursuing a route the program cannot execute. The problem is often the call graph. Missing indirect calls hide useful paths; incorrectly inferred calls create shortcuts that exist only in the analysis. We designed DynamicFuzz to revise that guidance as the program runs, using execution evidence to change the influence of uncertain edges.
The result is a directed fuzzer built around indirect-call confidence, function islands, and adaptive seed prioritization. Our DynamicFuzz paper in Computers & Security, published online on October 1, 2025, evaluates both reaching a target location and exposing a target crash. Those are separate outcomes. The distinction explains both the method and how its results should be read.
Why a shorter call-graph path can be the wrong path
Directed greybox fuzzing starts with a destination: a patched function, a suspected fault location, or code associated with a known vulnerability. It then allocates testing effort toward inputs that appear to approach that destination. Coverage-guided fuzzing asks which inputs open up more program behavior; directed fuzzing adds a narrower question about reaching particular code. The difficulty is that proximity is usually calculated on a model of the program rather than on a complete account of its possible executions.
Indirect calls make that model uncertain. A direct call identifies its callee in the code. A call through a function pointer depends on the pointer value at runtime. Static analysis can infer possible destinations, but its answer may contain both omissions and false candidates. A missed destination can disconnect the target from the entry point. A spurious destination can make an unproductive input look closer to the target than an input on the real triggering path.
In the programs examined in Table 1 of the original paper, indirect calls account for a relatively small fraction of all calls. Their structural role matters more than their count. A callback at a format dispatcher can connect an entry point to an entire parser. Losing that one edge can remove a substantial part of the apparent attack surface; accepting the wrong edge can distort distances throughout it.
The practical consequence is a ranking error. A fuzzer has a finite budget for executing and mutating inputs. If its guidance overvalues a false route, it repeatedly invests in the wrong seeds. Increasing execution speed does not necessarily repair the allocation decision. DynamicFuzz therefore treats call-graph reliability as part of the search process rather than as a prerequisite that static analysis must solve once and perfectly.
The FreeImage example that motivated DynamicFuzz
The paper uses CVE-2023-47992 to show how an apparently attractive route can mislead directed testing. The relevant FreeImage path includes dispatch through a loading callback, image-header processing in LoadPixelData, and an indirect read through _MemoryReadProc. The paper describes a large unsigned size being converted in the vulnerable read path. For this discussion, the important feature is the sequence of calls and the runtime conditions required to take them, rather than a payload for triggering the fault.
There is also a seemingly shorter route through ReadMemory et FreeImage_ReadMemory. In the configuration analyzed in the paper, config_stream is false, so the stream argument becomes NULL. The downstream check prevents that route from invoking the read callback. A graph that includes the candidate edge without accounting for this behavior makes the shorter route look useful even though it does not reach the target under those conditions. Figure 2 and Section 3 show the code and graph together.

This is more specific than saying static analysis has false positives. The false relationship affects which input is selected next. A seed following the shorter modeled route can outrank one progressing through the actual loader. Conversely, if the useful callback is missing entirely, a distance-based method may have no target-reaching route to score.
DynamicFuzz responds to both cases. It checks candidate relationships against execution, reduces the influence of relationships that remain unsupported, and records newly observed edges that were missing from the initial graph. When no route to the target is available, it can continue exploring deeper indirect calls instead of treating the incomplete graph as evidence that the target is unreachable.
From static analysis to an execution-driven graph
DynamicFuzz has two main components. Static analysis constructs the initial call graph, identifies indirect-call information, and instruments the program. The fuzzing component executes seeds, reconstructs function-level paths, updates graph information, and recalculates seed priorities. Figure 4 of the paper captures the feedback loop: execution changes the graph, and the revised graph changes subsequent execution choices.

The implementation uses LLVM passes for instrumentation, SEA for initial indirect-call analysis, Boost for graph maintenance and weight calculation, and AFL-2.52b as the basis for queueing and scheduling changes. It collects AFL-style coverage information and records function-entry and return events for reconstructing seed paths. These implementation choices make the research a program-instrumentation approach; it is not an HTTP-only scanner or an LLM that chooses shell commands.
A useful way to read the architecture is to separate discovery from confirmation. Static analysis proposes possible relationships. An observed seed path establishes that a particular relationship occurred during an execution. That observation can justify a graph update, a new island affiliation, and a change in which targets deserve attention. None of those observations establishes that a vulnerability has already been triggered. Reachability evidence and fault evidence remain separate.
That separation also explains why the graph is not simply thrown away when it contains mistakes. Static information provides a starting structure and candidate connections. Runtime evidence improves its usefulness incrementally. Requiring every relationship to be demonstrated before any directed testing could begin would sacrifice much of the value of static guidance; trusting every relationship indefinitely produces the opposite problem.
Confidence belongs to a call site and to its candidate edges
The confidence model has two phases. Before any candidate edge from an indirect-call site has been observed, uncertainty applies to the site as a whole. DynamicFuzz decays the confidence of its outgoing candidate edges according to execution of the function containing that site. This reduces the influence of speculative call sites that remain unverified while their surrounding function is repeatedly exercised.
Once an outgoing edge has been observed, the site itself has execution evidence. The remaining question concerns its other possible callees. DynamicFuzz then updates those unverified candidates using the call count of the indirect-call site. An observed edge receives confidence 1. An unobserved candidate receives a decaying score controlled by the parameter alpha. This two-stage distinction is specified in Section 4.4.1 and Equation 6.
| Execution evidence | What DynamicFuzz updates | Effect on guidance |
|---|---|---|
| No candidate edge from a site has been verified | Candidate confidence follows the execution count of the containing function | A speculative site gradually loses influence |
| At least one edge from the site has been verified | Remaining candidates decay with the site’s call count | Unsupported destinations become less attractive |
| A specific edge occurs in a seed path | That edge receives confidence 1 | The observed relationship supports stable guidance |
Confidence here is a search heuristic. A candidate that has not appeared in the current executions is not proven impossible in every configuration or for every input. Treating a decayed score as a formal non-reachability result would overstate what the method knows. Its purpose is to spend the next portion of the fuzzing budget more productively.
DynamicFuzz translates lower confidence into a higher edge weight. For an indirect edge, Equation 10 uses the reciprocal of its confidence. As confidence decreases, the edge becomes more expensive in the distance calculation. This lets unsupported shortcuts lose their attraction without requiring a binary decision that they are either universally valid or permanently impossible.
Function islands give the search a useful intermediate scale
A function island groups a root reached through an indirect call with functions reachable from that root through direct calls. The entry region forms the initial island. This gives DynamicFuzz a level of organization between individual functions and the complete call graph: it can reason about regions connected by uncertain dispatch relationships.
The definition is not a partition of the program. Islands may overlap. A helper can be reachable through direct calls from more than one root, and a function can acquire multiple island affiliations as execution reveals more relationships. Section 4.2 and Figure 5 make this explicit. Describing islands as independent, disjoint modules would lose an important part of the design.
Island depth and function depth represent levels of indirect calling rather than ordinary stack depth. DynamicFuzz combines multiple affiliations using weighted averages. For example, the paper gives a function belonging to islands at depths 1 and 2 a resulting depth of approximately 1.67. The model preserves information from multiple paths instead of selecting only the shallowest or deepest interpretation.
These depth calculations serve different decisions. Islands near the target’s depth can receive more attention, while deeper indirect-call functions provide useful exploration targets when the graph lacks a complete route. The method does not assume that every deeper function is vulnerable. Depth helps decide where to explore under incomplete information; the crash detector must still establish whether an input exposes a fault.
The maintained graph is kept acyclic. The implementation detects and breaks cycles during construction and checks newly discovered edges before accepting them into the graph. This permits the recursive depth calculation to terminate, but it also means the maintained structure is a guidance abstraction rather than a lossless representation of recursion and every runtime call relationship. The paper discusses this tradeoff in Section 4.2.
Four strategies turn the graph into testing decisions
The first strategy makes functions containing indirect calls intermediate targets, called Relative Target Functions. A seed that reaches an uncertain dispatcher can be valuable even before it reaches the final target. Testing that function may confirm a useful outgoing edge or uncover a relationship missing from static analysis. Giving such functions explicit value helps DynamicFuzz improve its own guidance while searching for the fault.
The second strategy prioritizes function islands using their depth and indirect-call content. The final target’s island receives particular emphasis. Elsewhere, a depth weighting favors regions near the target’s depth, while the number of indirect calls helps express how much unresolved structure an island contains. This is a more selective policy than distributing effort uniformly across all callbacks.
The third strategy prefers paths supported by high-confidence relationships. A short route composed of questionable edges should not automatically beat a slightly longer route whose calls have been observed. The confidence-derived edge cost, target weights, and function distances together influence the value assigned to nodes and seeds. Section 4.4 describes how these signals are combined.
The fourth strategy addresses the missing-path case. If static analysis has omitted an essential edge, the graph may contain no route to the final target. DynamicFuzz can prioritize deeper indirect calls and expand the observed structure from there. Its purpose is to keep the search moving when conventional target-distance information is unavailable. It complements confidence-based filtering rather than replacing it.
| Stratégie | Immediate question | Contribution |
|---|---|---|
| Target Function Selection | Which functions could resolve uncertain dispatch? | Makes indirect-call functions intermediate targets |
| Function Island Prioritization | Which regions deserve the next testing budget? | Uses island structure and depth to focus exploration |
| High-Confidence Path Prioritization | Which apparent routes have execution support? | Reduces the appeal of unsupported graph shortcuts |
| Deep Indirect Call Prioritization | Where can testing continue if no target route is known? | Explores deeper dispatch relationships to improve the graph |
The following pseudocode summarizes the control flow for explanation. It is derived from the architecture and weighting discussion, not copied implementation code or an installation recipe; it omits the paper’s detailed weight formulas and queue policies.
initialize instrumented program, call graph, and seed queues
while testing budget remains:
seed = select_seed_using_priority_and_execution_count()
observation = execute_and_collect_feedback(seed)
if observation adds or verifies an indirect-call edge:
update_graph_and_function_islands(observation)
update_depths_and_target_weights()
else:
decay_unverified_edge_confidence(observation)
update_affected_node_weights()
update_seed_weight_and_priority(seed)
schedule_mutations_and_record_faults()
Why promising seeds must eventually lose priority
Giving a useful seed more attention creates a second problem: a queue can keep returning to it after its marginal value has fallen. DynamicFuzz combines seed weight with execution count. For seeds executed at least once, Equation 15 divides the seed weight by the square root of that count. The policy gives promising inputs an opportunity to develop without allowing their initial advantage to dominate indefinitely.
New coverage receives explicit priority. The implementation maintains high- and low-priority queues, and the paper states that a newly generated seed without new coverage is discarded. A priority score is therefore not a replacement for the coverage feedback that determines which new inputs enter the retained corpus. These mechanisms perform related but distinct jobs. Section 4.5 specifies the queue policy.
Power scheduling determines how much mutation effort a selected seed receives. The study retains an AFLGo-style simulated-annealing approach so that the evaluation focuses on the proposed guidance rather than a separate scheduling invention. Selecting an input earlier and allocating more mutation energy to it should not be conflated when interpreting the implementation.
The graph analysis itself has a cost. DynamicFuzz limits updates to affected subgraphs, precomputes reachability information, prepares potential islands in advance, and uses a separate analysis process when new coverage warrants more detailed path collection. These are central engineering decisions: guidance that improves seed selection can still be counterproductive if maintaining it consumes the execution budget it is supposed to allocate.
Reading the DynamicFuzz results without losing the baseline
The evaluation uses FreeImage, the AFLGo Test Suite, and UniBench, with AFL, AFLGo, and FairFuzz as baselines. Target-reaching experiments use Time-to-Target, or TTT: the time until the first input reaches a selected location. Crash experiments use Time-to-Exposure, or TTE: the time until the target vulnerability is exposed. Reaching a function does not mean the input satisfies the additional conditions needed to trigger its bug.
The experimental configuration in Section 5.1 uses Ubuntu 22.04, a 24-hour limit, 10 instances for each TTT test, and 20 for each TTE test. The confidence-decay parameter is 1.0001. The paper reports both the Mann–Whitney U test and the Vargha–Delaney effect-size statistic. These are repeated fuzzing experiments, not measurements of a production pentesting service.
Section 5.2 reports average target-reaching factors of 5.64 times over AFL, 3.01 times over AFLGo, and 2.89 times over FairFuzz. Section 5.3 reports average crash-exposure factors of 69.8, 48.37, and 161.20 times in that same baseline order. The abstract lists the first two baseline names in the opposite order. The table below follows the detailed results sections and their per-target tables, keeping each factor attached to its stated baseline.
| Baseline | Reported average TTT factor in Section 5.2 | Reported average TTE factor in Section 5.3 |
|---|---|---|
| AFL | 5.64× | 69.8× |
| AFLGo | 3.01× | 48.37× |
| FairFuzz | 2.89× | 161.20× |

Those averages do not describe every target. For CVE-2023-47993, Table 3 reports a mean TTE of 15 seconds for DynamicFuzz, compared with 34 minutes 23 seconds for AFL and 12 minutes 39 seconds for AFLGo. That is a large difference. For CVE-2018-8962, however, FairFuzz records 1 hour 57 minutes and 20 hits, while DynamicFuzz records 1 hour 58 minutes and 19 hits. Both belong in an accurate reading of the results. Table 3 supports a strong aggregate improvement, not a claim that DynamicFuzz wins every run or every case.
The harder cases also give a more restrained picture than the largest averages suggest. Section 5.3 describes gains of roughly 1.2 to 2.9 times on challenging cases with mean exposure time above an hour. Very large factors on other cases strongly influence the overall summary. For an engineering decision, the relevant question is whether the target program exhibits the dispatch uncertainty that DynamicFuzz addresses, not whether the largest published factor can be repeated on an unrelated workload.
The paper also contains a count discrepancy: the narrative in Section 5.2 refers to eight targets and 80 experiments, while Table 2 lists nine target locations and Figure 8 describes 90 experiments. The underlying per-target entries are more useful than repeating an inconsistent total. These details do not justify discarding the study, but they do matter when someone builds a new comparison from its results.
Ablation shows where the gains come from
The ablation experiments distinguish the contribution of queue ordering from that of the guidance model. Table 4 reports that removing the seed sorting algorithm increases mean TTT by 26.89% on average. Removing the guiding strategies increases it by 289.83%. These are increases in the ablated configurations relative to the default; they should not be rewritten as equivalent percentages of time saved by the default configuration.
The individual-strategy results in Table 5 also explain when a component matters. Disabling Target Function Selection produces the largest reported average TTT increase among the individual strategies. Disabling Deep Indirect Call Prioritization has a much larger effect when prebuilt indirect edges have been removed than when SEA has already supplied usable routes. That is consistent with its intended role as a way forward when target reachability is missing from the model.
Parameter sensitivity reinforces the same point. Alpha controls how quickly unsupported relationships lose influence. Decay that is too slow preserves bad guidance; decay that is too fast can discount difficult but real paths before the current inputs exercise them. The sweep in Figure 12 uses jas_icc.c:1084 as its target and places 1.0001 in a favorable region. It does not establish a universal optimum across all programs and harnesses.
What the paper establishes for vulnerability research
DynamicFuzz is particularly relevant when a researcher has a concrete target and a program whose dispatch structure makes static distances unreliable. Patch-directed testing, reproducing a suspected crash, and investigating code reachable through callbacks are natural uses of the method. Before evaluating it on a new target, the researcher still needs a suitable harness, an instrumentable build, representative seed inputs, and a fault oracle that can recognize the outcome of interest.
The initial corpus matters. The FreeImage experiments use small images in different formats to reduce randomness from satisfying format magic values, and the paper states that comparative tools receive the same corpus. A campaign starting from empty files asks a different question about parsing barriers and input generation. Likewise, changing build flags, enabled plugins, or harness behavior can alter which indirect calls are executable. Comparisons need to preserve those conditions before assigning a timing difference to guidance alone.
The published evidence has limits. The baselines are AFL, AFLGo, and FairFuzz; several other directed fuzzers are discussed but not included in the comparison. The confidence model treats different indirect-call types uniformly, including callbacks and virtual dispatch. The conclusion reports eight previously undisclosed vulnerabilities assigned CVE identifiers, but that aggregate statement does not identify every discovery. The reproduction cases in Table 3 should not all be treated as newly discovered vulnerabilities. The paper’s threats-to-validity and conclusion sections provide the proper boundaries.
For Penligent, the broader research connection is how execution evidence should change the next testing decision. An inferred path is a hypothesis until observations support it. DynamicFuzz makes that distinction part of resource allocation inside a fuzzer. Applying a comparable discipline to an AI pentesting workflow requires its own implementation and evaluation; the paper’s fuzzing results do not establish a speedup for an LLM agent or a black-box web application test.
DynamicFuzz was published in Computers & Security, volume 159, article 104691, by Hao Jiang, Kang Wang, Yujie Yang, Shan Zhong, Shuai Zhang, Chengjie Liu, Xiarun Chen, and Weiping Wen, with Peking University listed as the affiliation. The full methods, equations, figures, and experimental tables are available in the published research paper. Its engineering argument is precise: when the call graph is uncertain, improving the evidence behind the guidance can be more valuable than repeatedly following the shortest path it happens to contain.

