Source-linked AI summary
Efficient Program Reduction for Library Fuzzing Harnesses
Haiying Li, Yunlong Lyu, Peng Chen, Hao Chen
TL;DR
Complex harnesses make library-crash triage difficult, and repeatedly building and analyzing candidates makes minimization costly. HarnessMinimizer reuses preparation, replays input values, and checks crashes without repeated symbolization; across four engines, it reports faster reduction and improved harness-bug triage accuracy.
Problem
Method
HarnessMinimizer reuses header analysis and prepared libraries and runtimes, replays recorded input values, and uses a lightweight crash oracle to avoid repeated symbolization.
Source: §1 · p. 3Results
3.46–4.51× mean total wall-clock speedups over each engine’s unoptimized pipeline; harness-bug triage accuracy rose from 28% to 44–51%, while library-bug accuracy remained at 100%.
Source: §1 · p. 3Takeaways & Limitations
The resulting reproducers expose relevant API calls and concrete arguments for debugging, and the LLM study provides evidence that minimization can assist crash triage.
Source: §6 · p. 17The magnitude of the improvements may vary with hardware and toolchains, and with the models, prompts, and inference settings used for triage.
Source: §6 · p. 17
Abstract
from arXiv · showhide
Library fuzzing harnesses translate generated inputs into API calls. When a complex harness triggers a crash, developers must determine whether the cause is a library defect or invalid harness usage. A smaller reproducer can simplify this investigation, but obtaining one requires repeatedly building and executing modified harnesses and analyzing their crashes, making manual or even automatic minimization time-consuming. Moreover, data-dependent statements prevent the reducer to remove unnecessary code and the heavy stack trace symbolization in libraries also slows down the candidate execution. To address these challenges, we present HarnessMinimizer, a system for efficient minimization of C/C++ library fuzzing harnesses. Its optimized pipeline reuses header analysis and prepared libraries and runtimes to reduce the work required to build and execute each simplified harness during the reduction process. It records and replays input values so that unnecessary input operations can be removed without changing the values supplied to surviving calls. It also uses a fast method to check whether each simplified harness preserves the original crash, avoiding repeated stack trace symbolization. We evaluate HarnessMinimizer with four reduction engines on 200 crash cases from 21 open source libraries. Compared with the unoptimized pipeline, HarnessMinimizer achieves speedups of 3.46-4.51 times in average total wall time. Average harness size decreases from 1,352 original tokens to 143-263 remaining tokens after minimization. In crash triage using a large language model, accuracy in identifying harness bugs increases from 28% with the original harnesses to 44-51% with the minimized harnesses, while accuracy for library bugs remains at 100%.
1 Introduction
Complex library fuzzing harnesses make crash triage difficult, while repeatedly building, executing, and analyzing reduced candidates makes minimization costly. HarnessMinimizer reuses stable preparation, preserves surviving input values, and checks crashes without repeated symbolization; its evaluation reports faster reduction and improved harness-bug triage accuracy.
Motivation
- Complex harnesses make it difficult to locate the calls and arguments that distinguish library defects from invalid harness usage, and manual simplification is time-consuming.Program reduction automates simplification by retaining changes that preserve the behavior under investigation.Source: §1 · p. 2
Challenges
- Existing reducers incur repeated build, execution, and symbolization costs, while deleting input operations can change arguments passed to surviving library calls.HarnessMinimizer targets these costs and dependencies while building on existing reduction engines.Source: §1 · p. 3, Figure 2 · p. 4
Approach
- HarnessMinimizer reuses header analysis and prepared libraries and runtimes, replays recorded input values, and uses a lightweight crash oracle to avoid repeated symbolization.It embeds surviving input values in the final source and validates the reproducer, exposing relevant API calls and concrete arguments.Source: §1 · p. 3
Evaluation
- 3.46–4.51× speedups in mean total wall-clock time over each engine’s unoptimized pipeline; harness-bug triage accuracy rose from 28% to 44–51%, while library-bug accuracy remained 100%.The evaluation covers four reduction engines and 200 crash cases from 21 open source libraries; mean harness size fell from 1,352 tokens to 143–263 tokens.Source: §1 · p. 3
Integration
- Adapters connect treereduce, Perses, WDD, and CDD to a shared interestingness-test interface, letting different reduction strategies use the same optimized testing pipeline.The evaluation includes all four engines.Source: §1 · pp. 3–4
2 Challenges in Harness Minimization
Harness minimization must repeatedly test modified code against a fixed library build and triggering input, while preserving both the relevant API arguments and the identity of the original crash. Three challenges make that testing costly or constrain deletion: repeated preparation, stateful input consumption, and crash recognition overhead.
2 Challenges in Harness Minimization
- A reduction query must build a candidate, supply its input values, and decide whether it preserves the reference crash; existing engines mainly provide search strategies and source transformations.The reproduced crash depends on the harness, triggering input, and library build.Source: §2 · p. 4
2.1 Repeated Build and Execution Costs
- Every candidate repeats header analysis, linking, and process startup even though the library and runtime environment remain fixed across queries.Precompiled headers can cache header analysis, but template instantiation and code generation may still cost time; reuse also requires candidate isolation.Source: §2.1 · pp. 4–5
2.2 Input Consumption Dependencies
- Removing an earlier FuzzedDataProvider operation can shift later consumption and change arguments to surviving calls, even when the crash input is unchanged.The reducer may retain an otherwise unused operation just to preserve the consumption state; loops and remaining_bytes() can also alter later values or control flow.Source: §2.2 · p. 5, Figure 3 · p. 5
2.3 Recognizing the Reference Crash Efficiently
- Crash matching must distinguish the reference fault from unrelated failures, but symbolization adds repeated work and unsymbolized executable offsets can move when candidates are relinked.Disabling symbolization also removes function and source-location context, complicating comparisons across candidates.Source: §2.3 · pp. 5–6, Figure 4 · p. 6
3 Design
HarnessMinimizer separates the changing harness from stable build and execution information, input values, and the reference crash. Its workflow prepares reusable context, evaluates candidates through existing reduction engines, then embeds surviving inputs and validates a reproducer.
3 Design
- HarnessMinimizer first prepares a reusable build and execution context and records consumed input values, then evaluates candidates and validates a reproducer with surviving inputs embedded.The reduction engine chooses transformations; HarnessMinimizer controls how each candidate is tested.Source: §3 · p. 6
3.1 Reusing the Build and Execution Environment
- A stable boundary separates the changing harness body from declarations and implementations prepared once, while a child process isolates candidate execution.Only the candidate body incurs additional build and execution overhead in subsequent queries.Source: §3.1 · p. 7
3.1.1 Header Isolation with Precompilation.
- The harness’s include prefix is compiled into a precompiled header, so candidate builds reuse header analysis while still checking and generating code for the modified body.Compatible compiler settings and include-affecting macros preserve the boundary; candidate type checking is not bypassed.Source: §3.1.1 · p. 7
3.1.2 Dedicated Execution with the Persistent Runner.
- Candidate plugins leave library references unresolved until loading, binding them to implementations already present in the runner rather than relinking the library for each query.This reuses both linked library code and the process state established before harness execution.Source: §3.1.1 · p. 7
- A persistent runner prepares library mappings, relocations, constructors, and runtime state; each query loads a candidate plugin in a disposable executor that inherits the prepared environment.Forking isolates private-memory changes and crashes from the parent reused for later queries; the static-library path links required archive members into the runner.Source: §3.1.2 · p. 8, Figure 6 · p. 8
PREPARE ONCE: ARCHIVES BECOME PART OF THE RUNNER
HarnessMinimizer prepares reusable library code and runtime support, preserves input values at surviving call sites, and checks crash identity without repeated symbolization.
PREPARE ONCE: ARCHIVES BECOME PART OF THE RUNNER
- HarnessMinimizer selects archive members referenced by the original harness and their dependencies, then exports their APIs from a persistent runner for plugin lookup.The analysis object supplies symbol roots but is not linked into the runner; unrelated archive members remain excluded.Source: PREPARE ONCE: ARCHIVES BECOME PART OF… · p. 9, Figure 7 · p. 9
3.2 Preserving Inputs with Call-Site Replay
- Call-site replay records consumed values under stable IDs so deleting an input operation does not change values supplied to surviving API calls.In the illustrated case, deleting call 101 still lets call 102 supply {30,40}.Source: §3.2 · p. 10, Figure 8 · p. 10
- For looped FDP calls, HarnessMinimizer records an ordered value sequence per site and gives each sequence an independent replay cursor.If a candidate requests more values than recorded, replay terminates execution; state queries such as remaining_bytes() also use recorded sequences.Source: §3.2 · p. 10
- The final reproducer inlines recorded values into literals or container initializers, allowing it to run without the replay runtime.Large buffers or repeated sequences with elements larger than 128 go in a supporting header, and developers can inspect concrete values in the source.Source: §3.2 · p. 11
3.3 Fast Crash Oracle Without Heavy Symbolization
- The crash oracle requires a matching crash pattern and compares unsymbolized stack evidence using a target-library offset for dynamic linking or stack depth for static linking.For static linking, different faults can share a pattern and frame count; final validation uses symbolization and reports detected collisions.Source: §3.3 · p. 11
- When a static-linking depth comparison detects a collision, users can repeat reduction with symbolized comparison or use dynamic linking if a shared-library build is available.The depth oracle is a filtering heuristic, not a guarantee that matching pattern and frame count identify the same fault.Source: §3.3 · pp. 11–12
4 Implementation
The implementation uses C++ parsing and a persistent runner to support harness transformation, input replay, and reduction-engine integration.
4 Implementation
- HarnessMinimizer is implemented primarily in Python and C++, with Python coordinating transformations, compilation, and crash checks and C++ implementing the persistent runner and replay runtime.The implementation contains approximately 9,400 lines of source code.Source: §4 · p. 12
- Tree-sitter identifies FDP call sites for instrumentation and replaces surviving calls with recorded values in the final reproducer.Repeated-call values are emitted as sequences and indices, large buffers go in a supporting header, and direct-input harnesses embed crash bytes in entry-point parameters.Source: §4 · p. 12
- Adapters integrate treereduce, Perses, WDD, and CDD through a shared interestingness test and translate its verdict to each engine’s acceptance convention.This gives all four engines the same HarnessMinimizer optimization.Source: §4 · p. 12
5 Evaluation
Across four reduction engines and 200 crash cases from 21 libraries, HarnessMinimizer reduces minimization time and harness size, while improving LLM identification of harness bugs.
5 Evaluation
- The evaluation compares four reduction engines on 200 crash cases from 21 libraries, with 100 library bugs and 100 harness bugs.Reduction runs use sequential candidate generation and continue to each engine’s fixed point.Source: §5 · p. 12, §5.1 · p. 12
5.1 Reduction Performance
- 3.46–4.51× speedups in mean total wall time accompany mean remaining harness sizes of 143–263 tokens, from 1,352 original tokens.The time comparison is against each engine’s unoptimized pipeline; the size range spans the four engines.Source: §5.1 · pp. 13–14, Table 1 · p. 13
5.2 Effectiveness of Each Component
- Compilation speedups are 9.43–9.68×, linking speedups are 2.83–2.91× dynamically and 10.79–11.56× statically, and execution speedups are 2.33–3.49×.Across the four engines, query throughput speedups are 3.01–3.63× under dynamic linking and 5.14–5.37× under static linking.Source: Table 2 · p. 14, §5.2 · p. 15
- Input replay reduces mean remaining size from 391.48 to 327.49 tokens and mean remaining FDP calls from 9.10 to 5.24 in 166 FDP cases.The call decrease is 42.38%; the size decrease is 16.35%.Source: §5.2 · p. 15, Table 3 · p. 15
- The offset and symbolized oracles return the same verdict for 97.24% of queries, and final reproducers in all 14 disagreement cases retain the original crash.The comparison used 551,812 queries across the 200 cases.Source: §5.2 · p. 15, Table 4 · p. 16
- The depth and symbolized oracles return the same verdict for 96.94% of queries; four cases had final reproducers that triggered crashes at different locations.Manual review found that final reproducers in the other 17 disagreement cases still triggered the original crash.Source: §5.2 · p. 16
5.3 Impact on Crash Triage
- Harness-bug accuracy rises from 28% with original harnesses to 44% with treereduce and 51% with Perses, while library-bug accuracy remains 100%.The configurations were evaluated on 100 cases per bug category.Source: §5.3 · p. 16, Table 5 · p. 16
6 Discussion
Harness minimization can aid crash triage by exposing relevant calls and concrete arguments, but its crash oracle and measured benefits have limits. Static-linking depth checks provide less location information and can collide, while timing and LLM results may vary across settings.
Implications
- HarnessMinimizer’s improvements across four reduction engines show that reducing repeated candidate-testing work can benefit different reduction strategies.Input replay can also remove operations retained only to advance input consumption, and the resulting reproducers expose API calls and concrete arguments for inspection.Source: §6 · p. 17
Limitation
- With static linking, the depth oracle gives less location information than a library offset or symbolized trace; manual review found collisions in 4 of 200 final reproducers (2%).A final symbolized validation checks for collisions, and reduction can be repeated with symbolized comparison if one is detected.Source: §6 · p. 17
Threats to validity
- Timing and LLM classifications can vary, and the magnitude of improvements may differ with hardware, toolchains, models, prompts, and inference settings.The study used the same cases and build environment with one worker per run, and 20 classifications with majority voting for triage.Source: §6 · p. 17
7 Related Work
Prior reduction work improves transformations, candidate selection, and reuse of test outcomes, while crash-triage systems focus on crash comparison or code relevant to diagnosis. HarnessMinimizer adapts existing engines to harnesses by reducing candidate-test costs and preserving recorded input values.
7.1 Delta Debugging and Program Reduction
- Prior reducers broaden transformations through hierarchical or grammar-aware simplification, while other approaches guide deletions or reuse tested outcomes.HDD handles hierarchical inputs; Perses and C-Reduce use language structure, while ProbDD, WDD, CDD, and RCC target candidate selection or result reuse.Source: §7.1 · pp. 17–18
- HarnessMinimizer combines existing reduction engines with a harness-specific interestingness test that checks candidates against the reference crash and preserves recorded input values.Its query-cost optimizations complement improvements to transformations, candidate selection, and caching.Source: §7.1 · p. 18
7.2 Crash Triage and Crash Preservation
- Crash-triage systems compare traces or reduce executable crash signatures, while harness-analysis tools select relevant statements or minimize harnesses before investigation.Arash selects statements relevant to crash parameters; FuzzAgent combines minimization with reproduction, source inspection, and debugging.Source: §7.2 · p. 18
- Repeated symbolization slows candidate checks, so HarnessMinimizer filters candidates using a normalized crash pattern and a library-relative offset or unsymbolized stack depth.Unsymbolized depth is used when a shared-library frame is unavailable.Source: §7.2 · p. 18
8 Conclusion
HarnessMinimizer speeds harness reduction across four engines and produces reproducers that are easier to inspect. Input replay supports more extensive simplification, and improved harness-bug identification in the LLM triage study supports using minimization during crash investigation.
8 Conclusion
- 3.46–4.51× speedups across four reduction engines show that reusing preparation and reducing diagnostic work can benefit different reduction strategies.The system aims to make automatic harness reduction faster and its reproducers easier to inspect.Source: §8 · p. 18
- Input replay enables more extensive simplification by allowing removal of operations retained only to preserve consumption order.The improved identification of harness bugs in the LLM triage study supports using minimization between crash discovery and triage.Source: §8 · p. 19
Tool Availability
HarnessMinimizer is publicly available.
Tool Availability
- HarnessMinimizer is publicly available at https://github.com/FuzzAnything/HarnessMinimizer.The repository is the stated location for the tool.Source: Tool Availability · p. 19