diff --git a/CMakeLists.txt b/CMakeLists.txt index 6973fbc5c..b58b6ce9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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 ) diff --git a/CONFIGURE.md b/CONFIGURE.md index becb4a883..1d00565f3 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -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 @@ -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. \ No newline at end of file +**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`. diff --git a/docs/FUZZING.md b/docs/FUZZING.md new file mode 100644 index 000000000..d80802677 --- /dev/null +++ b/docs/FUZZING.md @@ -0,0 +1,79 @@ +# 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 + - [ ] dilithium1 + - [x] dilithium2 + - [ ] 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) +``` diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index eb297a804..5d45ecb6f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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}) diff --git a/tests/fuzz_test_dilithium2.c b/tests/fuzz_test_dilithium2.c new file mode 100644 index 000000000..ae4cffecd --- /dev/null +++ b/tests/fuzz_test_dilithium2.c @@ -0,0 +1,97 @@ +/* + * fuzz_test_sig.c + * + * Minimal fuzz test for liboqs. + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include + +#include + +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("[example_heap] 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("[example_heap] 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; +} + +