Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AFLplusplus/AFLplusplus/llms.txt

Use this file to discover all available pages before exploring further.

Overview

LAF-Intel (Lost AFL Intel) provides compiler transformations that help AFL++ enter conditional blocks where conditions consist of comparisons of large values. By splitting complex comparisons into simpler ones, it dramatically improves the fuzzer’s ability to discover new paths.
Originally developed by an individual nicknamed “laf-intel” and described in the blog post Circumventing Fuzzing Roadblocks with Compiler Transformations.

The Problem

Consider this code:
if (input_value == 0x12345678) {
  // Interesting code here
}
Without LAF-Intel, AFL++ must randomly mutate the input until it hits exactly 0x12345678 - requiring approximately 1 in 4 billion attempts!

The Solution

LAF-Intel transforms the comparison into multiple smaller comparisons:
// Conceptual transformation
if ((input_value & 0xFF) == 0x78) {         // Byte 1
  if ((input_value & 0xFF00) == 0x5600) {   // Byte 2
    if ((input_value & 0xFF0000) == 0x340000) { // Byte 3
      if ((input_value & 0xFF000000) == 0x12000000) { // Byte 4
        // Interesting code
      }
    }
  }
}
Now AFL++ gets feedback at each byte, making discovery exponentially faster!

Transformation Passes

LAF-Intel includes three main transformation passes:

1. Split Switches

Transforms switch statements into separate conditional blocks. Enable with:
export AFL_LLVM_LAF_SPLIT_SWITCHES=1
Example transformation:
// Original
switch (value) {
  case 100: do_something(); break;
  case 200: do_other(); break;
  case 300: do_more(); break;
}

// Transformed (conceptual)
if (value == 100) do_something();
else if (value == 200) do_other();
else if (value == 300) do_more();
Benefit: Each case gets separate coverage feedback.

2. Transform Compares

Splits string comparison functions into byte-by-byte comparisons. Enable with:
export AFL_LLVM_LAF_TRANSFORM_COMPARES=1
Supported functions:
  • strcmp
  • strncmp
  • memcmp
  • strcasecmp
  • strncasecmp
Example transformation:
// Original
if (strcmp(input, "MAGIC") == 0) {
  // Secret code
}

// Transformed (conceptual)
if (input[0] == 'M') {
  if (input[1] == 'A') {
    if (input[2] == 'G') {
      if (input[3] == 'I') {
        if (input[4] == 'C') {
          // Secret code
        }
      }
    }
  }
}
Benefit: AFL++ gets feedback for each matching character.

3. Split Compares

Splits integer comparisons into chains of smaller comparisons. Enable with:
export AFL_LLVM_LAF_SPLIT_COMPARES=1
By default, this:
  1. Simplifies >= and <= into chains of > (or <) and ==
  2. Changes signed integer comparisons to chains of sign comparison + unsigned comparison
  3. Splits unsigned integer comparisons (64, 32, 16 bit) into 8-bit comparisons
Example transformation:
// Original
if (value32 == 0xDEADBEEF) {
  // Code
}

// Transformed to 8-bit comparisons
if ((value32 & 0xFF) == 0xEF) {
  if ((value32 & 0xFF00) == 0xBE00) {
    if ((value32 & 0xFF0000) == 0xAD0000) {
      if ((value32 & 0xFF000000) == 0xDE000000) {
        // Code
      }
    }
  }
}

Configuration Options

Basic Usage

AFL_LLVM_LAF_SPLIT_SWITCHES
boolean
Enable split-switches pass.
export AFL_LLVM_LAF_SPLIT_SWITCHES=1
afl-clang-fast -o target target.c
AFL_LLVM_LAF_TRANSFORM_COMPARES
boolean
Enable transform-compares pass for string comparison functions.
export AFL_LLVM_LAF_TRANSFORM_COMPARES=1
afl-clang-fast -o target target.c
AFL_LLVM_LAF_SPLIT_COMPARES
boolean
Enable split-compares pass for integer comparisons.
export AFL_LLVM_LAF_SPLIT_COMPARES=1
afl-clang-fast -o target target.c

Advanced Configuration

AFL_LLVM_LAF_SPLIT_COMPARES_BITW
integer
Set the bit width for splitting comparisons. Options: 64, 32, or 16.
export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=16
  • 64: Split only 64-bit comparisons (minimal splitting)
  • 32: Split 64 and 32-bit comparisons
  • 16: Split 64, 32, and 16-bit comparisons
  • Default: 8 (split all down to 8-bit)
AFL_LLVM_LAF_SPLIT_FLOATS
boolean
Split floating-point comparisons into sign, exponent, and mantissa comparisons.
export AFL_LLVM_LAF_SPLIT_FLOATS=1
Automatically activates AFL_LLVM_LAF_SPLIT_COMPARES.
AFL_LLVM_LAF_ALL
boolean
Enable all LAF-Intel transformations at once.
export AFL_LLVM_LAF_ALL=1
afl-clang-fast -o target target.c
Equivalent to setting:
  • AFL_LLVM_LAF_SPLIT_SWITCHES=1
  • AFL_LLVM_LAF_TRANSFORM_COMPARES=1
  • AFL_LLVM_LAF_SPLIT_COMPARES=1

Usage Examples

export AFL_LLVM_LAF_ALL=1
afl-clang-fast -o target target.c

Selective Transformations

# Only split switches and transform string compares
export AFL_LLVM_LAF_SPLIT_SWITCHES=1
export AFL_LLVM_LAF_TRANSFORM_COMPARES=1
afl-clang-fast -o target target.c

Custom Bit Width

# Split down to 16-bit instead of 8-bit
export AFL_LLVM_LAF_SPLIT_COMPARES=1
export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=16
afl-clang-fast -o target target.c

With Floating-Point Support

export AFL_LLVM_LAF_SPLIT_FLOATS=1
afl-clang-fast -o target target.c

Compiler Compatibility

LAF-Intel works with all LLVM-based instrumentation modes:
export AFL_LLVM_LAF_ALL=1
afl-clang-fast -o target target.c

Performance Considerations

Binary Size

LAF-Intel transformations increase binary size:
TransformationSize Increase
Split switches+10-30%
Transform compares+20-50%
Split compares+50-200%
All enabled+100-300%

Execution Speed

More comparisons = more instrumentation = slower execution:
SettingSpeed Impact
No LAF-IntelBaseline
Selective (switches only)-5 to -15%
Split compares-20 to -50%
All transformations-30 to -70%
The coverage improvement usually far outweighs the performance cost!

Coverage Improvement

Typical improvements in path discovery:
  • Magic bytes: 100-1000x faster discovery
  • Multi-byte comparisons: 10-100x faster
  • Switch statements: 2-10x more paths found
  • Overall coverage: 20-50% more edges discovered

Use Cases

// File format with magic bytes
if (*(uint32_t*)buf == 0x464c457f) {  // ELF magic
  parse_elf_file(buf);
}
LAF-Intel splits the 32-bit comparison, making the magic bytes discoverable byte-by-byte.
// Protocol parser
if (strncmp(packet, "HTTP/1.1", 8) == 0) {
  handle_http(packet);
} else if (strncmp(packet, "HTTP/2.0", 8) == 0) {
  handle_http2(packet);
}
Transform compares helps discover both protocol strings character-by-character.
// CRC check
uint32_t crc = calculate_crc32(data, len);
if (crc == expected_crc) {
  process_data(data);
}
Split compares makes it easier to synthesize valid checksums.
switch (state) {
  case STATE_INIT:    handle_init(); break;
  case STATE_READY:   handle_ready(); break;
  case STATE_ACTIVE:  handle_active(); break;
  case STATE_CLOSING: handle_closing(); break;
}
Split switches provides separate feedback for each state transition.

Comparison with CmpLog

FeatureLAF-IntelCmpLog
ApproachCompile-time transformationRuntime logging
String compares✅ Excellent✅ Excellent
Integer compares✅ Excellent⚠️ Limited
Switch statements✅ Yes❌ No
Magic bytes✅ Good✅ Excellent
Binary sizeLargerSeparate binary
PerformanceSlower executionOccasional slowdown
Memory overheadLowerHigher
For best results, use both LAF-Intel and CmpLog!
export AFL_LLVM_LAF_ALL=1
export AFL_LLVM_CMPLOG=1
afl-clang-fast -o target.cmplog target.c

Complete Example

#!/bin/bash
set -e

# Build target with LAF-Intel
echo "Building with LAF-Intel transformations..."

export AFL_LLVM_LAF_ALL=1
export AFL_USE_ASAN=1  # Optional: Add ASAN

afl-clang-lto \
  -o target.laf \
  target.c \
  -lz -lpthread

# Optional: Build CmpLog version too
echo "Building CmpLog version..."
export AFL_LLVM_CMPLOG=1

afl-clang-lto \
  -o target.cmplog \
  target.c \
  -lz -lpthread

unset AFL_LLVM_LAF_ALL
unset AFL_LLVM_CMPLOG

# Prepare corpus
mkdir -p input
echo "test" > input/seed

# Start fuzzing with both optimizations
echo "Starting fuzzer..."
afl-fuzz \
  -i input \
  -o output \
  -c ./target.cmplog \
  -m none \
  -- ./target.laf @@

Troubleshooting

Compilation Failures

Symptom: Target fails to compile with LAF-Intel enabled Solutions:
  1. Try disabling individual passes to identify the problematic one:
    # Test each separately
    export AFL_LLVM_LAF_SPLIT_SWITCHES=1
    # Try build
    
    unset AFL_LLVM_LAF_SPLIT_SWITCHES
    export AFL_LLVM_LAF_TRANSFORM_COMPARES=1
    # Try build
    
  2. Some optimizations may conflict. Try disabling optimizations:
    export AFL_DONT_OPTIMIZE=1
    

Binary Too Large

Symptom: Instrumented binary is excessively large Solutions:
  1. Use selective instrumentation:
    export AFL_LLVM_ALLOWLIST=important_files.txt
    
  2. Use less aggressive bit width:
    export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=16
    
  3. Enable only necessary transformations:
    # Instead of AFL_LLVM_LAF_ALL=1
    export AFL_LLVM_LAF_TRANSFORM_COMPARES=1
    

No Coverage Improvement

Symptom: LAF-Intel doesn’t improve coverage Possible reasons:
  1. Target has few large comparisons
  2. Bottleneck is elsewhere (try CmpLog)
  3. Need longer fuzzing time to see benefits

Best Practices

  1. Start with all transformations enabled:
    export AFL_LLVM_LAF_ALL=1
    
  2. Combine with LTO mode for best performance:
    export AFL_LLVM_LAF_ALL=1
    afl-clang-lto -o target target.c
    
  3. Use with CmpLog for maximum effectiveness:
    export AFL_LLVM_LAF_ALL=1
    export AFL_LLVM_CMPLOG=1
    
  4. Monitor binary size - if too large, selectively disable transformations
  5. Test stability - ensure transformations don’t break functionality

Next Steps

CmpLog

Combine with CmpLog for maximum comparison handling

LTO Mode

Use collision-free instrumentation for better coverage

Persistent Mode

Add persistent mode for 10-20x speedup

Selective Instrumentation

Instrument only specific parts of code