LAF-Intel Splitting
Relevant source files
The following files were used as context for generating this wiki page:LAF-Intel is a set of LLVM compiler passes within AFL++ that helps the fuzzer overcome complex conditional blocks. It achieves this by transforming code at compile time, breaking down large or complex comparison operations (strcmp, memcmp, multi-byte integers, floating-point numbers) into a series of simpler, byte-sized comparisons. This makes it significantly easier for the fuzzer to solve these conditions one byte at a time, thereby navigating previously impassable roadblocks and improving code coverage.
This feature is implemented as several distinct LLVM passes that can be enabled via environment variables when compiling a target with afl-clang-fast or afl-clang-lto. The concept is similar in purpose to the CompareCoverage (CmpLog) feature found in other modes like QEMU Mode and Unicorn Mode.
Sources: instrumentation/README.laf-intel.md, qemu_mode/README.md:94, unicorn_mode/README.md:83
Enabling LAF-Intel Passes
The LAF-Intel transformations are not enabled by default. They must be activated by setting specific environment variables before compiling the target program with an LLVM-based compiler like afl-clang-fast. The afl-cc wrapper detects these variables and injects the appropriate LLVM passes into the compilation process.
The following diagram illustrates how afl-cc processes these environment variables to load the necessary passes.
This diagram shows the logic within afl-cc for enabling LAF-Intel passes based on environment variables.
Sources: src/afl-cc.c:1479-1498
Configuration Options
The passes can be enabled individually or all at once.
| Environment Variable | Description |
|---|---|
AFL_LLVM_LAF_SPLIT_SWITCHES=1 | Enables the split-switches pass, which transforms switch statements. |
AFL_LLVM_LAF_TRANSFORM_COMPARES=1 | Enables the transform-compares pass for functions like strcmp, memcmp, strncmp, strcasecmp, and strncasecmp. |
AFL_LLVM_LAF_SPLIT_COMPARES=1 | Enables the split-compares pass. This simplifies >= and <= operators and splits large-width integer comparisons into chains of 8-bit comparisons. |
AFL_LLVM_LAF_SPLIT_COMPARES_BITW=<bit_width> | Modifies the behavior of split-compares to split comparisons down to the specified bit width (64, 32, or 16) instead of the default 8. |
AFL_LLVM_LAF_SPLIT_FLOATS=1 | Enables splitting of floating-point comparisons. This automatically activates AFL_LLVM_LAF_SPLIT_COMPARES. |
AFL_LLVM_LAF_ALL=1 | A convenience variable to enable all of the above transformations. |
Sources: instrumentation/README.laf-intel.md:14-42, src/afl-cc.c:1060-1065
Incompatibilities
The LAF-Intel passes are incompatible with the AFL_LLVM_DICT2FILE feature. If both are enabled, afl-cc will terminate with a fatal error.
Sources: src/afl-cc.c:1067-1071
Core Components and Transformations
The LAF-Intel feature consists of several distinct LLVM passes, each targeting a specific type of complex comparison.
split-switches Pass
This pass, enabled by AFL_LLVM_LAF_SPLIT_SWITCHES, transforms switch statements into a series of if-then-else comparisons. This makes it easier for the fuzzer to explore each case of the switch individually.
Sources: instrumentation/README.laf-intel.md:16-17, src/afl-cc.c:1483-1485
transform-compares Pass
Enabled by AFL_LLVM_LAF_TRANSFORM_COMPARES, this pass targets common string and memory comparison functions. It replaces calls to functions like strcmp and memcmp with equivalent code that performs byte-by-byte comparisons, providing granular feedback to the fuzzer. Enabling any LAF-Intel feature also automatically adds -fno-builtin-* flags for these comparison functions to prevent the compiler from optimizing them away.
Sources: instrumentation/README.laf-intel.md:19-21, src/afl-cc.c:1826-1836, src/afl-cc.c:1487-1490
split-compares Pass
This is one of the most critical passes, enabled by AFL_LLVM_LAF_SPLIT_COMPARES. It performs several transformations on integer comparisons:
- Simplifies
>=and<=operators into chains of>(<) and==comparisons. - Changes signed integer comparisons into a chain of a sign-only comparison and an unsigned integer comparison.
- Splits 64, 32, and 16-bit unsigned integer comparisons into a chain of 8-bit comparisons.
The following diagram illustrates how a 32-bit comparison is split.
This diagram shows how a single 32-bit comparison is broken into four sequential 8-bit comparisons, allowing the fuzzer to solve them incrementally.
Sources: instrumentation/README.laf-intel.md:23-30
The bit width for splitting can be controlled with AFL_LLVM_LAF_SPLIT_COMPARES_BITW, which accepts values of 64, 32, or 16. The default is 8.
Sources: instrumentation/README.laf-intel.md:32-35, test/test-llvm.sh:211-224
split-floats Pass
Enabled by AFL_LLVM_LAF_SPLIT_FLOATS, this unique feature splits floating-point comparisons. It works by breaking down the float into its constituent parts (sign, exponent, and mantissa) and comparing each part separately. These sub-comparisons are then further split into 8-bit comparisons if necessary. Enabling this option automatically activates the split-compares pass as well.
Sources: instrumentation/README.laf-intel.md:37-42, src/afl-cc.c:1495-1498
Testing and Verification
The functionality of the LAF-Intel passes is verified through the test/test-llvm.sh script. This script compiles test programs (test-int_cases.c, test-uint_cases.c, test-compcov.c, test-floatingpoint.c) with the various AFL_LLVM_LAF_* environment variables set and checks for expected outcomes.
For example, the test for integer splitting iterates through different integer types and bit-width splits, ensuring the target compiles and runs correctly.
# Example from test-llvm.sh for testing integer splitting
for testcase in ./test-int_cases.c ./test-uint_cases.c; do
for I in char short int long "long long"; do
for BITS in 8 16 32 64; do
bin="$testcase-split-$I-$BITS.compcov"
AFL_DEBUG=1 AFL_LLVM_LAF_SPLIT_COMPARES_BITW=$BITS AFL_LLVM_LAF_SPLIT_COMPARES=1 ../afl-clang-fast -fsigned-char -DINT_TYPE="$I" -o "$bin" "$testcase" > test.out 2>&1;
# ... checks for successful compilation and execution ...
done
done
done
Sources: test/test-llvm.sh:210-227
Similarly, the floating-point split test compiles test-floatingpoint.c and runs afl-fuzz against it, expecting to find a crash that is only reachable when the float comparison is successfully solved.
Sources: test/test-llvm.sh:246-264
Summary
LAF-Intel is a powerful feature in AFL++ for binary instrumentation that uses LLVM passes to deconstruct complex comparisons. By breaking down switch statements, memory/string comparison functions, and large-width integer and floating-point comparisons into simpler, byte-wise checks, it makes it much easier for the fuzzer to satisfy complex path conditions. This leads to deeper code exploration and a higher chance of discovering vulnerabilities. The feature is highly configurable through environment variables, allowing developers to target specific types of comparisons within their code.
Sources: instrumentation/README.laf-intel.md