Skip to content

[nasa/cryptolib#369] Fuzz Testing in Docker #456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 25, 2025
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ __pycache__
*.dat
*.so
build/
build-*/
venv
vgcore*
core.*
Expand All @@ -32,8 +33,10 @@ test/CMakeFiles/*
Testing/Temporary/*
docs/wiki/_build
docs/wiki/_templates
support/fuzz/corpus/
support/fuzz/output/
support/scripts/src/*
support/scripts/test/*
support/scripts/bin/*
support/scripts/CMakeFiles/*

output/*
13 changes: 12 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ option(CRYPTO_WOLFSSL "Cryptography Module - WolfSSL" OFF)
option(CRYPTO_CUSTOM "Cryptography Module - CUSTOM" OFF)
option(CRYPTO_CUSTOM_PATH "Cryptography Module - CUSTOM PATH" OFF)
option(DEBUG "Debug" OFF)
option(ENABLE_FUZZING "Enable fuzz testing" OFF)
option(KEY_CUSTOM "Key Module - Custom" OFF)
option(KEY_CUSTOM_PATH "Custom Key Path" OFF)
option(KEY_INTERNAL "Key Module - Internal" OFF)
Expand Down Expand Up @@ -188,7 +189,13 @@ endif()
#
# Project Specifics
#
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
if(ENABLE_FUZZING)
# More permissive flags for fuzzing (afl compiler fails with -Werror for self-assign warnings)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-self-assign -g -O0")
else()
# Stricter flags for normal builds (treat warnings as errors)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -g -O0")
endif()

include_directories(include)
add_subdirectory(src)
Expand All @@ -200,3 +207,7 @@ endif()
if(TEST)
add_subdirectory(test)
endif()

if(ENABLE_FUZZING)
add_subdirectory(./support/fuzz)
endif()
67 changes: 36 additions & 31 deletions support/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,18 @@
#
# Follow multi-arch instructions: https://www.docker.com/blog/multi-arch-images/
# docker login
# docker buildx create --name clbuilder
# docker buildx use clbuilder
# docker buildx build --platform linux/amd64 -t ivvitc/cryptolib:dev --push .
#
# TODO:
# docker buildx create --name clb
# docker buildx use clb
# docker buildx build --platform linux/amd64,linux/arm64 -t ivvitc/cryptolib:dev --push .
#

ARG WOLFSSL_VERSION=5.6.0-stable
FROM ubuntu:noble-20241118.1 AS cl0

FROM ubuntu:noble-20250127 AS cl0
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y \
&& apt-get install -y \
autoconf \
automake \
build-essential \
build-essential \
ca-certificates \
cmake \
curl \
Expand All @@ -34,6 +29,7 @@ RUN apt-get update -y \
gcc-14 \
lcov \
libcurl4-openssl-dev \
libgcrypt20-dev \
libmariadb-dev \
libmariadb-dev-compat \
libtool \
Expand All @@ -45,32 +41,12 @@ RUN apt-get update -y \
python3-myst-parser \
unzip \
&& rm -rf /var/lib/apt/lists/*

RUN ldconfig \
&& update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 60 \
&& update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-14 60

FROM cl0 AS cl1
ARG GPG_ERROR_VERSION=1.50
ARG GCRYPT_VERSION=1.11.0
RUN curl \
-LS https://www.gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 \
-o /tmp/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 \
&& tar -xjf /tmp/libgpg-error-${GPG_ERROR_VERSION}.tar.bz2 -C /tmp/ \
&& cd /tmp/libgpg-error-${GPG_ERROR_VERSION} \
&& ./configure \
&& make install \
&& curl \
-LS https://www.gnupg.org/ftp/gcrypt/libgcrypt/libgcrypt-${GCRYPT_VERSION}.tar.bz2 \
-o /tmp/libgcrypt-${GCRYPT_VERSION}.tar.bz2 \
&& tar -xjf /tmp/libgcrypt-${GCRYPT_VERSION}.tar.bz2 -C /tmp/ \
&& cd /tmp/libgcrypt-${GCRYPT_VERSION} \
&& ./configure \
&& make install \
&& ldconfig

FROM cl1 AS cl2
ARG WOLFSSL_VERSION=5.6.0-stable
ARG WOLFSSL_VERSION=5.7.6-stable
RUN curl \
-LS https://github.com/wolfSSL/wolfssl/archive/v${WOLFSSL_VERSION}.zip \
-o v${WOLFSSL_VERSION}.zip \
Expand All @@ -84,4 +60,33 @@ RUN curl \
&& make install \
&& ldconfig


FROM cl1 AS cl2
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y \
&& apt-get install -y \
build-essential \
python3-dev \
automake \
cmake \
git \
flex \
bison \
libglib2.0-dev \
libpixman-1-dev \
python3-setuptools \
cargo \
libgtk-3-dev \
lld-14 \
llvm-14 \
llvm-14-dev \
clang-14 \
gcc-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-plugin-dev \
libstdc++-$(gcc --version|head -n1|sed 's/\..*//'|sed 's/.* //')-dev \
ninja-build \
screen \
&& rm -rf /var/lib/apt/lists/* \
&& git clone https://github.com/AFLplusplus/AFLplusplus -b v4.31c /tmp/AFLplusplus \
&& cd /tmp/AFLplusplus \
&& make distrib \
&& make install \
&& rm -rf /tmp/AFLplusplus
21 changes: 21 additions & 0 deletions support/fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Include necessary directories
include_directories(../../include)
include_directories(../../test/include)

# Create shared utils library from test code
add_library(shared_utils STATIC ../../test/core/shared_util.c)

# Build the fuzzing harness
add_executable(fuzz_harness src/fuzz_harness.c)
target_link_libraries(fuzz_harness LINK_PUBLIC shared_utils crypto pthread)

# Add fuzzing-specific compiler flags
target_compile_options(fuzz_harness PRIVATE -fsanitize=fuzzer -g)
target_link_options(fuzz_harness PRIVATE -fsanitize=fuzzer)

# Copy the executable to bin directory
add_custom_command(TARGET fuzz_harness POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:fuzz_harness> ${PROJECT_BINARY_DIR}/bin/fuzz_harness
COMMAND ${CMAKE_COMMAND} -E remove $<TARGET_FILE:fuzz_harness>
COMMENT "Created ${PROJECT_BINARY_DIR}/bin/fuzz_harness"
)
132 changes: 132 additions & 0 deletions support/fuzz/generate_corpus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env python3

import os
import random
import argparse
from pathlib import Path


def ensure_dir(directory):
"""Create directory if it doesn't exist"""
Path(directory).mkdir(parents=True, exist_ok=True)


def generate_random_bytes(min_size, max_size):
"""Generate random bytes of size between min_size and max_size"""
size = random.randint(min_size, max_size)
return bytes(random.randint(0, 255) for _ in range(size))


def generate_tc_frame():
"""Generate a TC frame with valid-looking header"""
frame_size = random.randint(6, 1024)
frame = bytearray(generate_random_bytes(frame_size, frame_size))

# Set basic TC frame header fields
frame[0] = 0x20 # Version 1, Type TC
frame[1] = 0x03 # SCID
frame[2] = 0x00 | ((frame_size - 1) >> 8); # VCID
frame[3] = (frame_size - 1) & 0xFF; # Frame length
frame[4] = 0x00; # Frame Sequence Number

return frame


def generate_tm_frame():
"""Generate a TM frame with valid-looking header"""
frame_size = 1786
frame = bytearray(generate_random_bytes(frame_size, frame_size))

# Set basic TM frame header fields
frame[0] = 0x02 # Version 1, TM
frame[1] = 0xC0 # SCID
frame[2] = 0x00 # VCID

return frame


def generate_aos_frame():
"""Generate an AOS frame with valid-looking header"""
frame_size = 1786
frame = bytearray(generate_random_bytes(frame_size, frame_size))

# Set basic AOS frame header fields
frame[0] = 0x40 # Version 1, AOS
frame[1] = 0xC0 # SCID
frame[2] = 0x00 # VCID

return frame


def generate_corpus(output_dir, num_samples_per_selector=5):
"""Generate corpus files for each selector value"""
ensure_dir(output_dir)

# Generate samples for each selector (0-6)
for selector in range(7):
for i in range(num_samples_per_selector):
# File naming: selector_type_variant.bin
if selector in [0, 1]: # TC frame operations
frame = generate_tc_frame()
file_name = f"{selector:02d}_tc_{i:02d}.bin"
elif selector in [2, 5]: # TM frame operations
frame = generate_tm_frame()
file_name = f"{selector:02d}_tm_{i:02d}.bin"
elif selector in [3, 4]: # AOS frame operations
frame = generate_aos_frame()
file_name = f"{selector:02d}_aos_{i:02d}.bin"
else: # selector == 6, TC frame for FECF check
frame = generate_tc_frame()
file_name = f"{selector:02d}_tc_fecf_{i:02d}.bin"

# Add the selector byte at the beginning
output = bytearray([selector]) + frame

# Write to file
with open(os.path.join(output_dir, file_name), "wb") as f:
f.write(output)

# Generate some edge cases
edge_cases = [
# Minimal valid input (just selector)
(0, bytearray([0])),
(1, bytearray([1])),
(2, bytearray([2])),
(3, bytearray([3])),
(4, bytearray([4])),
(5, bytearray([5])),
(6, bytearray([6])),

# Very large inputs
(0, bytearray([0]) + generate_random_bytes(2000, 2000)),
(3, bytearray([3]) + generate_random_bytes(2000, 2000)),

# Interesting byte patterns
(0, bytearray([0]) + bytes([0xFF] * 50)),
(1, bytearray([1]) + bytes([0x00] * 50)),
(2, bytearray([2]) + bytes([i % 256 for i in range(100)])),
(5, bytearray([5]) + bytes([0xAA, 0x55] * 25)) # Alternating bits
]

for idx, (selector, data) in enumerate(edge_cases):
file_name = f"edge_{idx:02d}_sel{selector}.bin"
with open(os.path.join(output_dir, file_name), "wb") as f:
f.write(data)


def main():
parser = argparse.ArgumentParser(
description='Generate corpus for CryptoLib fuzzer')
parser.add_argument('--output', '-o', default='corpus',
help='Output directory for corpus files')
parser.add_argument('--samples', '-n', type=int, default=5,
help='Number of samples per selector')
args = parser.parse_args()

print(f"Generating corpus in directory: {args.output}")
generate_corpus(args.output, args.samples)
print(f"Generated {7 * args.samples + 11} corpus files")


if __name__ == "__main__":
main()
Loading