Skip to content

Commit 98bd0a8

Browse files
authored
Merge pull request #456 from #369-fuzz
[#369] Fuzz Testing in Docker
2 parents d72b9e2 + 92e0906 commit 98bd0a8

10 files changed

Lines changed: 766 additions & 35 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
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.*
@@ -32,8 +33,10 @@ test/CMakeFiles/*
3233
Testing/Temporary/*
3334
docs/wiki/_build
3435
docs/wiki/_templates
36+
support/fuzz/corpus/
37+
support/fuzz/output/
3538
support/scripts/src/*
3639
support/scripts/test/*
3740
support/scripts/bin/*
3841
support/scripts/CMakeFiles/*
39-
42+
output/*

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)
@@ -188,7 +189,13 @@ endif()
188189
#
189190
# Project Specifics
190191
#
191-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
192+
if(ENABLE_FUZZING)
193+
# More permissive flags for fuzzing (afl compiler fails with -Werror for self-assign warnings)
194+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-self-assign -g -O0")
195+
else()
196+
# Stricter flags for normal builds (treat warnings as errors)
197+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
198+
endif()
192199

193200
include_directories(include)
194201
add_subdirectory(src)
@@ -200,3 +207,7 @@ endif()
200207
if(TEST)
201208
add_subdirectory(test)
202209
endif()
210+
211+
if(ENABLE_FUZZING)
212+
add_subdirectory(./support/fuzz)
213+
endif()

support/Dockerfile

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,18 @@
88
#
99
# Follow multi-arch instructions: https://www.docker.com/blog/multi-arch-images/
1010
# docker login
11-
# docker buildx create --name clbuilder
12-
# docker buildx use clbuilder
13-
# docker buildx build --platform linux/amd64 -t ivvitc/cryptolib:dev --push .
14-
#
15-
# TODO:
11+
# docker buildx create --name clb
12+
# docker buildx use clb
1613
# docker buildx build --platform linux/amd64,linux/arm64 -t ivvitc/cryptolib:dev --push .
1714
#
1815

19-
ARG WOLFSSL_VERSION=5.6.0-stable
20-
FROM ubuntu:noble-20241118.1 AS cl0
21-
16+
FROM ubuntu:noble-20250127 AS cl0
2217
ARG DEBIAN_FRONTEND=noninteractive
2318
RUN apt-get update -y \
2419
&& apt-get install -y \
2520
autoconf \
2621
automake \
27-
build-essential \
22+
build-essential \
2823
ca-certificates \
2924
cmake \
3025
curl \
@@ -34,6 +29,7 @@ RUN apt-get update -y \
3429
gcc-14 \
3530
lcov \
3631
libcurl4-openssl-dev \
32+
libgcrypt20-dev \
3733
libmariadb-dev \
3834
libmariadb-dev-compat \
3935
libtool \
@@ -45,32 +41,12 @@ RUN apt-get update -y \
4541
python3-myst-parser \
4642
unzip \
4743
&& rm -rf /var/lib/apt/lists/*
48-
4944
RUN ldconfig \
5045
&& update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 \
5146
&& update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-14 60
5247

5348
FROM cl0 AS cl1
54-
ARG GPG_ERROR_VERSION=1.50
55-
ARG GCRYPT_VERSION=1.11.0
56-
RUN curl \
57-
-LS https://www.gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 \
58-
-o /tmp/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 \
59-
&& tar -xjf /tmp/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 -C /tmp/ \
60-
&& cd /tmp/libgpg-error-${GPG_ERROR_VERSION} \
61-
&& ./configure \
62-
&& make install \
63-
&& curl \
64-
-LS https://www.gnupg.org/ftp/gcrypt/libgcrypt/libgcrypt-${GCRYPT_VERSION}.tar.bz2 \
65-
-o /tmp/libgcrypt-${GCRYPT_VERSION}.tar.bz2 \
66-
&& tar -xjf /tmp/libgcrypt-${GCRYPT_VERSION}.tar.bz2 -C /tmp/ \
67-
&& cd /tmp/libgcrypt-${GCRYPT_VERSION} \
68-
&& ./configure \
69-
&& make install \
70-
&& ldconfig
71-
72-
FROM cl1 AS cl2
73-
ARG WOLFSSL_VERSION=5.6.0-stable
49+
ARG WOLFSSL_VERSION=5.7.6-stable
7450
RUN curl \
7551
-LS https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}.zip \
7652
-o v${WOLFSSL_VERSION}.zip \
@@ -84,4 +60,33 @@ RUN curl \
8460
&& make install \
8561
&& ldconfig
8662

87-
63+
FROM cl1 AS cl2
64+
ARG DEBIAN_FRONTEND=noninteractive
65+
RUN apt-get update -y \
66+
&& apt-get install -y \
67+
build-essential \
68+
python3-dev \
69+
automake \
70+
cmake \
71+
git \
72+
flex \
73+
bison \
74+
libglib2.0-dev \
75+
libpixman-1-dev \
76+
python3-setuptools \
77+
cargo \
78+
libgtk-3-dev \
79+
lld-14 \
80+
llvm-14 \
81+
llvm-14-dev \
82+
clang-14 \
83+
gcc-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-plugin-dev \
84+
libstdc++-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-dev \
85+
ninja-build \
86+
screen \
87+
&& rm -rf /var/lib/apt/lists/* \
88+
&& git clone https://github.com/AFLplusplus/AFLplusplus -b v4.31c /tmp/AFLplusplus \
89+
&& cd /tmp/AFLplusplus \
90+
&& make distrib \
91+
&& make install \
92+
&& rm -rf /tmp/AFLplusplus

support/fuzz/CMakeLists.txt

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

support/fuzz/generate_corpus.py

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

0 commit comments

Comments
 (0)