Skip to content

Commit

Permalink
Add a basic fuzz testing harness for Dilithium2 (#1905)
Browse files Browse the repository at this point in the history
* Add a basic fuzz testing harness for dilithium2

Co-authored-by: Spencer Wilson <[email protected]>
Signed-off-by: Nathaniel Brough <[email protected]>

* Add basic build checks for fuzz tests

Co-authored-by: Spencer Wilson <[email protected]>
Signed-off-by: Nathaniel Brough <[email protected]>

---------

Signed-off-by: Nathaniel Brough <[email protected]>
Co-authored-by: Spencer Wilson <[email protected]>
  • Loading branch information
nathaniel-brough and SWilson4 authored Oct 18, 2024
1 parent 81b4452 commit 0310631
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 1 deletion.
34 changes: 34 additions & 0 deletions .github/workflows/basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,37 @@ jobs:
- name: Build documentation
run: ninja gen_docs
working-directory: build


fuzzbuildcheck:
name: Check that code passes a basic fuzzing build
needs: [ workflowcheck, stylecheck, upstreamcheck ]
runs-on: ubuntu-latest
container: openquantumsafe/ci-ubuntu-latest:latest
env:
SIG_NAME: dilithium_2
CC: clang
CXX: clang++
CFLAGS: -fsanitize=fuzzer-no-link,address
LDFLAGS: -fsanitize=address
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4
- name: Configure
run: |
mkdir build && \
cd build && \
cmake -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
working-directory: build

- name: Short fuzz check (30s)
run: ./tests/fuzz_test_dilithium2 -max_total_time=30
working-directory: build
16 changes: 16 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ option(OQS_PERMIT_UNSUPPORTED_ARCHITECTURE "Permit compilation on an an unsuppor
option(OQS_STRICT_WARNINGS "Enable all compiler warnings." OFF)
option(OQS_EMBEDDED_BUILD "Compile liboqs for an Embedded environment without a full standard library." OFF)

# Libfuzzer isn't supported on gcc
if('${CMAKE_C_COMPILER_ID}' STREQUAL 'Clang')
option(OQS_BUILD_FUZZ_TESTS "Build fuzz test suite" OFF)
endif()


set(OQS_OPT_TARGET auto CACHE STRING "The target microarchitecture for optimization.")

set(CMAKE_C_STANDARD 11)
Expand All @@ -39,6 +45,16 @@ set(OQS_COMPILE_BUILD_TARGET "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_HOST_SYSTEM}")
set(OQS_MINIMAL_GCC_VERSION "7.1.0")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Determine the flags for fuzzing. Use OSS-Fuzz's configuration if available, otherwise fall back to defaults.
if(DEFINED ENV{LIB_FUZZING_ENGINE})
set(FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE})
set(FUZZING_COMPILE_FLAGS "")
set(FUZZING_LINK_FLAGS "${FUZZING_ENGINE}")
else()
set(FUZZING_COMPILE_FLAGS "-fsanitize=fuzzer,address")
set(FUZZING_LINK_FLAGS "-fsanitize=fuzzer,address")
endif()

# heuristic check to see whether we're running on a RaspberryPi
if(EXISTS "/opt/vc/include/bcm_host.h")
add_definitions( -DOQS_USE_RASPBERRY_PI )
Expand Down
10 changes: 9 additions & 1 deletion CONFIGURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The following options can be passed to CMake before the build file generation pr
- [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD)
- [OQS_LIBJADE_BUILD](#OQS_LIBJADE_BUILD)
- [OQS_ENABLE_LIBJADE_KEM_ALG/OQS_ENABLE_LIBJADE_SIG_ALG](#OQS_ENABLE_LIBJADE_KEM_ALG/OQS_ENABLE_LIBJADE_SIG_ALG)
- [OQS_BUILD_FUZZ_TESTS](#OQS_BUILD_FUZZ_TESTS)

## BUILD_SHARED_LIBS

Expand Down Expand Up @@ -214,4 +215,11 @@ At the moment, libjade only supports Linux and Darwin based operating systems on

Note: `ALG` in `OQS_ENABLE_LIBJADE_KEM_ALG/OQS_ENABLE_LIBJADE_SIG_ALG` should be replaced with the specific algorithm name as demonstrated in OQS_ENABLE_KEM_ALG/OQS_ENABLE_SIG_ALG.

**Default**: `OFF` if OQS_LIBJADE_BUILD is `OFF` else unset.
**Default**: `OFF` if OQS_LIBJADE_BUILD is `OFF` else unset.

## OQS_BUILD_FUZZ_TESTS
Can be `ON` or `OFF`. When `ON` liboqs the fuzz test-suite will be enabled. This option is only available if the c compiler is set to clang i.e. `-DCMAKE_C_COMPILER=clang`.

Note: It is strongly recommended that this configuration be enabled with `CFLAGS=-fsanitize=address,fuzzer-no-link LDFLAGS=-fsanitize=address`. While fuzzing will run without these flags, enabling this instrumentation will make fuzzing performance much faster and catch [potential memory related bugs](https://clang.llvm.org/docs/AddressSanitizer.html).

**Default** `OFF`.
80 changes: 80 additions & 0 deletions docs/FUZZING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# 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
- [ ] dilithium
- [x] dilithium2
- [ ] dilithium3
- [ ] dilithium5
- [ ] falcon
- [ ] mayo
- [ ] ml_dsa
- [ ] sphincs
- [ ] 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;

```bash
mkdir build && cd build
cmake -GNinja .. -DOQS_BUILD_FUZZ_TESTS=ON
ninja -j$(nproc)
```

You'll now be able to run a fuzz test e.g.
```bash
./tests/fuzz_test_dilithium2
#9764 NEW cov: 4 ft: 708 corp: 100/318b lim: 43 exec/s: 9764 rss: 362Mb L: 41/41 MS: 4 EraseBytes-InsertRepeatedBytes-CMP-ChangeBit- DE: "\0004m\372"-
...
```
The fuzzer will run indefinetely 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.

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

## 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;

```
export CFLAGS=-fsanitize=fuzzer-no-link
```

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.

```
export CFLAGS=-fsanitize=fuzzer-no-link,address
export LDFLAGS=-fsanitize=address
```

Then rerun cmake as normal i.e.
```bash
mkdir build && cd build
cmake -GNinja .. -DOQS_BUILD_FUZZ_TESTS=ON
ninja -j$(nproc)
```
9 changes: 9 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem)
add_executable(example_sig example_sig.c)
target_link_libraries(example_sig PRIVATE ${TEST_DEPS})

if(OQS_BUILD_FUZZ_TESTS AND '${CMAKE_C_COMPILER_ID}' STREQUAL 'Clang')
add_executable(fuzz_test_dilithium2 fuzz_test_dilithium2.c)
target_link_libraries(fuzz_test_dilithium2 PRIVATE ${TEST_DEPS})
set_target_properties(fuzz_test_dilithium2 PROPERTIES
COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}"
LINK_FLAGS "${FUZZING_LINK_FLAGS}"
)
endif()

# Stateful SIG API tests
add_executable(example_sig_stfl example_sig_stfl.c)
target_link_libraries(example_sig_stfl PRIVATE ${TEST_DEPS})
Expand Down
97 changes: 97 additions & 0 deletions tests/fuzz_test_dilithium2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* fuzz_test_sig.c
*
* Minimal fuzz test for liboqs.
*
* SPDX-License-Identifier: MIT
*/

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>

#include <oqs/oqs.h>

void cleanup_heap(uint8_t *public_key, uint8_t *secret_key,
uint8_t *signature,
OQS_SIG *sig);

static OQS_STATUS fuzz_dilithium_2(const uint8_t *message, size_t message_len) {

#ifdef OQS_ENABLE_SIG_dilithium_2

OQS_SIG *sig = NULL;
uint8_t *public_key = NULL;
uint8_t *secret_key = NULL;
uint8_t *signature = NULL;
size_t signature_len;
OQS_STATUS rc;

sig = OQS_SIG_new(OQS_SIG_alg_dilithium_2);
if (sig == NULL) {
printf("[fuzz_test_dilithium_2] OQS_SIG_alg_dilithium_2 was not enabled at compile-time.\n");
return OQS_ERROR;
}

public_key = malloc(sig->length_public_key);
secret_key = malloc(sig->length_secret_key);
signature = malloc(sig->length_signature);
if ((public_key == NULL) || (secret_key == NULL) || (message == NULL) || (signature == NULL)) {
fprintf(stderr, "ERROR: malloc failed!\n");
cleanup_heap(public_key, secret_key, signature, sig);
return OQS_ERROR;
}

rc = OQS_SIG_keypair(sig, public_key, secret_key);
if (rc != OQS_SUCCESS) {
fprintf(stderr, "ERROR: OQS_SIG_keypair failed!\n");
cleanup_heap(public_key, secret_key, signature, sig);
return OQS_ERROR;
}
rc = OQS_SIG_sign(sig, signature, &signature_len, message, message_len, secret_key);
if (rc != OQS_SUCCESS) {
fprintf(stderr, "ERROR: OQS_SIG_sign failed!\n");
cleanup_heap(public_key, secret_key, signature, sig);
return OQS_ERROR;
}
rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key);
if (rc != OQS_SUCCESS) {
fprintf(stderr, "ERROR: OQS_SIG_verify failed!\n");
cleanup_heap(public_key, secret_key, signature, sig);
exit(1);
}

cleanup_heap(public_key, secret_key, signature, sig);
return OQS_SUCCESS; // success
#else

printf("[fuzz_test_dilithium_2] OQS_SIG_dilithium_2 was not enabled at compile-time.\n");
return OQS_SUCCESS;

#endif
}

void cleanup_heap(uint8_t *public_key, uint8_t *secret_key,
uint8_t *signature,
OQS_SIG *sig) {
if (sig != NULL) {
OQS_MEM_secure_free(secret_key, sig->length_secret_key);
}
OQS_MEM_insecure_free(public_key);
OQS_MEM_insecure_free(signature);
OQS_SIG_free(sig);
}

int LLVMFuzzerTestOneInput(const char *data, size_t size) {
OQS_init();
if (OQS_ERROR == fuzz_dilithium_2((const uint8_t *)data, size)) {
// If we get an error prune testcase from corpus.
return -1;
}
OQS_destroy();
return 0;
}


0 comments on commit 0310631

Please sign in to comment.