Skip to content

Commit 7c5fd2c

Browse files
authored
Merge pull request #442 from mirkobitetto/dev
feat: add fuzzing harness and infrastructure;
2 parents eb29d31 + 137420c commit 7c5fd2c

File tree

7 files changed

+719
-1
lines changed

7 files changed

+719
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ __pycache__
1212
*.dat
1313
*.so
1414
build/
15+
build-*/
1516
venv
1617
vgcore*
1718
core.*
@@ -23,6 +24,8 @@ DartConfiguration.tcl
2324
sa_save_file.bin
2425
bin/*
2526
CMakeFiles/*
27+
fuzz/corpus/
28+
fuzz/output/
2629
src/cmake_install.cmake
2730
src/CTestTestfile.cmake
2831
src/CMakeFiles/*

CMakeLists.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ option(CRYPTO_WOLFSSL "Cryptography Module - WolfSSL" OFF)
4141
option(CRYPTO_CUSTOM "Cryptography Module - CUSTOM" OFF)
4242
option(CRYPTO_CUSTOM_PATH "Cryptography Module - CUSTOM PATH" OFF)
4343
option(DEBUG "Debug" OFF)
44+
option(ENABLE_FUZZING "Enable fuzz testing" OFF)
4445
option(KEY_CUSTOM "Key Module - Custom" OFF)
4546
option(KEY_CUSTOM_PATH "Custom Key Path" OFF)
4647
option(KEY_INTERNAL "Key Module - Internal" OFF)
@@ -191,7 +192,13 @@ endif()
191192
#
192193
# Project Specifics
193194
#
194-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
195+
if(ENABLE_FUZZING)
196+
# More permissive flags for fuzzing (afl compiler fails with -Werror for self-assign warnings)
197+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-self-assign -g -O0")
198+
else()
199+
# Stricter flags for normal builds (treat warnings as errors)
200+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
201+
endif()
195202

196203
include_directories(include)
197204
add_subdirectory(src)
@@ -203,3 +210,7 @@ endif()
203210
if(TEST)
204211
add_subdirectory(test)
205212
endif()
213+
214+
if(ENABLE_FUZZING)
215+
add_subdirectory(fuzz)
216+
endif()

fuzz/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Include necessary directories
2+
include_directories(include)
3+
include_directories(../include)
4+
include_directories(../test/include)
5+
6+
# Create shared utils library from test code
7+
add_library(shared_utils STATIC ../test/core/shared_util.c)
8+
9+
# Build the fuzzing harness
10+
add_executable(fuzz_harness src/fuzz_harness.c)
11+
target_link_libraries(fuzz_harness LINK_PUBLIC shared_utils crypto pthread)
12+
13+
# Add fuzzing-specific compiler flags
14+
target_compile_options(fuzz_harness PRIVATE -fsanitize=fuzzer -g)
15+
target_link_options(fuzz_harness PRIVATE -fsanitize=fuzzer)
16+
17+
# Copy the executable to bin directory
18+
add_custom_command(TARGET fuzz_harness POST_BUILD
19+
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:fuzz_harness> ${PROJECT_BINARY_DIR}/bin/fuzz_harness
20+
COMMAND ${CMAKE_COMMAND} -E remove $<TARGET_FILE:fuzz_harness>
21+
COMMENT "Created ${PROJECT_BINARY_DIR}/bin/fuzz_harness"
22+
)

fuzz/generate_corpus.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import random
5+
import struct
6+
import argparse
7+
from pathlib import Path
8+
9+
10+
def ensure_dir(directory):
11+
"""Create directory if it doesn't exist"""
12+
Path(directory).mkdir(parents=True, exist_ok=True)
13+
14+
15+
def generate_random_bytes(min_size, max_size):
16+
"""Generate random bytes of size between min_size and max_size"""
17+
size = random.randint(min_size, max_size)
18+
return bytes(random.randint(0, 255) for _ in range(size))
19+
20+
21+
def generate_tc_frame(random_content=True):
22+
"""Generate a TC frame with valid-looking header"""
23+
if random_content:
24+
frame_size = random.randint(10, 200)
25+
frame = bytearray(generate_random_bytes(frame_size, frame_size))
26+
else:
27+
frame_size = 10
28+
frame = bytearray(frame_size)
29+
30+
# Set basic TC frame header fields
31+
frame[0] = 0x20 # Version 1, Type TC
32+
frame[1] = 0x03 # SCID
33+
frame[2] = 0x00 # VCID
34+
frame[3] = (frame_size - 5) & 0xFF # Frame length
35+
36+
return frame
37+
38+
39+
def generate_tm_frame(random_content=True):
40+
"""Generate a TM frame with valid-looking header"""
41+
if random_content:
42+
frame_size = random.randint(12, 200)
43+
frame = bytearray(generate_random_bytes(frame_size, frame_size))
44+
else:
45+
frame_size = 12
46+
frame = bytearray(frame_size)
47+
48+
# Set basic TM frame header fields
49+
frame[0] = 0x08 # Version 1, TM
50+
frame[1] = 0x03 # SCID
51+
frame[2] = 0x00 # VCID
52+
53+
return frame
54+
55+
56+
def generate_aos_frame(random_content=True):
57+
"""Generate an AOS frame with valid-looking header"""
58+
if random_content:
59+
frame_size = random.randint(14, 200)
60+
frame = bytearray(generate_random_bytes(frame_size, frame_size))
61+
else:
62+
frame_size = 14
63+
frame = bytearray(frame_size)
64+
65+
# Set basic AOS frame header fields
66+
frame[0] = 0x10 # Version 1, AOS
67+
frame[1] = 0x03 # SCID
68+
frame[2] = 0x00 # VCID
69+
70+
return frame
71+
72+
73+
def generate_corpus(output_dir, num_samples_per_selector=5):
74+
"""Generate corpus files for each selector value"""
75+
ensure_dir(output_dir)
76+
77+
# Generate samples for each selector (0-6)
78+
for selector in range(7):
79+
for i in range(num_samples_per_selector):
80+
# File naming: selector_type_variant.bin
81+
if selector in [0, 1]: # TC frame operations
82+
frame = generate_tc_frame(random_content=(i != 0))
83+
file_name = f"{selector:02d}_tc_{i:02d}.bin"
84+
elif selector in [2, 5]: # TM frame operations
85+
frame = generate_tm_frame(random_content=(i != 0))
86+
file_name = f"{selector:02d}_tm_{i:02d}.bin"
87+
elif selector in [3, 4]: # AOS frame operations
88+
frame = generate_aos_frame(random_content=(i != 0))
89+
file_name = f"{selector:02d}_aos_{i:02d}.bin"
90+
else: # selector == 6, TC frame for FECF check
91+
frame = generate_tc_frame(random_content=(i != 0))
92+
file_name = f"{selector:02d}_tc_fecf_{i:02d}.bin"
93+
94+
# Add the selector byte at the beginning
95+
output = bytearray([selector]) + frame
96+
97+
# Write to file
98+
with open(os.path.join(output_dir, file_name), "wb") as f:
99+
f.write(output)
100+
101+
# Generate some edge cases
102+
edge_cases = [
103+
# Minimal valid input (just selector)
104+
(0, bytearray([0])),
105+
(1, bytearray([1])),
106+
(2, bytearray([2])),
107+
(3, bytearray([3])),
108+
(4, bytearray([4])),
109+
(5, bytearray([5])),
110+
(6, bytearray([6])),
111+
112+
# Very large inputs
113+
(0, bytearray([0]) + generate_random_bytes(2000, 2000)),
114+
(3, bytearray([3]) + generate_random_bytes(2000, 2000)),
115+
116+
# Interesting byte patterns
117+
(0, bytearray([0]) + bytes([0xFF] * 50)),
118+
(1, bytearray([1]) + bytes([0x00] * 50)),
119+
(2, bytearray([2]) + bytes([i % 256 for i in range(100)])),
120+
(5, bytearray([5]) + bytes([0xAA, 0x55] * 25)) # Alternating bits
121+
]
122+
123+
for idx, (selector, data) in enumerate(edge_cases):
124+
file_name = f"edge_{idx:02d}_sel{selector}.bin"
125+
with open(os.path.join(output_dir, file_name), "wb") as f:
126+
f.write(data)
127+
128+
129+
def main():
130+
parser = argparse.ArgumentParser(
131+
description='Generate corpus for CryptoLib fuzzer')
132+
parser.add_argument('--output', '-o', default='corpus',
133+
help='Output directory for corpus files')
134+
parser.add_argument('--samples', '-n', type=int, default=5,
135+
help='Number of samples per selector')
136+
args = parser.parse_args()
137+
138+
print(f"Generating corpus in directory: {args.output}")
139+
generate_corpus(args.output, args.samples)
140+
print(f"Generated {7 * args.samples + 11} corpus files")
141+
142+
143+
if __name__ == "__main__":
144+
main()

fuzz/scripts/build-fuzz.sh

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/bin/bash
2+
3+
# === Configuration Options ===
4+
# Set to 1 to enable aggressive optimizations (requires CPU with AVX2/FMA support)
5+
# Set to 0 for more compatible builds
6+
ENABLE_OPTIMIZATIONS=1
7+
8+
# Navigate to project root directory
9+
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
10+
cd "$PROJECT_ROOT"
11+
echo "🏠 Working from project root: $PROJECT_ROOT"
12+
13+
14+
# === Check for AFL++ and select best compiler ===
15+
echo "🔍 Checking for AFL++ compilers..."
16+
17+
if command -v afl-clang-lto &> /dev/null; then
18+
echo "✅ Found afl-clang-lto (recommended LTO mode)"
19+
CC=afl-clang-lto
20+
CXX=afl-clang-lto++
21+
elif command -v afl-clang-fast &> /dev/null; then
22+
echo "✅ Found afl-clang-fast (LLVM mode)"
23+
CC=afl-clang-fast
24+
CXX=afl-clang-fast++
25+
elif command -v afl-gcc-fast &> /dev/null; then
26+
echo "✅ Found afl-gcc-fast (GCC plugin mode)"
27+
CC=afl-gcc-fast
28+
CXX=afl-g++-fast
29+
elif command -v afl-gcc &> /dev/null; then
30+
echo "✅ Found afl-gcc (basic AFL instrumentation)"
31+
CC=afl-gcc
32+
CXX=afl-g++
33+
else
34+
echo "❌ ERROR: No AFL++ compilers found. Please install AFL++ first:"
35+
echo " git clone https://github.com/AFLplusplus/AFLplusplus"
36+
echo " cd AFLplusplus && make && sudo make install"
37+
echo "See: https://github.com/AFLplusplus/AFLplusplus/blob/stable/docs/INSTALL.md"
38+
exit 1
39+
fi
40+
41+
# Export the selected compiler
42+
export CC=$CC
43+
export CXX=$CXX
44+
45+
# Number of CPU cores for parallel compilation
46+
CORES=$(nproc)
47+
48+
# Set optimization flags based on configuration
49+
if [ $ENABLE_OPTIMIZATIONS -eq 1 ]; then
50+
echo "⚠️ Using aggressive optimizations (requires CPU with AVX2/FMA support)"
51+
OPT_FLAGS="-O3 -march=native -mtune=native -flto -funroll-loops -ffast-math -mavx2 -mfma"
52+
else
53+
echo "ℹ️ Using standard optimization level (compatible with most CPUs)"
54+
OPT_FLAGS="-O2"
55+
fi
56+
57+
# === Compile without ASan ===
58+
echo "🔨 Compiling CryptoLib without ASan..."
59+
rm -rf build
60+
mkdir build && cd build
61+
cmake .. -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \
62+
-DCMAKE_C_FLAGS="$OPT_FLAGS" \
63+
-DCMAKE_CXX_FLAGS="$OPT_FLAGS" \
64+
-DCMAKE_EXE_LINKER_FLAGS="-flto" \
65+
-DCRYPTO_LIBGCRYPT=ON \
66+
-DENABLE_FUZZING=ON \
67+
-DDEBUG=ON \
68+
-DKEY_INTERNAL=ON \
69+
-DMC_INTERNAL=ON \
70+
-DSA_INTERNAL=ON
71+
make -j$CORES
72+
cd ..
73+
74+
# === Compile with ASan ===
75+
echo "🔨 Compiling CryptoLib with ASan..."
76+
rm -rf build-asan
77+
mkdir build-asan && cd build-asan
78+
cmake .. -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \
79+
-DCMAKE_C_FLAGS="-fsanitize=address $OPT_FLAGS" \
80+
-DCMAKE_CXX_FLAGS="-fsanitize=address $OPT_FLAGS" \
81+
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -flto" \
82+
-DCRYPTO_LIBGCRYPT=ON \
83+
-DENABLE_FUZZING=ON \
84+
-DDEBUG=ON \
85+
-DKEY_INTERNAL=ON \
86+
-DMC_INTERNAL=ON \
87+
-DSA_INTERNAL=ON
88+
make -j$CORES
89+
cd ..
90+
91+
# === Compile with CmpLog ===
92+
echo "🔨 Compiling CryptoLib with CmpLog instrumentation..."
93+
rm -rf build-cmplog
94+
mkdir build-cmplog && cd build-cmplog
95+
export AFL_LLVM_CMPLOG=1 # Enable CmpLog instrumentation
96+
cmake .. -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \
97+
-DCMAKE_C_FLAGS="$OPT_FLAGS" \
98+
-DCMAKE_CXX_FLAGS="$OPT_FLAGS" \
99+
-DCRYPTO_LIBGCRYPT=ON \
100+
-DENABLE_FUZZING=ON \
101+
-DDEBUG=ON \
102+
-DKEY_INTERNAL=ON \
103+
-DMC_INTERNAL=ON \
104+
-DSA_INTERNAL=ON
105+
make -j$CORES
106+
unset AFL_LLVM_CMPLOG # Unset to avoid affecting other builds
107+
cd ..
108+
109+
# === Compile with CompCov (laf-intel) ===
110+
echo "🔨 Compiling CryptoLib with CompCov (laf-intel) instrumentation..."
111+
rm -rf build-compcov
112+
mkdir build-compcov && cd build-compcov
113+
export AFL_LLVM_LAF_ALL=1 # Enable CompCov instrumentation
114+
cmake .. -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \
115+
-DCMAKE_C_FLAGS="$OPT_FLAGS" \
116+
-DCMAKE_CXX_FLAGS="$OPT_FLAGS" \
117+
-DCRYPTO_LIBGCRYPT=ON \
118+
-DENABLE_FUZZING=ON \
119+
-DDEBUG=ON \
120+
-DKEY_INTERNAL=ON \
121+
-DMC_INTERNAL=ON \
122+
-DSA_INTERNAL=ON
123+
make -j$CORES
124+
unset AFL_LLVM_LAF_ALL # Unset to avoid affecting other builds
125+
cd ..
126+
127+
# === Final Status ===
128+
echo "✅ Build complete!"
129+
echo "📂 Non-ASan build: 'build/'"
130+
echo "📂 ASan build: 'build-asan/'"
131+
echo "📂 CmpLog build: 'build-cmplog/'"
132+
echo "📂 CompCov (laf-intel) build: 'build-compcov/'"
133+
echo ""
134+
echo "To run fuzzing with AFL++:"
135+
echo "$(dirname "$0")/run-fuzz-multithreaded.sh"
136+
echo ""
137+
echo "⚠️ AFL++ SYSTEM CONFIGURATION REMINDERS ⚠️"
138+
echo "For optimal fuzzing performance, consider running these commands:"
139+
echo ""
140+
echo "1️⃣ Disable CPU frequency scaling:"
141+
echo " echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"
142+
echo ""
143+
echo "2️⃣ Configure core pattern for crash analysis:"
144+
echo " echo core | sudo tee /proc/sys/kernel/core_pattern"
145+
echo ""
146+
echo "📋 TROUBLESHOOTING FUZZING SESSIONS 📋"
147+
echo "If the fuzzer does not start or you encounter issues:"
148+
echo ""
149+
echo "1. List all screen sessions:"
150+
echo " screen -ls"
151+
echo ""
152+
echo "2. Reattach to a specific session to see errors:"
153+
echo " screen -r session_name"
154+
echo ""
155+
echo "3. To detach from a screen session: Press Ctrl+A, then D"

0 commit comments

Comments
 (0)