Technical Report & Research Article

Automating Fuzzing Harness Generation using Large Language Models and Evolutionary Optimization

Designing a dynamic and iterative framework for the semantic evolution of Fuzz Harness codes based on multi-dimensional execution environment feedback.

Necessity and Motivation of the Research

Software forms the backbone of many critical services in modern societies. Today, banking systems, energy distribution networks, communication infrastructures, and even healthcare services all depend on the correctness and security of code. This dependency means that a software vulnerability is no longer just a simple technical error, but can have serious economic, security, and social consequences. This issue is particularly crucial in libraries written in C and C++; libraries that have often been used in the infrastructure of modern software for years or even decades. In 2024 alone, over 40,000 new vulnerabilities were registered in the CVE database, and a significant portion of these vulnerabilities relate to these old yet widely-used ecosystems.

In such an environment, fuzzing tests have become one of the most effective methods for automatic vulnerability discovery. Tools like AFL and libFuzzer identify new execution paths by repeatedly executing the program on mutated inputs, keeping and expanding inputs that lead to the discovery of new behaviors. The success of this approach in projects like Google’s OSS-Fuzz and syzkaller has shown that fuzzing can reveal thousands of real bugs and vulnerabilities on a large scale.

Main Challenge: Fuzz Harness Quality

However, the success of fuzzing testing does not solely depend on the power of the mutation engine. A determining factor is the quality of the “fuzz harness”; the part of the code that converts the raw input generated by the fuzzer into meaningful calls in the target library’s API. If this harness is weak, limited, or overly simple, no matter how advanced the fuzzer is, it will not be able to access deeper parts of the code.

In other words, the fuzz harness is the window through which the fuzzer sees the library; the narrower this window, the larger the portion of code that remains in the dark. Writing these harnesses manually, especially for complex libraries like OpenSSL, is a difficult and time-consuming task. The developer must know the correct sequence of calls, initialization of data structures, hidden API conditions, and correct memory management patterns; knowledge that is not always present in official documentation. Furthermore, a handwritten harness usually only covers a static view of the API, and after a while, the code coverage growth stops. This phenomenon in fuzzing is known as a Plateau.

Fuzz Harness Concept

Research Problem and Existing Gap

Empirical evidence shows that in many complex libraries, fuzzing plateaus occur not due to the weakness of the fuzzer engine, but due to the limitations of the harness itself. If the harness only activates a specific path of the API, the fuzzer cannot go beyond the boundaries set by that harness. Therefore, the main problem of this research is how to transform the fuzz harness from a static piece of code into an entity capable of improvement and evolution.

In recent years, various tools have been proposed for the automatic generation of fuzz harnesses. For example, FuzzGen generates a harness by statically analyzing API dependencies, but does not change it after generation and does not use the fuzzer’s execution feedback to improve it. Tools like OSS-Fuzz-Gen and HarnessAgent use large language models to generate the harness and can partially fix compilation errors, but this process mostly remains at the syntactic level. Once the code compiles, there is usually no mechanism for the logical and gradual improvement of the harness.

Research Gap

The main gap in this field is that the fuzz harness is usually considered a static output, not an entity that can change based on execution results. This research focuses exactly on this point: designing a framework that can iteratively and evolutionarily improve the fuzz harness using execution feedback.

Research Gap

Proposed Approach

The core idea of this research is simple yet significant: if inputs in fuzzing mutate to discover new paths in the program, why shouldn’t the fuzz harness itself mutate? Instead of always feeding new inputs to a static harness, the harness itself can be modified to activate more parts of the API and make new execution paths accessible to the fuzzer. Accordingly, in this research, designing a fuzz harness is defined as a multi-objective evolutionary optimization problem on the source code.

Stages of the System’s Evolutionary Cycle:

Step One: Initial Generation of Candidates
First, the large language model generates an initial set of candidate harnesses based on the target library’s API signatures.
Step Two: Compilation and Dynamic Execution
Then, each harness is compiled and executed using tools like Clang, libFuzzer, and sanitizers for memory errors and undefined behavior.
Step Three: Multi-criteria Fitness Function Analysis
After execution, the quality of each harness is evaluated with a composite fitness function. This function considers three criteria simultaneously: edge coverage rate, execution stability, and diversity of discovered paths.
Step Four: Applying Changes and Semantic Mutation
At the end of each generation, the best harnesses along with performance feedback are fed back to the language model. The model then suggests structured changes; such as modifying the input parsing method, adding new API calls, introducing stateful logic, or fixing memory management.

In this framework, the large language model is not just a one-time code generator, but plays the role of a “semantic mutation operator”. This role is the main difference between the proposed approach and previous methods.

Why do Large Language Models perform successfully as mutation operators?

Random mutation at the C/C++ code level is usually ineffective, as most random changes lead to invalid or uncompilable code. However, large language models, due to being trained on massive amounts of code, are somewhat familiar with common API patterns, call sequences, and implicit memory management rules. Therefore, they can suggest changes that have a much higher probability of compilability and semantic correctness than random mutations.

Methodology Part 1 Methodology Part 2

Experimental Results

The proposed framework was evaluated on eleven diverse C and C++ libraries. In seven cases, meaning over 63% of the tests, the system was able to generate measurable code coverage and improve the fuzz harness over successive generations. Unsuccessful cases were mainly related to complex compilation dependencies or highly specific APIs; situations where the language model sometimes suffered from incorrect assumptions or hallucinations.

Avatar photo
تیم تولید محتوای مبنا Website

Comments closed.