Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .CMake/alg_support.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,12 @@ cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h10_w8 "" ON "OQS_E
cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h15_w8 "" ON "OQS_ENABLE_SIG_STFL_LMS" OFF)
cmake_dependent_option(OQS_ENABLE_SIG_STFL_lms_sha256_h20_w8_h20_w8 "" ON "OQS_ENABLE_SIG_STFL_LMS" OFF)

# The main minimal-build filter runs before STFL options exist. Run it again
# now so unrequested STFL variants are disabled as well.
if(NOT ((OQS_MINIMAL_BUILD STREQUAL "") OR (OQS_MINIMAL_BUILD STREQUAL "OFF")))
filter_algs("${OQS_MINIMAL_BUILD}")
endif()

option(OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN "Enable stateful key and signature generation for research and experimentation" OFF)
cmake_dependent_option(OQS_ALLOW_STFL_KEY_AND_SIG_GEN "" ON "OQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN" OFF)

Expand Down
39 changes: 5 additions & 34 deletions .github/workflows/basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,41 +125,12 @@ jobs:
./example_sig
working-directory: ${{ env.RANDOM_BUILD_DIR }}

fuzzbuildcheck:
name: Check that code passes a basic fuzzing build
fuzz-smoke:
name: Check public-input fuzz harnesses
needs: [workflowcheck, stylecheck, upstreamcheck]
runs-on: ubuntu-latest
container: openquantumsafe/ci-ubuntu-latest:latest
env:
SIG_NAME: ml_dsa_44
CC: clang
CXX: clang++
CFLAGS: -fsanitize=fuzzer-no-link,address
LDFLAGS: -fsanitize=address
steps:
- name: Create random build folder
run: tmp_build=$(mktemp -d) && echo "RANDOM_BUILD_DIR=$tmp_build" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v4
- name: Configure
run: |
cmake \
-B ${{ env.RANDOM_BUILD_DIR }} \
-GNinja \
-DOQS_STRICT_WARNINGS=ON \
-DOQS_BUILD_FUZZ_TESTS=ON \
-DOQS_MINIMAL_BUILD="SIG_$SIG_NAME" \
--warn-uninitialized . > config.log 2>&1 && \
cat config.log && \
cmake -LA -N . && \
! (grep -i "uninitialized variable" config.log)
- name: Build code
run: ninja fuzz_test_sig
working-directory: ${{ env.RANDOM_BUILD_DIR }}

- name: Short fuzz check (30s)
run: ./tests/fuzz_test_sig -max_total_time=30
working-directory: ${{ env.RANDOM_BUILD_DIR }}
uses: ./.github/workflows/fuzz.yml
with:
profile: smoke

nixflakecheck:
name: Check that Nix flake has correct syntax and can build
Expand Down
115 changes: 115 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Public-input fuzzing

permissions:
contents: read

on:
workflow_call:
inputs:
profile:
description: Fuzzing profile to run
required: true
type: string
workflow_dispatch:
inputs:
profile:
description: Fuzzing profile to run
required: true
default: smoke
type: choice
options:
- smoke
- full

jobs:
fuzz:
name: ${{ inputs.profile }} public-input fuzzing
runs-on: ubuntu-latest
container: openquantumsafe/ci-ubuntu-latest:latest
env:
CC: clang
CXX: clang++
CFLAGS: -fsanitize=fuzzer-no-link,address
LDFLAGS: -fsanitize=address
steps:
- name: Create random build folder
run: tmp_build=$(mktemp -d) && echo "RANDOM_BUILD_DIR=$tmp_build" >> "$GITHUB_ENV"
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # pin@v4
- name: Validate corpus
run: python3 tests/fuzz/generate_corpus.py --check
- name: Configure smoke profile
if: ${{ inputs.profile == 'smoke' }}
run: |
echo "FUZZ_TIMEOUT=30" >> "$GITHUB_ENV"
echo "FUZZ_INPUT_TIMEOUT=10" >> "$GITHUB_ENV"
cmake \
-B "${{ env.RANDOM_BUILD_DIR }}" \
-GNinja \
-DOQS_STRICT_WARNINGS=ON \
-DOQS_BUILD_FUZZ_TESTS=ON \
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
-DOQS_ENABLE_SIG_STFL_LMS=ON \
-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" \
--warn-uninitialized . > config.log 2>&1
cat config.log
cmake -LA -N "${{ env.RANDOM_BUILD_DIR }}"
! grep -i "uninitialized variable" config.log
- name: Configure full profile
if: ${{ inputs.profile == 'full' }}
run: |
echo "FUZZ_TIMEOUT=600" >> "$GITHUB_ENV"
echo "FUZZ_INPUT_TIMEOUT=60" >> "$GITHUB_ENV"
cmake \
-B "${{ env.RANDOM_BUILD_DIR }}" \
-GNinja \
-DOQS_STRICT_WARNINGS=ON \
-DOQS_BUILD_FUZZ_TESTS=ON \
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
-DOQS_ENABLE_SIG_STFL_LMS=ON \
--warn-uninitialized . > config.log 2>&1
cat config.log
cmake -LA -N "${{ env.RANDOM_BUILD_DIR }}"
! grep -i "uninitialized variable" config.log
- name: Reject unknown profile
if: ${{ inputs.profile != 'smoke' && inputs.profile != 'full' }}
run: |
echo "unknown fuzzing profile: ${{ inputs.profile }}" >&2
exit 1
- name: Build fuzz targets
working-directory: ${{ env.RANDOM_BUILD_DIR }}
run: |
ninja \
fuzz_test_kem_encaps \
fuzz_test_kem_decaps \
fuzz_test_sig_verify \
fuzz_test_sig_stfl_verify \
fuzz_test_sig_stfl_deserialize
- name: Run fuzz targets
run: |
mkdir -p "$GITHUB_WORKSPACE/fuzz-artifacts"
for target in \
fuzz_test_kem_encaps \
fuzz_test_kem_decaps \
fuzz_test_sig_verify \
fuzz_test_sig_stfl_verify \
fuzz_test_sig_stfl_deserialize; do
"${{ env.RANDOM_BUILD_DIR }}/tests/$target" \
"$GITHUB_WORKSPACE/tests/fuzz/corpus/$target" \
-artifact_prefix="$GITHUB_WORKSPACE/fuzz-artifacts/$target-" \
-max_total_time="$FUZZ_TIMEOUT" \
-timeout="$FUZZ_INPUT_TIMEOUT" \
-max_len=65536 \
-keep_seed=1 \
-verbosity=0 \
-print_final_stats=1
done
- name: Upload crash artifacts
if: failure()
uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4
with:
name: fuzz-artifacts-${{ inputs.profile }}
path: fuzz-artifacts/
if-no-files-found: ignore
7 changes: 6 additions & 1 deletion .github/workflows/weekly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ jobs:
sig-continuous-benchmarking:
uses: ./.github/workflows/sig-bench.yml
permissions:
contents: write
contents: write

fuzz-full:
uses: ./.github/workflows/fuzz.yml
with:
profile: full
133 changes: 78 additions & 55 deletions docs/FUZZING.md
Original file line number Diff line number Diff line change
@@ -1,71 +1,94 @@
# Fuzzing

Fuzz testing is an automated software testing method that injects invalid,
malformed, or unexpected inputs to reveal defects and vulnerabilities. A fuzzing
tool monitors the system for exceptions like crashes, information leakage, or
errors, helping developers identify and fix bugs and security loopholes.

## Current state of fuzzing in liboqs
- [ ] kem
- [ ] bike
- [ ] classic_mceliece
- [ ] frodokem
- [ ] hqc
- [ ] kyber
- [ ] ml_kem
- [ ] ntruprime
- [ ] sig
- [x] falcon
- [x] mayo
- [x] ml_dsa
- [ ] sig_stfl
- [ ] lms
- [ ] sig_stfl
- [ ] xmss

## Building and running fuzz tests

Building fuzz tests is very similar to building normally with some optional
steps to target different types of bugs. The most basic ways to build the
fuzz tests is as follows;
liboqs fuzz targets focus on public inputs that can reach scheme-specific
decoders or deserializers before authentication succeeds. The targets start
from a valid cached baseline for each enabled algorithm and then mutate the
attacker-controlled fields, which keeps the fuzzer in deep parsing and
verification paths instead of spending most iterations on early rejects.

```bash
mkdir build && cd build
cmake -GNinja -DOQS_BUILD_FUZZ_TESTS=ON ..
ninja
```
## Targets

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

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

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

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

```
export CFLAGS=-fsanitize=fuzzer-no-link
```bash
export CC=clang
export CXX=clang++
export CFLAGS=-fsanitize=fuzzer-no-link,address
export LDFLAGS=-fsanitize=address

cmake -S . -B build/fuzz -GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DOQS_BUILD_FUZZ_TESTS=ON \
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
-DOQS_ENABLE_SIG_STFL_LMS=ON
ninja -C build/fuzz \
fuzz_test_kem_encaps \
fuzz_test_kem_decaps \
fuzz_test_sig_verify \
fuzz_test_sig_stfl_verify \
fuzz_test_sig_stfl_deserialize
```

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

```bash
cmake -S . -B build/fuzz-minimal -GNinja \
-DCMAKE_BUILD_TYPE=Debug \
-DOQS_BUILD_FUZZ_TESTS=ON \
-DOQS_HAZARDOUS_EXPERIMENTAL_ENABLE_SIG_STFL_KEY_SIG_GEN=ON \
-DOQS_ENABLE_SIG_STFL_XMSS=ON \
-DOQS_ENABLE_SIG_STFL_LMS=ON \
'-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'
```
export CFLAGS=-fsanitize=fuzzer-no-link,address
export LDFLAGS=-fsanitize=address

## Corpus

The committed seed corpus is generated from the algorithm registries in
`src/kem/kem.c`, `src/sig/sig.c`, and `src/sig_stfl/sig_stfl.c`. Each seed is a
small binary header selecting one algorithm and API mode; the target then
reconstructs a valid baseline in memory.

Regenerate or validate the corpus after algorithm registry changes:

```bash
python3 tests/fuzz/generate_corpus.py --write
python3 tests/fuzz/generate_corpus.py --check
```

Then rerun cmake as normal i.e.
## Running

Pass the matching corpus directory to libFuzzer and use an artifact prefix so a
crashing input is preserved:

```bash
mkdir build && cd build
cmake -GNinja .. -DOQS_BUILD_FUZZ_TESTS=ON
ninja -j$(nproc)
mkdir -p fuzz-artifacts
build/fuzz/tests/fuzz_test_sig_verify \
tests/fuzz/corpus/fuzz_test_sig_verify \
-max_total_time=300 \
-timeout=10 \
-max_len=65536 \
-keep_seed=1 \
-verbosity=0 \
-print_final_stats=1 \
-artifact_prefix=fuzz-artifacts/fuzz_test_sig_verify-
```

The PR workflow runs a short sanitizer smoke profile over representative KEM,
signature, XMSS, and LMS algorithms. The weekly workflow enables all default
algorithms plus both stateful families and runs every target for longer.
3 changes: 1 addition & 2 deletions src/sig_stfl/lms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ set(SRCS


add_library(lms OBJECT ${SRCS})
target_include_directories(lms PRIVATE ${LIBOQS_ROOT_DIR}/include)
target_include_directories(lms PRIVATE ${PROJECT_BINARY_DIR}/include)
set(_LMS_OBJS ${_LMS_OBJS} $<TARGET_OBJECTS:lms>)
set(LMS_OBJS ${_LMS_OBJS} PARENT_SCOPE)



Loading
Loading