diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..22f8347 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,79 @@ +name: CI Fuzz +# Set a CI/CD variable called "CI_SENSE_API_TOKEN" with an API token +# generated in CI Fuzz web interface and a variable called "CI_FUZZ_DOWNLOAD_TOKEN" +# with a download token from https://downloads.code-intelligence.com. +# To download the CI Fuzz maven extension or gradle plugin set the secrets +# MAVEN_REGISTRY_USERNAME and MAVEN_REGISTRY_PASSWORD with the credentials +# from https://downloads.code-intelligence.com. + +on: + workflow_dispatch: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + # Timeout until the pipeline is marked as 'success' + # if during that time no failing findings are found. + TIMEOUT: "5m" + # Minimum severity for findings that causes the pipeline to fail. + # Findings with lower severity are still reported but do not fail + # the pipeline. + # Possible values: 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL' + MIN_FINDINGS_SEVERITY: LOW + # The CI Sense URL. + CI_SENSE_HTTP_URL: https://app.code-intelligence.com + CI_SENSE_GRPC_URL: grpc.code-intelligence.com:443 + # The CI Sense project name. + PROJECT: prj-Rp8lKbLPrG2Z + # Directory in which the repository will be cloned. + CHECKOUT_DIR: checkout-dir/ +jobs: + fuzz_tests: + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout Repository + uses: actions/checkout@v2 + with: + path: ${{ env.CHECKOUT_DIR }} + # Uncomment to configure access to CI Fuzz maven repository. + # - uses: s4u/maven-settings-action@v2.8.0 + # with: + # servers: '[{"id": "code-intelligence", "username": "${{ secrets.MAVEN_REGISTRY_USERNAME }}", "password": "${{ secrets.MAVEN_REGISTRY_PASSWORD }}"}]' + - id: install-cifuzz + name: Install cifuzz + uses: CodeIntelligenceTesting/github-actions/install-cifuzz@v6 + with: + download_token: ${{ secrets.CI_FUZZ_DOWNLOAD_TOKEN }} + version: 'latest' + - id: run-fuzz-tests + name: Run Fuzz Tests + uses: CodeIntelligenceTesting/github-actions/run-fuzz-tests@v6 + with: + ci_sense_api_token: ${{ secrets.CI_SENSE_API_TOKEN }} + project_name: ${{ env.PROJECT }} + repository_dir: ${{ env.CHECKOUT_DIR }} + timeout: ${{ env.TIMEOUT }} + min_findings_severity: ${{ env.MIN_FINDINGS_SEVERITY }} + ci_sense_http_url: ${{ env.CI_SENSE_HTTP_URL }} + - id: save-results + name: Save Fuzz Test Results + uses: CodeIntelligenceTesting/github-actions/save-results@v6 + if: ${{ success() || failure() }} + with: + ci_sense_api_token: ${{ secrets.CI_SENSE_API_TOKEN }} + ci_sense_http_url: ${{ env.CI_SENSE_HTTP_URL }} + ci_sense_grpc_url: ${{ env.CI_SENSE_GRPC_URL }} + project_name: ${{ env.PROJECT }} + started_run: ${{ steps.run-fuzz-tests.outputs.started_run }} + - id: upload-artifact + uses: actions/upload-artifact@v2 + if: ${{ (success() || failure()) }} + with: + name: ci_fuzz_results + path: | + findings.json + coverage.json + web_app_address.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dc9639..5df2020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,8 @@ set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/external) enable_testing() include(googletest) +find_package(cifuzz NO_SYSTEM_ENVIRONMENT_PATH) +enable_fuzz_testing() + add_subdirectory(src/explore_me) -add_subdirectory(src/automotive) \ No newline at end of file +add_subdirectory(src/automotive) diff --git a/CMakeUserPresets.json b/CMakeUserPresets.json new file mode 100644 index 0000000..fad5c91 --- /dev/null +++ b/CMakeUserPresets.json @@ -0,0 +1,100 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "cifuzz (Coverage)", + "displayName": "cifuzz (Coverage)", + "binaryDir": "${sourceDir}/.cifuzz-build/replayer/gcov", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CIFUZZ_ENGINE": "replayer", + "CIFUZZ_SANITIZERS": "gcov", + "CIFUZZ_TESTING": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_RPATH_USE_ORIGIN": { + "type": "BOOL", + "value": "ON" + } + } + }, + { + "name": "cifuzz (Fuzzing)", + "displayName": "cifuzz (Fuzzing)", + "binaryDir": "${sourceDir}/.cifuzz-build/libfuzzer/address+undefined", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CIFUZZ_ENGINE": "libfuzzer", + "CIFUZZ_SANITIZERS": "address;undefined", + "CIFUZZ_TESTING": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_RPATH_USE_ORIGIN": { + "type": "BOOL", + "value": "ON" + } + }, + "environment": { + "CC": "clang", + "CXX": "clang++" + } + }, + { + "name": "cifuzz (Regression Test)", + "displayName": "cifuzz (Regression Test)", + "binaryDir": "${sourceDir}/.cifuzz-build/replayer/address+undefined", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CIFUZZ_ENGINE": "replayer", + "CIFUZZ_SANITIZERS": "address;undefined", + "CIFUZZ_TESTING": { + "type": "BOOL", + "value": "ON" + }, + "CMAKE_BUILD_RPATH_USE_ORIGIN": { + "type": "BOOL", + "value": "ON" + } + } + } + ], + "buildPresets": [ + { + "name": "cifuzz (Coverage)", + "displayName": "cifuzz (Coverage)", + "configurePreset": "cifuzz (Coverage)", + "configuration": "RelWithDebInfo" + }, + { + "name": "cifuzz (Fuzzing)", + "displayName": "cifuzz (Fuzzing)", + "configurePreset": "cifuzz (Fuzzing)", + "configuration": "RelWithDebInfo" + }, + { + "name": "cifuzz (Regression Test)", + "displayName": "cifuzz (Regression Test)", + "configurePreset": "cifuzz (Regression Test)", + "configuration": "RelWithDebInfo" + } + ], + "testPresets": [ + { + "name": "cifuzz (Regression Test)", + "displayName": "cifuzz (Regression Test)", + "configurePreset": "cifuzz (Regression Test)", + "filter": { + "include": { + "label": "^cifuzz_regression_test$" + } + } + } + ] +} diff --git a/cifuzz.yaml b/cifuzz.yaml new file mode 100644 index 0000000..91be510 --- /dev/null +++ b/cifuzz.yaml @@ -0,0 +1,46 @@ +## Configuration for a CI Fuzz project +## Generated on 2023-06-06 + +## The build system used to build this project. If not set, cifuzz tries +## to detect the build system automatically. +## Valid values: "bazel", "cmake", "maven", "gradle", "other". +#build-system: cmake + +## If the build system type is "other", this command is used by +## `cifuzz run` to build the fuzz test. +#build-command: "make my_fuzz_test" + +## Directories containing sample inputs for the code under test. +## See https://llvm.org/docs/LibFuzzer.html#corpus +#seed-corpus-dirs: +# - path/to/seed-corpus + +## A file containing input language keywords or other interesting byte +## sequences. +## See https://llvm.org/docs/LibFuzzer.html#dictionaries +#dict: path/to/dictionary.dct + +## Command-line arguments to pass to libFuzzer. +## See https://llvm.org/docs/LibFuzzer.html#options +engine-args: + - -use_value_profile=1 + +## Maximum time to run fuzz tests. The default is to run indefinitely. +timeout: 5m + +## By default, fuzz tests are executed in a sandbox to prevent accidental +## damage to the system. Set to false to run fuzz tests unsandboxed. +## Only supported on Linux. +#use-sandbox: false + +## Set to true to print output of the `cifuzz run` command as JSON. +#print-json: true + +## Set to true to disable desktop notifications +#no-notifications: true + +## Set URL of the CI App +#server: https://app.code-intelligence.com + +## Set the project name on the CI App +#project: my-project-1a2b3c4d diff --git a/src/automotive/CMakeLists.txt b/src/automotive/CMakeLists.txt index 6fc7507..ba4b591 100644 --- a/src/automotive/CMakeLists.txt +++ b/src/automotive/CMakeLists.txt @@ -11,4 +11,13 @@ target_include_directories(automotive PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/gps ${CMAKE_CURRENT_SOURCE_DIR}/key_management ${CMAKE_CURRENT_SOURCE_DIR}/time -) \ No newline at end of file +) + +add_fuzz_test(automotive_fuzzer + fuzz_test.cpp + mocks.cpp +) + +target_link_libraries(automotive_fuzzer + automotive +) diff --git a/src/automotive/crypto/crypto_1.c b/src/automotive/crypto/crypto_1.c index f3f09ab..d458d67 100644 --- a/src/automotive/crypto/crypto_1.c +++ b/src/automotive/crypto/crypto_1.c @@ -60,6 +60,7 @@ enum crypto_return_status crypto_calculate_hmac(const uint8_t *message, int len, hmac->hmac) == 0) { // Delete nonce to make sure it is only used once free(current_nonce); + current_nonce = NULL; return hmac_successfully_calculated; } } diff --git a/src/automotive/fuzz_test.cpp b/src/automotive/fuzz_test.cpp new file mode 100644 index 0000000..f3b62e1 --- /dev/null +++ b/src/automotive/fuzz_test.cpp @@ -0,0 +1,209 @@ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "crypto_1.h" +#include "crypto_2.h" +#include "gps_1.h" +#include "key_management_1.h" +#include "time_1.h" + +#ifdef __cplusplus +} +#endif + +void SetFDP(FuzzedDataProvider *fuzzed_data_provider); +FuzzedDataProvider *GetFDP(); +void ConsumeDataAndFillRestWithZeros(void *destination, size_t num_bytes); + +FUZZ_TEST_SETUP() {} + +FUZZ_TEST(const uint8_t *data, size_t size) { + + // Ensure a minimum data length + if (size < 100) + return; + + // Setup FuzzedDataProvider and initialize the mocklib + FuzzedDataProvider fdp(data, size); + SetFDP(&fdp); + + int number_of_functions = GetFDP()->ConsumeIntegralInRange(1, 100); + for (int i = 0; i < number_of_functions; i++) { + int function_id = GetFDP()->ConsumeIntegralInRange(0, 15); + switch (function_id) { + + case 0: { + crypto_get_state(); + break; + } + + case 1: { + get_destination_position(); + break; + } + + case 2: { + crypto_key key = {}; + ConsumeDataAndFillRestWithZeros(key.key, 64); + + crypto_verify_key(key); + break; + } + + case 3: { + current_time(); + break; + } + + case 4: { + crypto_nonce nonce_tmp = {}; + ConsumeDataAndFillRestWithZeros(nonce_tmp.nonce, 64); + nonce_tmp.time_of_creation = GetFDP()->ConsumeIntegral(); + crypto_nonce *nonce = &nonce_tmp; + + crypto_verify_nonce(nonce); + break; + } + + case 5: { + std::vector message_vec = GetFDP()->ConsumeBytes( + sizeof(uint8_t) * GetFDP()->ConsumeIntegral()); + const uint8_t *message = (const uint8_t *)message_vec.data(); + + // The parameter "len" seems to represent the length of a buffer/array. In + // this case, we usually don't want to provide fuzzer-generated lengths + // that differ from the actual length of the buffer. If you confirm that + // the parameter is a length parameter, you can get the length of the + // fuzzer-generated buffer as follows (replace "buffer" with the actual + // variable): + // int len = buffer.size(); + int len = message_vec.size(); + crypto_hmac hmac_tmp = {}; + ConsumeDataAndFillRestWithZeros(hmac_tmp.hmac, 64); + crypto_hmac *hmac = &hmac_tmp; + + crypto_verify_hmac(message, len, hmac); + break; + } + + case 6: { + crypto_nonce nonce = {}; + ConsumeDataAndFillRestWithZeros(nonce.nonce, 64); + nonce.time_of_creation = GetFDP()->ConsumeIntegral(); + + crypto_set_nonce(nonce); + break; + } + + case 7: { + std::vector message_vec = GetFDP()->ConsumeBytes( + sizeof(uint8_t) * GetFDP()->ConsumeIntegral()); + const uint8_t *message = (const uint8_t *)message_vec.data(); + + // The parameter "len" seems to represent the length of a buffer/array. In + // this case, we usually don't want to provide fuzzer-generated lengths + // that differ from the actual length of the buffer. If you confirm that + // the parameter is a length parameter, you can get the length of the + // fuzzer-generated buffer as follows (replace "buffer" with the actual + // variable): + // int len = buffer.size(); + int len = message_vec.size(); + crypto_hmac hmac_tmp = {}; + ConsumeDataAndFillRestWithZeros(hmac_tmp.hmac, 64); + crypto_hmac *hmac = &hmac_tmp; + + crypto_calculate_hmac(message, len, hmac); + break; + } + + case 8: { + GPS_position position = {}; + position.longitude_degree = GetFDP()->ConsumeIntegral(); + position.longitude_minute = GetFDP()->ConsumeIntegral(); + position.longitude_second = GetFDP()->ConsumeIntegral(); + position.latitude_degree = GetFDP()->ConsumeIntegral(); + position.latitude_minute = GetFDP()->ConsumeIntegral(); + position.latitude_second = GetFDP()->ConsumeIntegral(); + + set_destination_postition(position); + break; + } + + case 9: { + crypto_key key = {}; + ConsumeDataAndFillRestWithZeros(key.key, 64); + + crypto_set_key(key); + break; + } + + case 10: { + GPS_position position_tmp = {}; + position_tmp.longitude_degree = GetFDP()->ConsumeIntegral(); + position_tmp.longitude_minute = GetFDP()->ConsumeIntegral(); + position_tmp.longitude_second = GetFDP()->ConsumeIntegral(); + position_tmp.latitude_degree = GetFDP()->ConsumeIntegral(); + position_tmp.latitude_minute = GetFDP()->ConsumeIntegral(); + position_tmp.latitude_second = GetFDP()->ConsumeIntegral(); + GPS_position *position = &position_tmp; + + get_current_position(position); + break; + } + + case 11: { + init_crypto_module(); + break; + } + + case 12: { + std::vector key_vec = GetFDP()->ConsumeBytes( + sizeof(uint8_t) * GetFDP()->ConsumeIntegral()); + uint8_t *key = (uint8_t *)key_vec.data(); + + // The parameter "length" seems to represent the length of a buffer/array. + // In this case, we usually don't want to provide fuzzer-generated lengths + // that differ from the actual length of the buffer. If you confirm that + // the parameter is a length parameter, you can get the length of the + // fuzzer-generated buffer as follows (replace "buffer" with the actual + // variable): + // uint8_t length = buffer.size(); + uint8_t length = key_vec.size(); + + key_management_create_key(key, length); + break; + } + + case 13: { + std::vector nonce_vec = GetFDP()->ConsumeBytes( + sizeof(uint8_t) * GetFDP()->ConsumeIntegral()); + uint8_t *nonce = (uint8_t *)nonce_vec.data(); + + // The parameter "length" seems to represent the length of a buffer/array. + // In this case, we usually don't want to provide fuzzer-generated lengths + // that differ from the actual length of the buffer. If you confirm that + // the parameter is a length parameter, you can get the length of the + // fuzzer-generated buffer as follows (replace "buffer" with the actual + // variable): + // uint8_t length = buffer.size(); + uint8_t length = nonce_vec.size(); + + key_management_create_nonce(nonce, length); + break; + } + + case 14: { + crypto_init(); + break; + } + } + } +} diff --git a/src/automotive/mocks.cpp b/src/automotive/mocks.cpp new file mode 100644 index 0000000..7def44e --- /dev/null +++ b/src/automotive/mocks.cpp @@ -0,0 +1,83 @@ + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "crypto_1.h" +#include "gps_1.h" +#include "key_management_1.h" +#include "time_1.h" + +#ifdef __cplusplus +} +#endif + +static FuzzedDataProvider *gFDP; + +// This function received the fuzzer generated data from the fuzz target. +// It needs to be called at the beginning of the LLVMFuzzerTestOneInput +// function. +void SetFDP(FuzzedDataProvider *fuzzed_data_provider) { + gFDP = fuzzed_data_provider; +} + +FuzzedDataProvider *GetFDP() { return gFDP; } + +// Wrapper function for FuzzedDataProvider.h +// Writes |num_bytes| of input data to the given destination pointer. If there +// is not enough data left, writes all remaining bytes and fills the rest with +// zeros. Return value is the number of bytes written. +void ConsumeDataAndFillRestWithZeros(void *destination, size_t num_bytes) { + if (destination != nullptr) { + size_t num_consumed_bytes = GetFDP()->ConsumeData(destination, num_bytes); + if (num_bytes > num_consumed_bytes) { + size_t num_zero_bytes = num_bytes - num_consumed_bytes; + std::memset((char *)destination + num_consumed_bytes, 0, num_zero_bytes); + } + } +} + +#ifdef __cplusplus +extern "C" { +#endif + +int driver_get_current_time() { + int cifuzz_var_0 = GetFDP()->ConsumeIntegral(); + return cifuzz_var_0; +} + +uint8_t GPS_driver_obtain_current_position(uint8_t *position_as_bytes, + uint8_t *hmac_as_bytes) { + unsigned int position_as_bytes_length = 12; + ConsumeDataAndFillRestWithZeros((void *)position_as_bytes, + position_as_bytes_length); + unsigned int hmac_as_bytes_length = 64; + ConsumeDataAndFillRestWithZeros((void *)hmac_as_bytes, hmac_as_bytes_length); + uint8_t cifuzz_var_1 = GetFDP()->ConsumeIntegral(); + return cifuzz_var_1; +} + +uint8_t HSM_get_random_byte() { + uint8_t cifuzz_var_2 = GetFDP()->ConsumeIntegral(); + return cifuzz_var_2; +} + +uint8_t third_party_library_calc_hmac(const uint8_t *message, int len, + const char *key, const char *nonce, + uint8_t *hmac) { + unsigned int hmac_length = 64; + ConsumeDataAndFillRestWithZeros((void *)hmac, hmac_length); + uint8_t cifuzz_var_3 = GetFDP()->ConsumeIntegral(); + return cifuzz_var_3; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/explore_me/CMakeLists.txt b/src/explore_me/CMakeLists.txt index a3c3e04..9273a3d 100644 --- a/src/explore_me/CMakeLists.txt +++ b/src/explore_me/CMakeLists.txt @@ -34,4 +34,13 @@ foreach(TestType IN ITEMS ) add_test(explore_me.${TestType} ${TestType}_test) + + add_fuzz_test(${TestType}_fuzz_test + ${TestType}_test.cpp + ) + + target_link_libraries(${TestType}_fuzz_test + explore_me + ${GTEST_BOTH_LIBRARIES} + ) endforeach(TestType ) diff --git a/src/explore_me/complex_checks_test.cpp b/src/explore_me/complex_checks_test.cpp index 190e014..81ad008 100644 --- a/src/explore_me/complex_checks_test.cpp +++ b/src/explore_me/complex_checks_test.cpp @@ -1,9 +1,11 @@ #include #include -#include #include "explore_me.h" +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#include + TEST(ExploreComplexChecksTests, DeveloperTest) { EXPECT_NO_THROW(ExploreComplexChecks(0, 10, "Developer")); } @@ -11,3 +13,14 @@ TEST(ExploreComplexChecksTests, DeveloperTest) { TEST(ExploreComplexChecksTests, MaintainerTest) { EXPECT_NO_THROW(ExploreComplexChecks(20, -10, "Maintainer")); } + +#endif + +FUZZ_TEST(const uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + long a = fdp.ConsumeIntegral(); + long b = fdp.ConsumeIntegral(); + std::string c = fdp.ConsumeRemainingBytesAsString(); + + ExploreComplexChecks(a, b, c); +} diff --git a/src/explore_me/simple_checks_test.cpp b/src/explore_me/simple_checks_test.cpp index 2eec307..2819401 100644 --- a/src/explore_me/simple_checks_test.cpp +++ b/src/explore_me/simple_checks_test.cpp @@ -1,9 +1,11 @@ #include #include -#include #include "explore_me.h" +#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#include + TEST(ExploreSimpleChecksTests, DeveloperTest) { EXPECT_NO_THROW(ExploreSimpleChecks(0, 10, "Developer")); } @@ -11,3 +13,14 @@ TEST(ExploreSimpleChecksTests, DeveloperTest) { TEST(ExploreSimpleChecksTests, MaintainerTest) { EXPECT_NO_THROW(ExploreSimpleChecks(20, -10, "Maintainer")); } + +#endif + +FUZZ_TEST(const uint8_t *data, size_t size) { + FuzzedDataProvider fdp(data, size); + int a = fdp.ConsumeIntegral(); + int b = fdp.ConsumeIntegral(); + std::string c = fdp.ConsumeRemainingBytesAsString(); + + ExploreSimpleChecks(a, b, c); +}