Skip to content

Commit 9f3dda7

Browse files
authored
Merge pull request #757 from DLTcollab/fuzzing
Add libFuzzer-based fuzz testing infrastructure
2 parents c2ea61d + da7bbbd commit 9f3dda7

File tree

4 files changed

+1976
-2
lines changed

4 files changed

+1976
-2
lines changed

.github/workflows/main.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ jobs:
508508
Write-Host "OS: $([System.Environment]::OSVersion)"
509509
Write-Host "Processor: $env:PROCESSOR_ARCHITECTURE"
510510
511-
# Sanitizers on ARM: UBSan + ASan per IMPROVE.md H5
511+
# Sanitizers on ARM: UBSan + ASan
512512
sanitizers-arm:
513513
runs-on: ubuntu-24.04-arm
514514
strategy:
@@ -540,6 +540,30 @@ jobs:
540540
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1
541541
run: make SANITIZE=${{ matrix.sanitizer }} check
542542

543+
# Fuzz testing on ARM: libFuzzer with ASan/UBSan
544+
# Only runs on native ARM64 (no QEMU) with clang (libFuzzer requirement)
545+
fuzz-arm:
546+
runs-on: ubuntu-24.04-arm
547+
steps:
548+
- name: checkout code
549+
uses: actions/checkout@v6
550+
- name: install clang
551+
run: |
552+
# clang-18 is in 'universe' repository on Ubuntu 24.04
553+
sudo add-apt-repository -y -n universe
554+
sudo apt-get -o Acquire::Retries=5 update -q -y
555+
sudo apt-get -o Acquire::Retries=5 install -q -y --no-install-recommends clang-18
556+
- name: build and run fuzzer
557+
env:
558+
CXX: clang++-18
559+
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
560+
ASAN_OPTIONS: detect_leaks=0:halt_on_error=1
561+
run: |
562+
# Build fuzzer with crypto+crc extensions for full coverage
563+
make FEATURE=crypto+crc FUZZ_CXX=clang++-18 tests/fuzz
564+
# Run for 30 seconds in CI (verbose mode for logs)
565+
make FUZZ_TIME=30 fuzz-verbose
566+
543567
static-analysis:
544568
runs-on: ubuntu-24.04
545569
env:

Makefile

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,84 @@ endif
291291
coverage-report:
292292
@python3 scripts/coverage-check.py
293293

294-
.PHONY: clean check check-main check-ieee754 check-nan check-aes check-ubsan check-asan check-strict-aliasing check-uninit check-macros check-differential generate-golden coverage-report indent ieee754 nan aes
294+
# Fuzzing configuration
295+
FUZZ_EXEC = tests/fuzz
296+
FUZZ_TIME ?= 90
297+
FUZZ_ARGS ?=
298+
FUZZ_CORPUS ?= fuzz_corpus
299+
300+
# Detect if clang is available for fuzzing (libFuzzer requires clang)
301+
FUZZ_CXX := $(shell command -v clang++ 2>/dev/null)
302+
303+
# Fuzzer build flags
304+
# Note: libFuzzer is built into clang on Linux but may require separate
305+
# installation on macOS (install via: brew install llvm)
306+
FUZZ_CXXFLAGS = -g -O1 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer
307+
# Enable crypto extensions for AES and CLMUL testing on AArch64
308+
FUZZ_ARCH_FLAGS := $(ARCH_CFLAGS)
309+
ifeq ($(processor),$(filter $(processor),aarch64 arm64))
310+
FUZZ_ARCH_FLAGS := -march=armv8-a+fp+simd+crypto+crc
311+
endif
312+
FUZZ_CXXFLAGS += -Wall -Wno-unused-function -I. $(FUZZ_ARCH_FLAGS) -std=gnu++14
313+
314+
# On macOS, prefer Homebrew LLVM if available (has libFuzzer built-in)
315+
UNAME_S := $(shell uname -s)
316+
ifeq ($(UNAME_S),Darwin)
317+
HOMEBREW_LLVM_DIR := $(shell \
318+
if [ -d /opt/homebrew/opt/llvm ]; then echo /opt/homebrew/opt/llvm; \
319+
elif [ -d /usr/local/opt/llvm ]; then echo /usr/local/opt/llvm; \
320+
fi)
321+
ifneq ($(HOMEBREW_LLVM_DIR),)
322+
FUZZ_CXX := $(HOMEBREW_LLVM_DIR)/bin/clang++
323+
# Use Homebrew LLVM's libc++ to avoid ABI mismatch with libFuzzer
324+
FUZZ_CXXFLAGS += -L$(HOMEBREW_LLVM_DIR)/lib/c++ -Wl,-rpath,$(HOMEBREW_LLVM_DIR)/lib/c++
325+
FUZZ_CXXFLAGS += -L$(HOMEBREW_LLVM_DIR)/lib -Wl,-rpath,$(HOMEBREW_LLVM_DIR)/lib
326+
endif
327+
endif
328+
329+
# Build the fuzzer binary
330+
$(FUZZ_EXEC): tests/fuzz.cpp sse2neon.h
331+
@if [ -z "$(FUZZ_CXX)" ]; then \
332+
echo "Error: clang++ is required for fuzzing (libFuzzer support)"; \
333+
echo "On macOS: brew install llvm"; \
334+
echo "On Linux: apt install clang (or equivalent)"; \
335+
exit 1; \
336+
fi
337+
@echo "Building fuzzer with: $(FUZZ_CXX)"
338+
$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ tests/fuzz.cpp || { \
339+
echo ""; \
340+
echo "Build failed. If libFuzzer is missing:"; \
341+
echo " macOS: brew install llvm"; \
342+
echo " Linux: Should work with clang 11+"; \
343+
exit 1; \
344+
}
345+
346+
# Run fuzzer with progress bar (interactive mode)
347+
# Usage: make fuzz [FUZZ_TIME=60] [FUZZ_ARGS="-max_len=1024"]
348+
fuzz: $(FUZZ_EXEC)
349+
@mkdir -p $(FUZZ_CORPUS)
350+
./scripts/fuzz-progress.sh $(FUZZ_EXEC) $(FUZZ_TIME) $(FUZZ_CORPUS) $(FUZZ_ARGS)
351+
352+
# Run fuzzer in verbose mode (raw libFuzzer output)
353+
# Useful for debugging or CI/CD environments
354+
fuzz-verbose: $(FUZZ_EXEC)
355+
@mkdir -p $(FUZZ_CORPUS)
356+
$(FUZZ_EXEC) -max_total_time=$(FUZZ_TIME) $(FUZZ_CORPUS) $(FUZZ_ARGS)
357+
358+
# Clean fuzzer artifacts
359+
fuzz-clean:
360+
$(RM) $(FUZZ_EXEC)
361+
$(RM) -r $(FUZZ_CORPUS)
362+
$(RM) crash-* leak-* timeout-* oom-*
363+
364+
.PHONY: clean check check-main check-ieee754 check-nan check-aes check-ubsan check-asan check-strict-aliasing check-uninit check-macros check-differential generate-golden coverage-report indent ieee754 nan aes fuzz fuzz-verbose fuzz-clean
295365
clean:
296366
$(RM) $(OBJS) $(EXEC) $(deps) sse2neon.h.gch
297367
$(RM) $(IEEE754_OBJS) $(IEEE754_EXEC) $(ieee754_deps)
298368
$(RM) $(NAN_OBJS) $(NAN_EXEC) $(nan_deps)
299369
$(RM) $(AES_OBJS) $(AES_EXEC) $(aes_deps)
300370
$(RM) $(DIFFERENTIAL_OBJS) $(DIFFERENTIAL_EXEC) $(differential_deps)
371+
$(RM) $(FUZZ_EXEC)
301372

302373
-include $(deps)
303374
-include $(ieee754_deps)

scripts/fuzz-progress.sh

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env bash
2+
3+
# Progress bar wrapper for libFuzzer
4+
# Usage: ./scripts/fuzz-progress.sh <fuzzer_binary> <max_time_seconds> [fuzzer_args...]
5+
#
6+
# Features:
7+
# - Visual progress bar using Unicode block characters
8+
# - Real-time metrics (coverage, corpus size, exec/s)
9+
# - Automatic quiet mode in CI/CD environments
10+
# - Elapsed/remaining time display
11+
#
12+
# CI/CD Detection:
13+
# - Set CI=true, GITHUB_ACTIONS=true, or JENKINS_URL to enable quiet mode
14+
# - Or run with FUZZ_QUIET=1 to force quiet mode
15+
16+
set -e
17+
18+
# Check arguments
19+
if [ $# -lt 2 ]; then
20+
echo "Usage: $0 <fuzzer_binary> <max_time_seconds> [fuzzer_args...]"
21+
echo "Example: $0 tests/fuzz 60 -max_len=1024"
22+
exit 1
23+
fi
24+
25+
FUZZER="$1"
26+
MAX_TIME="$2"
27+
shift 2
28+
29+
# Validate MAX_TIME is a positive integer
30+
if ! [[ "$MAX_TIME" =~ ^[0-9]+$ ]] || [ "$MAX_TIME" -eq 0 ]; then
31+
echo "Error: max_time_seconds must be a positive integer (got: '$MAX_TIME')"
32+
exit 1
33+
fi
34+
35+
# Remaining arguments are passed to fuzzer (use array to handle spaces)
36+
FUZZER_ARGS=("$@")
37+
38+
# Detect CI/CD environment
39+
is_ci() {
40+
[ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$JENKINS_URL" ] ||
41+
[ -n "$GITLAB_CI" ] || [ -n "$CIRCLECI" ] || [ -n "$TRAVIS" ] ||
42+
[ -n "$FUZZ_QUIET" ]
43+
}
44+
45+
# ANSI color codes
46+
if [ -t 1 ] && ! is_ci; then
47+
COLOR_GREEN="\033[32m"
48+
COLOR_YELLOW="\033[33m"
49+
COLOR_CYAN="\033[36m"
50+
COLOR_BOLD="\033[1m"
51+
COLOR_RESET="\033[0m"
52+
CLEAR_LINE="\033[2K\r"
53+
else
54+
COLOR_GREEN=""
55+
COLOR_YELLOW=""
56+
COLOR_CYAN=""
57+
COLOR_BOLD=""
58+
COLOR_RESET=""
59+
CLEAR_LINE=""
60+
fi
61+
62+
# Progress bar configuration
63+
BAR_WIDTH=30
64+
65+
# Draw progress bar
66+
draw_progress_bar() {
67+
local percentage=$1
68+
local filled=$((percentage * BAR_WIDTH / 100))
69+
local empty=$((BAR_WIDTH - filled))
70+
71+
printf "${COLOR_CYAN}"
72+
for ((i = 0; i < filled; i++)); do printf ""; done
73+
for ((i = 0; i < empty; i++)); do printf ""; done
74+
printf "${COLOR_RESET}"
75+
}
76+
77+
# Format time as MM:SS
78+
format_time() {
79+
local seconds=$1
80+
printf "%02d:%02d" $((seconds / 60)) $((seconds % 60))
81+
}
82+
83+
# Parse libFuzzer output and extract metrics
84+
parse_metrics() {
85+
local line="$1"
86+
87+
# Extract coverage (cov: or NEW: lines contain coverage info)
88+
if [[ "$line" =~ cov:\ *([0-9]+) ]]; then
89+
echo "cov=${BASH_REMATCH[1]}"
90+
fi
91+
92+
# Extract corpus size
93+
if [[ "$line" =~ corp:\ *([0-9]+) ]]; then
94+
echo "corp=${BASH_REMATCH[1]}"
95+
fi
96+
97+
# Extract exec/s
98+
if [[ "$line" =~ exec/s:\ *([0-9]+) ]]; then
99+
echo "execs=${BASH_REMATCH[1]}"
100+
fi
101+
102+
# Detect special events
103+
if [[ "$line" =~ (BINGO|ERROR|CRASH|TIMEOUT|SUMMARY) ]]; then
104+
echo "event=${BASH_REMATCH[1]}"
105+
fi
106+
}
107+
108+
# Main progress display function
109+
run_with_progress() {
110+
local start_time=$(date +%s)
111+
local coverage=0
112+
local corpus=0
113+
local execs=0
114+
local last_event=""
115+
116+
# Create temporary file for fuzzer output
117+
local tmpfile=$(mktemp)
118+
trap "rm -f $tmpfile" EXIT
119+
120+
# Start fuzzer in background
121+
"$FUZZER" -max_total_time="$MAX_TIME" "${FUZZER_ARGS[@]}" 2>&1 | tee "$tmpfile" |
122+
while IFS= read -r line; do
123+
# Parse metrics from line
124+
metrics=$(parse_metrics "$line")
125+
126+
for metric in $metrics; do
127+
case "$metric" in
128+
cov=*) coverage="${metric#cov=}" ;;
129+
corp=*) corpus="${metric#corp=}" ;;
130+
execs=*) execs="${metric#execs=}" ;;
131+
event=*) last_event="${metric#event=}" ;;
132+
esac
133+
done
134+
135+
# Calculate timing
136+
local now=$(date +%s)
137+
local elapsed=$((now - start_time))
138+
local remaining=$((MAX_TIME - elapsed))
139+
if [ $remaining -lt 0 ]; then remaining=0; fi
140+
141+
local percentage=$((elapsed * 100 / MAX_TIME))
142+
if [ $percentage -gt 100 ]; then percentage=100; fi
143+
144+
# Display progress line
145+
printf "${CLEAR_LINE}"
146+
draw_progress_bar $percentage
147+
printf " ${COLOR_BOLD}%3d%%${COLOR_RESET}" $percentage
148+
printf " ${COLOR_GREEN}%s${COLOR_RESET}/%s" "$(format_time $elapsed)" "$(format_time $MAX_TIME)"
149+
printf " ${COLOR_YELLOW}cov:%d${COLOR_RESET}" $coverage
150+
printf " corp:%d" $corpus
151+
printf " exec/s:%d" $execs
152+
153+
# Show special events
154+
if [ -n "$last_event" ]; then
155+
printf " ${COLOR_BOLD}[%s]${COLOR_RESET}" "$last_event"
156+
fi
157+
done
158+
159+
# Final newline
160+
printf "\n"
161+
162+
# Check for crashes
163+
if grep -q "SUMMARY.*: [1-9]" "$tmpfile" 2>/dev/null; then
164+
echo ""
165+
echo "${COLOR_BOLD}Fuzzing found issues. Check crash files in the current directory.${COLOR_RESET}"
166+
return 1
167+
fi
168+
169+
return 0
170+
}
171+
172+
# Run in quiet mode (for CI/CD)
173+
run_quiet() {
174+
echo "Running fuzzer for ${MAX_TIME}s (quiet mode)..."
175+
"$FUZZER" -max_total_time="$MAX_TIME" "${FUZZER_ARGS[@]}"
176+
local status=$?
177+
178+
if [ $status -eq 0 ]; then
179+
echo "Fuzzing completed successfully."
180+
else
181+
echo "Fuzzing found issues (exit code: $status)."
182+
fi
183+
184+
return $status
185+
}
186+
187+
# Main entry point
188+
main() {
189+
# Verify fuzzer exists
190+
if [ ! -x "$FUZZER" ]; then
191+
echo "Error: Fuzzer binary '$FUZZER' not found or not executable."
192+
exit 1
193+
fi
194+
195+
echo "sse2neon Fuzzer"
196+
echo "==============="
197+
echo "Binary: $FUZZER"
198+
echo "Duration: ${MAX_TIME}s"
199+
echo "Extra args: ${FUZZER_ARGS[*]:-none}"
200+
echo ""
201+
202+
if is_ci; then
203+
run_quiet
204+
else
205+
run_with_progress
206+
fi
207+
}
208+
209+
main

0 commit comments

Comments
 (0)