Understanding and Improving Coverage Tracking with AFL++

No ratings

Presented at FUZZING 2024 by

Coverage-based fuzzers use code coverage, i.e., which program parts were visited when executing a specific input, as a proxy measure to (1) guide the fuzzing process, and (2) explore the Program Under Test (PUT)’s state space. One way to record coverage progress is to enumerate basic block pairs (i.e., edges in the control-flow graph) belonging to a program and use them to index into a hash table that holds counters. The counter is incremented every time a fuzzer’s input exercises the corresponding edge. Traditionally the coverage map has been a compact bitmap that fits the L2 CPU cache to reduce runtime overhead. In such a design where space is traded for speed, two sources of imprecision can arise: (1) collisions, and (2) overflows. Collisions refer to the situation when two different basic block pairs hash to the same entry. Imprecision arises since one pair is now counted twice, but the fuzzer cannot tell one apart from the other. Overflows refer to the situation when a single counter inside the hash table overflows, indicating a situation where high-frequency control-flow exceeds the predefined, expected maximum counter size (e.g., in loops). Due to execution frequencies obeying exponential power laws, such overflows will affect a small number of hash table entries. In 2018, CollAFL examined how collisions impact precision, and presented a new hashing scheme to reduce the number of collisions. CollAFL did not address the overflow problem and its effect on precision and, furthermore, only considered a limited set of hardware architectures and benchmark programs. By extending both evaluated hardware architectures and benchmark programs as well as investigating the precision impact of overflows, this registered report aims at providing new insights. Leveraging the evaluation data, we will be able to determine precise architecture-aware settings for AFL++. In addition, we hope to be able to demonstrate an adaptive optimization strategy that optimizes the values for a specific combination of an architecture and PUT.