Skip to content

Commit c0bb5bd

Browse files
committed
Add public-input fuzzing CI
Signed-off-by: Scott Arciszewski <scott.arciszewski@trailofbits.com>
1 parent aa294f5 commit c0bb5bd

949 files changed

Lines changed: 1535 additions & 968 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.CMake/alg_support.cmake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,6 +1330,12 @@ cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h10_w8 "" ON "OQS_E
13301330
cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h15_w8 "" ON "OQS_ENABLE_SIG_STFL_LMS" OFF)
13311331
cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h20_w8 "" ON "OQS_ENABLE_SIG_STFL_LMS" OFF)
13321332

1333+
# The main minimal-build filter runs before STFL options exist. Run it again
1334+
# now so unrequested STFL variants are disabled as well.
1335+
if(NOT ((OQS_MINIMAL_BUILD STREQUAL "") OR (OQS_MINIMAL_BUILD STREQUAL "OFF")))
1336+
filter_algs("${OQS_MINIMAL_BUILD}")
1337+
endif()
1338+
13331339
option(OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN "Enable stateful key and signature generation for research and experimentation" OFF)
13341340
cmake_dependent_option(OQS_ALLOW_STFL_KEY_AND_SIG_GEN "" ON "OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN" OFF)
13351341

.github/workflows/basic.yml

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -125,41 +125,12 @@ jobs:
125125
./example_sig
126126
working-directory: ${{ env.RANDOM_BUILD_DIR }}
127127

128-
fuzzbuildcheck:
129-
name: Check that code passes a basic fuzzing build
128+
fuzz-smoke:
129+
name: Check public-input fuzz harnesses
130130
needs: [workflowcheck, stylecheck, upstreamcheck]
131-
runs-on: ubuntu-latest
132-
container: openquantumsafe/ci-ubuntu-latest:latest
133-
env:
134-
SIG_NAME: ml_dsa_44
135-
CC: clang
136-
CXX: clang++
137-
CFLAGS: -fsanitize=fuzzer-no-link,address
138-
LDFLAGS: -fsanitize=address
139-
steps:
140-
- name: Create random build folder
141-
run: tmp_build=$(mktemp -d) && echo "RANDOM_BUILD_DIR=$tmp_build" >> $GITHUB_ENV
142-
- name: Checkout code
143-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v4
144-
- name: Configure
145-
run: |
146-
cmake \
147-
-B ${{ env.RANDOM_BUILD_DIR }} \
148-
-GNinja \
149-
-DOQS_STRICT_WARNINGS=ON \
150-
-DOQS_BUILD_FUZZ_TESTS=ON \
151-
-DOQS_MINIMAL_BUILD="SIG_$SIG_NAME" \
152-
--warn-uninitialized . > config.log 2>&1 && \
153-
cat config.log && \
154-
cmake -LA -N . && \
155-
! (grep -i "uninitialized variable" config.log)
156-
- name: Build code
157-
run: ninja fuzz_test_sig
158-
working-directory: ${{ env.RANDOM_BUILD_DIR }}
159-
160-
- name: Short fuzz check (30s)
161-
run: ./tests/fuzz_test_sig -max_total_time=30
162-
working-directory: ${{ env.RANDOM_BUILD_DIR }}
131+
uses: ./.github/workflows/fuzz.yml
132+
with:
133+
profile: smoke
163134

164135
nixflakecheck:
165136
name: Check that Nix flake has correct syntax and can build

.github/workflows/fuzz.yml

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
name: Public-input fuzzing
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
profile:
10+
description: Fuzzing profile to run
11+
required: true
12+
type: string
13+
workflow_dispatch:
14+
inputs:
15+
profile:
16+
description: Fuzzing profile to run
17+
required: true
18+
default: smoke
19+
type: choice
20+
options:
21+
- smoke
22+
- full
23+
24+
jobs:
25+
fuzz:
26+
name: ${{ inputs.profile }} public-input fuzzing
27+
runs-on: ubuntu-latest
28+
container: openquantumsafe/ci-ubuntu-latest:latest
29+
env:
30+
CC: clang
31+
CXX: clang++
32+
CFLAGS: -fsanitize=fuzzer-no-link,address
33+
LDFLAGS: -fsanitize=address
34+
steps:
35+
- name: Create random build folder
36+
run: tmp_build=$(mktemp -d) && echo "RANDOM_BUILD_DIR=$tmp_build" >> "$GITHUB_ENV"
37+
- name: Checkout code
38+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v4
39+
- name: Validate corpus
40+
run: python3 tests/fuzz/generate_corpus.py --check
41+
- name: Configure smoke profile
42+
if: ${{ inputs.profile == 'smoke' }}
43+
run: |
44+
echo "FUZZ_TIMEOUT=30" >> "$GITHUB_ENV"
45+
echo "FUZZ_INPUT_TIMEOUT=10" >> "$GITHUB_ENV"
46+
cmake \
47+
-B "${{ env.RANDOM_BUILD_DIR }}" \
48+
-GNinja \
49+
-DOQS_STRICT_WARNINGS=ON \
50+
-DOQS_BUILD_FUZZ_TESTS=ON \
51+
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
52+
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
53+
-DOQS_ENABLE_SIG_STFL_LMS=ON \
54+
-DOQS_MINIMAL_BUILD="KEM_ml_kem_768;KEM_bike_l1;SIG_ml_dsa_44;SIG_falcon_512;SIG_STFL_xmss_shake128_h10;SIG_STFL_lms_sha256_h5_w2" \
55+
--warn-uninitialized . > config.log 2>&1
56+
cat config.log
57+
cmake -LA -N "${{ env.RANDOM_BUILD_DIR }}"
58+
! grep -i "uninitialized variable" config.log
59+
- name: Configure full profile
60+
if: ${{ inputs.profile == 'full' }}
61+
run: |
62+
echo "FUZZ_TIMEOUT=600" >> "$GITHUB_ENV"
63+
echo "FUZZ_INPUT_TIMEOUT=60" >> "$GITHUB_ENV"
64+
cmake \
65+
-B "${{ env.RANDOM_BUILD_DIR }}" \
66+
-GNinja \
67+
-DOQS_STRICT_WARNINGS=ON \
68+
-DOQS_BUILD_FUZZ_TESTS=ON \
69+
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
70+
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
71+
-DOQS_ENABLE_SIG_STFL_LMS=ON \
72+
--warn-uninitialized . > config.log 2>&1
73+
cat config.log
74+
cmake -LA -N "${{ env.RANDOM_BUILD_DIR }}"
75+
! grep -i "uninitialized variable" config.log
76+
- name: Reject unknown profile
77+
if: ${{ inputs.profile != 'smoke' && inputs.profile != 'full' }}
78+
run: |
79+
echo "unknown fuzzing profile: ${{ inputs.profile }}" >&2
80+
exit 1
81+
- name: Build fuzz targets
82+
working-directory: ${{ env.RANDOM_BUILD_DIR }}
83+
run: |
84+
ninja \
85+
fuzz_test_kem_encaps \
86+
fuzz_test_kem_decaps \
87+
fuzz_test_sig_verify \
88+
fuzz_test_sig_stfl_verify \
89+
fuzz_test_sig_stfl_deserialize
90+
- name: Run fuzz targets
91+
run: |
92+
mkdir -p "$GITHUB_WORKSPACE/fuzz-artifacts"
93+
for target in \
94+
fuzz_test_kem_encaps \
95+
fuzz_test_kem_decaps \
96+
fuzz_test_sig_verify \
97+
fuzz_test_sig_stfl_verify \
98+
fuzz_test_sig_stfl_deserialize; do
99+
"${{ env.RANDOM_BUILD_DIR }}/tests/$target" \
100+
"$GITHUB_WORKSPACE/tests/fuzz/corpus/$target" \
101+
-artifact_prefix="$GITHUB_WORKSPACE/fuzz-artifacts/$target-" \
102+
-max_total_time="$FUZZ_TIMEOUT" \
103+
-timeout="$FUZZ_INPUT_TIMEOUT" \
104+
-max_len=65536 \
105+
-keep_seed=1 \
106+
-verbosity=0 \
107+
-print_final_stats=1
108+
done
109+
- name: Upload crash artifacts
110+
if: failure()
111+
uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4
112+
with:
113+
name: fuzz-artifacts-${{ inputs.profile }}
114+
path: fuzz-artifacts/
115+
if-no-files-found: ignore

.github/workflows/weekly.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ jobs:
3030
sig-continuous-benchmarking:
3131
uses: ./.github/workflows/sig-bench.yml
3232
permissions:
33-
contents: write
33+
contents: write
34+
35+
fuzz-full:
36+
uses: ./.github/workflows/fuzz.yml
37+
with:
38+
profile: full

docs/FUZZING.md

Lines changed: 78 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,94 @@
11
# Fuzzing
22

3-
Fuzz testing is an automated software testing method that injects invalid,
4-
malformed, or unexpected inputs to reveal defects and vulnerabilities. A fuzzing
5-
tool monitors the system for exceptions like crashes, information leakage, or
6-
errors, helping developers identify and fix bugs and security loopholes.
7-
8-
## Current state of fuzzing in liboqs
9-
- [ ] kem
10-
- [ ] bike
11-
- [ ] classic_mceliece
12-
- [ ] frodokem
13-
- [ ] hqc
14-
- [ ] kyber
15-
- [ ] ml_kem
16-
- [ ] ntruprime
17-
- [ ] sig
18-
- [x] falcon
19-
- [x] mayo
20-
- [x] ml_dsa
21-
- [ ] sig_stfl
22-
- [ ] lms
23-
- [ ] sig_stfl
24-
- [ ] xmss
25-
26-
## Building and running fuzz tests
27-
28-
Building fuzz tests is very similar to building normally with some optional
29-
steps to target different types of bugs. The most basic ways to build the
30-
fuzz tests is as follows;
3+
liboqs fuzz targets focus on public inputs that can reach scheme-specific
4+
decoders or deserializers before authentication succeeds. The targets start
5+
from a valid cached baseline for each enabled algorithm and then mutate the
6+
attacker-controlled fields, which keeps the fuzzer in deep parsing and
7+
verification paths instead of spending most iterations on early rejects.
318

32-
```bash
33-
mkdir build && cd build
34-
cmake -GNinja -DOQS_BUILD_FUZZ_TESTS=ON ..
35-
ninja
36-
```
9+
## Targets
3710

38-
`OQS_BUILD_FUZZ_TESTS` will build two test binaries: `tests/fuzz_test_sig` and `tests/fuzz_test_kem`.
11+
| Target | Public API surface | Mutated attacker input |
12+
| --- | --- | --- |
13+
| `fuzz_test_kem_encaps` | `OQS_KEM_encaps`, `OQS_KEM_encaps_derand` | KEM public keys |
14+
| `fuzz_test_kem_decaps` | `OQS_KEM_decaps` | KEM ciphertexts |
15+
| `fuzz_test_sig_verify` | `OQS_SIG_verify`, `OQS_SIG_verify_with_ctx_str` | Public keys, signatures, messages, contexts |
16+
| `fuzz_test_sig_stfl_verify` | `OQS_SIG_STFL_verify` | Stateful public keys, signatures, messages |
17+
| `fuzz_test_sig_stfl_deserialize` | `OQS_SIG_STFL_SECRET_KEY_deserialize` and round-trip serialization | Serialized stateful secret keys |
3918

40-
The fuzzer will run indefinitely or;
41-
- until it finds a bug and crashes,
42-
- you manually stop the fuzzer i.e. CTRL-C
43-
- you set a timeout using the command line.
19+
`fuzz_test_sig_stfl_deserialize` is only built when
20+
`OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON`, because the
21+
target relies on the full stateful object layout and persistence callback path
22+
available in key/sign builds.
4423

45-
For more details on the available command line args please consult the [libfuzzer docs](https://llvm.org/docs/LibFuzzer.html).
24+
## Building
4625

47-
## Sanitizers
48-
It is a common pattern to combine fuzzing with various sanitizers to catch different bugs.
49-
One of the simpler sanitizers is the fuzzing sanitizer, which will instrument the code
50-
for coverage driven fuzzing. To enable this simply add this to your environment variables
51-
before configuring cmake;
26+
Use Clang and sanitizer instrumentation for local runs:
5227

53-
```
54-
export CFLAGS=-fsanitize=fuzzer-no-link
28+
```bash
29+
export CC=clang
30+
export CXX=clang++
31+
export CFLAGS=-fsanitize=fuzzer-no-link,address
32+
export LDFLAGS=-fsanitize=address
33+
34+
cmake -S . -B build/fuzz -GNinja \
35+
-DCMAKE_BUILD_TYPE=Debug \
36+
-DOQS_BUILD_FUZZ_TESTS=ON \
37+
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
38+
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
39+
-DOQS_ENABLE_SIG_STFL_LMS=ON
40+
ninja -C build/fuzz \
41+
fuzz_test_kem_encaps \
42+
fuzz_test_kem_decaps \
43+
fuzz_test_sig_verify \
44+
fuzz_test_sig_stfl_verify \
45+
fuzz_test_sig_stfl_deserialize
5546
```
5647

57-
It is common to combine the fuzzer sanitizer with either the [address](https://clang.llvm.org/docs/AddressSanitizer.html)
58-
or the [undefined behaviour sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html). To
59-
add these simply add the relevant flags to BOTH the CFLAGS and LDFLAGS e.g.
48+
For focused local work, prefer a minimal build:
6049

50+
```bash
51+
cmake -S . -B build/fuzz-minimal -GNinja \
52+
-DCMAKE_BUILD_TYPE=Debug \
53+
-DOQS_BUILD_FUZZ_TESTS=ON \
54+
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
55+
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
56+
-DOQS_ENABLE_SIG_STFL_LMS=ON \
57+
'-DOQS_MINIMAL_BUILD=KEM_ml_kem_768;KEM_bike_l1;SIG_ml_dsa_44;SIG_falcon_512;SIG_STFL_xmss_shake128_h10;SIG_STFL_lms_sha256_h5_w2'
6158
```
62-
export CFLAGS=-fsanitize=fuzzer-no-link,address
63-
export LDFLAGS=-fsanitize=address
59+
60+
## Corpus
61+
62+
The committed seed corpus is generated from the algorithm registries in
63+
`src/kem/kem.c`, `src/sig/sig.c`, and `src/sig_stfl/sig_stfl.c`. Each seed is a
64+
small binary header selecting one algorithm and API mode; the target then
65+
reconstructs a valid baseline in memory.
66+
67+
Regenerate or validate the corpus after algorithm registry changes:
68+
69+
```bash
70+
python3 tests/fuzz/generate_corpus.py --write
71+
python3 tests/fuzz/generate_corpus.py --check
6472
```
6573

66-
Then rerun cmake as normal i.e.
74+
## Running
75+
76+
Pass the matching corpus directory to libFuzzer and use an artifact prefix so a
77+
crashing input is preserved:
78+
6779
```bash
68-
mkdir build && cd build
69-
cmake -GNinja .. -DOQS_BUILD_FUZZ_TESTS=ON
70-
ninja -j$(nproc)
80+
mkdir -p fuzz-artifacts
81+
build/fuzz/tests/fuzz_test_sig_verify \
82+
tests/fuzz/corpus/fuzz_test_sig_verify \
83+
-max_total_time=300 \
84+
-timeout=10 \
85+
-max_len=65536 \
86+
-keep_seed=1 \
87+
-verbosity=0 \
88+
-print_final_stats=1 \
89+
-artifact_prefix=fuzz-artifacts/fuzz_test_sig_verify-
7190
```
91+
92+
The PR workflow runs a short sanitizer smoke profile over representative KEM,
93+
signature, XMSS, and LMS algorithms. The weekly workflow enables all default
94+
algorithms plus both stateful families and runs every target for longer.

src/sig_stfl/lms/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ set(SRCS
3737

3838

3939
add_library(lms OBJECT ${SRCS})
40-
target_include_directories(lms PRIVATE ${LIBOQS_ROOT_DIR}/include)
40+
target_include_directories(lms PRIVATE ${PROJECT_BINARY_DIR}/include)
4141
set(_LMS_OBJS ${_LMS_OBJS} $<TARGET_OBJECTS:lms>)
4242
set(LMS_OBJS ${_LMS_OBJS} PARENT_SCOPE)
4343

4444

45-

0 commit comments

Comments
 (0)