From ec05084df550fd25c3fe0acd7ab62ba7d2130b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20Gean=C4=83?= <90633119+alexgeana@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:26:30 +0200 Subject: [PATCH] Initial fuzzing code (#372) Introducing initial fuzzing functionality --- .github/workflows/build_and_fuzz.yml | 75 ++++ .../workflows/push_fuzzing_msan_container.yml | 35 ++ CMakeLists.txt | 1 + cmake/Fuzzing.cmake | 27 ++ cmake/SecurityFlags.cmake | 4 +- examples/CMakeLists.txt | 1 + lib/CMakeLists.txt | 12 + lib/fuzz/fuzz_send_plain_msg.cc | 46 ++ lib/fuzz/fuzz_send_secure_msg.cc | 62 +++ lib/fuzz/yubihsm_fuzz.cc | 425 ++++++++++++++++++ lib/fuzz/yubihsm_fuzz.h | 21 + lib/internal.h | 3 + lib/scp.h | 7 +- lib/yubihsm.c | 50 ++- lib/yubihsm.h | 15 + pkcs11/CMakeLists.txt | 37 ++ pkcs11/fuzz/fuzz_get_attribute_value.cc | 234 ++++++++++ pkcs11/util_pkcs11.c | 37 +- pkcs11/yubihsm_pkcs11.c | 8 + resources/fuzzing/Docker.fuzzing_msan | 83 ++++ src/CMakeLists.txt | 2 +- 21 files changed, 1165 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/build_and_fuzz.yml create mode 100644 .github/workflows/push_fuzzing_msan_container.yml create mode 100644 cmake/Fuzzing.cmake create mode 100644 lib/fuzz/fuzz_send_plain_msg.cc create mode 100644 lib/fuzz/fuzz_send_secure_msg.cc create mode 100644 lib/fuzz/yubihsm_fuzz.cc create mode 100644 lib/fuzz/yubihsm_fuzz.h create mode 100644 pkcs11/fuzz/fuzz_get_attribute_value.cc create mode 100644 resources/fuzzing/Docker.fuzzing_msan diff --git a/.github/workflows/build_and_fuzz.yml b/.github/workflows/build_and_fuzz.yml new file mode 100644 index 000000000..adec08c95 --- /dev/null +++ b/.github/workflows/build_and_fuzz.yml @@ -0,0 +1,75 @@ +name: Build and Fuzz + +on: + schedule: + # Run this every wednesday at 3:50. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule + - cron: '50 3 * * 3' + +jobs: + fuzz_msan: + name: fuzz with MemorySanitizer + runs-on: ubuntu-latest + container: ghcr.io/yubico/yubihsm-shell/fuzzing-msan:latest + + steps: + + - name: clone the Yubico/yubihsm-shell repository + uses: actions/checkout@v3 + with: + path: yubihsm-shell + + - name: do build + working-directory: yubihsm-shell + run: | + cmake \ + -DFUZZING=ON \ + -DFUZZING_MSAN=ON \ + -DWITHOUT_MANPAGES=ON \ + -DDISABLE_LTO=ON \ + -DENABLE_STATIC=ON \ + -B build-msan + cmake --build build-msan + + - name: run harness for fuzz_get_attribute_value + working-directory: yubihsm-shell + run: ./build-msan/pkcs11/fuzz_get_attribute_value -max_total_time=1800 + + fuzz_asan: + name: fuzz with AddressSanitizer + runs-on: ubuntu-latest + container: ubuntu:23.04 + + steps: + + - name: install dependencies from package management + env: + DEBIAN_FRONTEND: noninteractive + run: | + apt -q -y update + apt -q -y install \ + llvm-16 clang-16 lld-16 \ + build-essential cmake ninja-build pkg-config \ + libssl-dev libedit-dev libcurl4-openssl-dev libusb-1.0-0-dev libpcsclite-dev gengetopt + + - name: clone the Yubico/yubihsm-shell repository + uses: actions/checkout@v3 + with: + path: yubihsm-shell + + - name: do build + env: + CC: clang-16 + CXX: clang++-16 + working-directory: yubihsm-shell + run: | + cmake \ + -DFUZZING=ON \ + -DWITHOUT_MANPAGES=ON \ + -DDISABLE_LTO=ON \ + -DENABLE_STATIC=ON \ + -B build-asan + cmake --build build-asan + + - name: run harness for fuzz_get_attribute_value + working-directory: yubihsm-shell + run: ./build-asan/pkcs11/fuzz_get_attribute_value -max_total_time=1800 diff --git a/.github/workflows/push_fuzzing_msan_container.yml b/.github/workflows/push_fuzzing_msan_container.yml new file mode 100644 index 000000000..64ccac44b --- /dev/null +++ b/.github/workflows/push_fuzzing_msan_container.yml @@ -0,0 +1,35 @@ +name: Create MSAN fuzzing docker image + +on: + push: + paths: + - "resources/fuzzing/Docker.fuzzing_msan" + +jobs: + build_and_push: + name: Build and Push + + runs-on: ubuntu-latest + + permissions: + packages: write + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build image + uses: docker/build-push-action@v5 + with: + pull: true + push: true + tags: ghcr.io/yubico/yubihsm-shell/fuzzing-msan:latest + context: "{{defaultContext}}:resources/fuzzing" + file: "Docker.fuzzing_msan" diff --git a/CMakeLists.txt b/CMakeLists.txt index bed20628c..4bcc900b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ project (yubihsm-shell) option(BUILD_ONLY_LIB "Library only build" OFF) option(SUPRESS_MSVC_WARNINGS "Suppresses a lot of the warnings when compiling with MSVC" ON) +include(${CMAKE_SOURCE_DIR}/cmake/Fuzzing.cmake) include(${CMAKE_SOURCE_DIR}/cmake/SecurityFlags.cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") diff --git a/cmake/Fuzzing.cmake b/cmake/Fuzzing.cmake new file mode 100644 index 000000000..2847939c7 --- /dev/null +++ b/cmake/Fuzzing.cmake @@ -0,0 +1,27 @@ +option(FUZZING "Compile binaries with fuzzing instrumentation" OFF) +option(LIBFUZZER_ASAN "Enable ASAN instrumentation with libfuzzer" OFF) +option(FUZZING_MSAN "Compile binaries with MemorySanitizer instrumentation" OFF) + +if (FUZZING) + message(STATUS "Building with fuzzing instrumentation.") + + string (APPEND CMAKE_C_FLAGS " -DFUZZING") + string (APPEND CMAKE_C_FLAGS " -fno-omit-frame-pointer -O1 -g") + + string (APPEND CMAKE_CXX_FLAGS " -std=c++17") + string (APPEND CMAKE_CXX_FLAGS " -DFUZZING") + string (APPEND CMAKE_CXX_FLAGS " -fno-omit-frame-pointer -O1 -g") + + string (APPEND CMAKE_EXE_LINKER_FLAGS " -g") + + if (FUZZING_MSAN) + string (APPEND CMAKE_C_FLAGS " -fsanitize=memory") + string (APPEND CMAKE_CXX_FLAGS " -fsanitize=memory") + string (APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=memory") + else (FUZZING_MSAN) + string (APPEND CMAKE_C_FLAGS " -fsanitize=address -fsanitize=undefined") + string (APPEND CMAKE_CXX_FLAGS " -fsanitize=address -fsanitize=undefined") + string (APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address -fsanitize=undefined") + endif (FUZZING_MSAN) + +endif () diff --git a/cmake/SecurityFlags.cmake b/cmake/SecurityFlags.cmake index 69f4edfd4..1743b07f1 100644 --- a/cmake/SecurityFlags.cmake +++ b/cmake/SecurityFlags.cmake @@ -4,7 +4,6 @@ if (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_C_COMPILER_ID STREQUAL "GNU") - add_compile_options (-Wall -Wextra -Werror) add_compile_options (-Wformat -Wformat-nonliteral -Wformat-security) add_compile_options (-Wshadow) #add_compile_options (-Wcast-qual) @@ -12,7 +11,8 @@ if (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR add_compile_options (-Wbad-function-cast) add_compile_options (-pedantic -pedantic-errors) add_compile_options (-fpie -fpic) - if (NOT FUZZ) + if (NOT FUZZING) + add_compile_options (-Wall -Wextra -Werror) add_compile_options(-O2) add_definitions (-D_FORTIFY_SOURCE=2) endif () diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 28750ad16..50ab0e6f8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -52,6 +52,7 @@ set ( SOURCE_WRAP wrap.c ../common/util.c + ../common/hash.c ../common/openssl-compat.c ) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 79d829447..d588f3d58 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -94,6 +94,18 @@ else(WIN32) set(CRYPT_LIBRARY ${LIBCRYPTO_LDFLAGS}) list(APPEND STATIC_SOURCE yubihsm_libusb.c yubihsm_usb.c yubihsm_curl.c) + + if(FUZZING) + add_executable(fuzz_send_plain_msg ${SOURCE} fuzz/fuzz_send_plain_msg.cc fuzz/yubihsm_fuzz.cc) + set_target_properties (fuzz_send_plain_msg PROPERTIES COMPILE_FLAGS "-DSTATIC -fsanitize=fuzzer ") + set_target_properties (fuzz_send_plain_msg PROPERTIES LINK_FLAGS "-fsanitize=fuzzer ") + target_link_libraries (fuzz_send_plain_msg ${LIBCRYPTO_LDFLAGS}) + + add_executable(fuzz_send_secure_msg ${SOURCE} fuzz/fuzz_send_secure_msg.cc fuzz/yubihsm_fuzz.cc) + set_target_properties (fuzz_send_secure_msg PROPERTIES COMPILE_FLAGS "-DSTATIC -fsanitize=fuzzer ") + set_target_properties (fuzz_send_secure_msg PROPERTIES LINK_FLAGS "-fsanitize=fuzzer ") + target_link_libraries (fuzz_send_secure_msg ${LIBCRYPTO_LDFLAGS}) + endif(FUZZING) endif(WIN32) include_directories ( diff --git a/lib/fuzz/fuzz_send_plain_msg.cc b/lib/fuzz/fuzz_send_plain_msg.cc new file mode 100644 index 000000000..49d1406ef --- /dev/null +++ b/lib/fuzz/fuzz_send_plain_msg.cc @@ -0,0 +1,46 @@ +#include +#include "debug_lib.h" + +extern "C" { +#include "yubihsm.h" + +uint8_t *backend_data; +size_t backend_data_len; +} + +#include "yubihsm_fuzz.h" + +yh_connector *connector; + +static bool initialize() { + yh_rc rc = yh_init_connector("yhfuzz://yubihsm_fuzz", &connector); + assert(rc == YHR_SUCCESS); + rc = yh_connect(connector, 0); + assert(rc == YHR_SUCCESS); + return true; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + static bool is_initialized = initialize(); + + if (size < 2) { + return 0; + } + size_t data_len = data[0]; + size_t response_len = data[1]; + + backend_data = data + 2; + backend_data_len = size - 2; + + uint8_t *hsm_data = new uint8_t[data_len]; + uint8_t *response = new uint8_t[response_len]; + yh_cmd response_cmd; + + yh_send_plain_msg(connector, YHC_ECHO, hsm_data, data_len, &response_cmd, + response, &response_len); + + delete[] hsm_data; + delete[] response; + + return 0; +} diff --git a/lib/fuzz/fuzz_send_secure_msg.cc b/lib/fuzz/fuzz_send_secure_msg.cc new file mode 100644 index 000000000..e4f413f53 --- /dev/null +++ b/lib/fuzz/fuzz_send_secure_msg.cc @@ -0,0 +1,62 @@ +#include +#include +#include "debug_lib.h" + +extern "C" { +#include "yubihsm.h" + +uint8_t *backend_data; +size_t backend_data_len; +yh_session *fuzz_session; +} + +#include "yubihsm_fuzz.h" + +yh_connector *connector; + +static bool initialize() { + yh_rc rc = yh_init_connector("yhfuzz://yubihsm_fuzz", &connector); + assert(rc == YHR_SUCCESS); + rc = yh_connect(connector, 0); + assert(rc == YHR_SUCCESS); + return true; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + static bool is_initialized = initialize(); + yh_rc yrc = YHR_GENERIC_ERROR; + + if (size < 2) { + return 0; + } + + yrc = yh_create_session_derived(connector, 1, + (const uint8_t *) FUZZ_BACKEND_PASSWORD, + strlen(FUZZ_BACKEND_PASSWORD), false, + &fuzz_session); + assert(yrc == YHR_SUCCESS); + + size_t data_len = data[0]; + size_t response_len = data[1]; + + backend_data = data + 2; + backend_data_len = size - 2; + + uint8_t *hsm_data = new uint8_t[data_len]; + uint8_t *response = new uint8_t[response_len]; + yh_cmd response_cmd; + + yh_send_secure_msg(fuzz_session, YHC_ECHO, hsm_data, data_len, &response_cmd, + response, &response_len); + + yrc = yh_util_close_session(fuzz_session); + assert(yrc == YHR_SUCCESS); + + yrc = yh_destroy_session(&fuzz_session); + assert(yrc == YHR_SUCCESS); + + delete[] hsm_data; + delete[] response; + + return 0; +} diff --git a/lib/fuzz/yubihsm_fuzz.cc b/lib/fuzz/yubihsm_fuzz.cc new file mode 100644 index 000000000..d778a76a2 --- /dev/null +++ b/lib/fuzz/yubihsm_fuzz.cc @@ -0,0 +1,425 @@ +/* + * Copyright 2015-2021 Yubico AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "scp.h" +#include +#include +#include + +using namespace std; + +extern "C" { +#include "yubihsm.h" +#include "internal.h" +#include "debug_lib.h" +#include "../common/platform-config.h" +#include "../common/pkcs5.h" +#include "../common/hash.h" +#include "../aes_cmac/aes.h" +#include "../aes_cmac/aes_cmac.h" +} + +#include "yubihsm_fuzz.h" + +static void process_msg(Msg *msg, Msg *response); +static bool compute_mac(Scp_ctx *s, uint8_t *key, Msg *msg, size_t raw_msg_len, + int host_order_len, uint8_t *mac); + +struct state {}; + +static uint8_t key_enc[SCP_KEY_LEN]; +static uint8_t key_mac[SCP_KEY_LEN]; + +#define FUZZ_MAX_SESSIONS 10 +static Scp_ctx sessions[FUZZ_MAX_SESSIONS]; +static uint8_t init_sessions[FUZZ_MAX_SESSIONS]; +static int current_session_id = -1; + +static int get_free_session_slot() { + for (int i = 0; i < FUZZ_MAX_SESSIONS; i++) { + if (init_sessions[i] == 0) { + return i; + } + } + return -1; +} + +static int is_session_slot_initialized(int slot) { + if (slot < 0 || slot >= FUZZ_MAX_SESSIONS) { + return 0; + } + return init_sessions[slot]; +} + +static bool compute_mac(Scp_ctx *s, uint8_t *key, Msg *msg, size_t raw_msg_len, + int host_order_len, uint8_t *mac) { + aes_context aes_ctx; + aes_cmac_context_t cmac_ctx; + +#pragma pack(push, 1) + struct { + uint8_t mac_chaining_value[SCP_PRF_LEN]; + Msg msg; + } mac_msg; +#pragma pack(pop) + + memset(&mac_msg, 0, sizeof(mac_msg)); + + if (raw_msg_len > sizeof(Msg)) { + return false; + } + memcpy(mac_msg.mac_chaining_value, s->mac_chaining_value, SCP_PRF_LEN); + memcpy(&mac_msg.msg, msg, raw_msg_len); + + if (host_order_len) { + // macced len field is in network byte order + mac_msg.msg.st.len = htons(mac_msg.msg.st.len); + } + + // this is the size of the raw package with everything to be macced + size_t macced_data_len = SCP_PRF_LEN + raw_msg_len; + + memset(&aes_ctx, 0, sizeof(aes_ctx)); + aes_set_key(key, SCP_KEY_LEN, &aes_ctx); + aes_cmac_init(&aes_ctx, &cmac_ctx); + aes_cmac_encrypt(&cmac_ctx, (uint8_t *) &mac_msg, macced_data_len, mac); + + aes_cmac_destroy(&cmac_ctx); + aes_destroy(&aes_ctx); + + return true; +} + +static void process_msg(Msg *msg, Msg *response) { + aes_context aes_ctx; + memset(&aes_ctx, 0, sizeof(aes_ctx)); + + msg->st.len = ntohs(msg->st.len); + + switch (msg->st.cmd) { + + case YHC_CREATE_SESSION: { + /* The data (i.e. msg->st.data) associated with a create session request + * is authentication key ID -> first SCP_AUTHKEY_ID_LEN bytes host + * challenge -> the rest of the msg->st.len bytes See also + * yh_begin_create_session(). + */ + if (msg->st.len < SCP_AUTHKEY_ID_LEN) { + response->st.cmd = YHC_ERROR; + break; + } + + // Check if a new session can be created or we reached the max number of + // open sessions. + int session_id = get_free_session_slot(); + if (session_id < 0) { + response->st.cmd = YHC_ERROR; + break; + } + + uint16_t host_challenge_len; + host_challenge_len = msg->st.len - SCP_AUTHKEY_ID_LEN; + + /* Setting up the session context used later on to calculate the card + * cryptogram. See also yh_begin_create_session(). The session context + * contains the host challenge the card challenge (assumed 0s here) + */ + uint8_t session_context[2 * YH_EC_P256_PUBKEY_LEN] = {0}; + if (host_challenge_len > sizeof(session_context)) { + response->st.cmd = YHC_ERROR; + break; + } + memcpy(session_context, msg->st.data + SCP_AUTHKEY_ID_LEN, + host_challenge_len); + + // Derive the SCP context s_env, s_mac and s_rmac keys. + compute_cryptogram(key_enc, SCP_KEY_LEN, SCP_S_ENC_DERIVATION, + session_context, SCP_KEY_LEN * 8, + sessions[session_id].s_enc); + compute_cryptogram(key_mac, SCP_KEY_LEN, SCP_S_MAC_DERIVATION, + session_context, SCP_KEY_LEN * 8, + sessions[session_id].s_mac); + compute_cryptogram(key_mac, SCP_KEY_LEN, SCP_S_RMAC_DERIVATION, + session_context, SCP_KEY_LEN * 8, + sessions[session_id].s_rmac); + + /* Calculation of the card cryptogram. + * type = SCP_CARD_CRYPTOGRAM + * L = SCP_CARD_CRYPTO_LEN * 8 + * context = the session context + */ + uint8_t calculated_card_cryptogram[SCP_PRF_LEN]; + compute_cryptogram(sessions[session_id].s_mac, SCP_KEY_LEN, + SCP_CARD_CRYPTOGRAM, session_context, + SCP_CARD_CRYPTO_LEN * 8, calculated_card_cryptogram); + + /* The expected response is + * session id - 1 byte + * the card challenge - SCP_CARD_CHAL_LEN + * the resulting cryptogram - SCP_CARD_CRYPTO_LEN + */ + response->st.cmd = YHC_CREATE_SESSION_R; + response->st.len = 1 + SCP_CARD_CHAL_LEN + SCP_CARD_CRYPTO_LEN; + response->st.data[0] = session_id; + memcpy(response->st.data + 1 + SCP_CARD_CHAL_LEN, + calculated_card_cryptogram, SCP_CARD_CRYPTO_LEN); + + init_sessions[session_id] = 1; + + break; + } + + case YHC_AUTHENTICATE_SESSION: { + int session_id = msg->st.data[0]; + uint8_t mac[SCP_PRF_LEN] = {0}; + + if (is_session_slot_initialized(session_id) == 0) { + response->st.cmd = YHC_ERROR; + break; + } + Scp_ctx *s = &sessions[session_id]; + + if (!compute_mac(s, s->s_mac, msg, 3 + msg->st.len - SCP_MAC_LEN, 1, + mac)) { + response->st.cmd = YHC_ERROR; + break; + } + // update the session mac chaining value + memcpy(s->mac_chaining_value, mac, SCP_PRF_LEN); + + if (memcmp(mac, &msg->st.data[msg->st.len - SCP_MAC_LEN], SCP_MAC_LEN)) { + DBG_ERR("invalid mac during YHC_AUTHENTICATE_SESSION"); + } + + response->st.cmd = YHC_AUTHENTICATE_SESSION_R; + response->st.len = SCP_MAC_LEN; + compute_mac(s, s->s_rmac, response, 3, 1, mac); + + // copy the mac into the response struct and update the length + memcpy(response->st.data, mac, SCP_MAC_LEN); + + increment_ctr(s->ctr, SCP_PRF_LEN); + + break; + } + + case YHC_CLOSE_SESSION: { + if (current_session_id != -1 && + is_session_slot_initialized(current_session_id) == 0) { + response->st.cmd = YHC_ERROR; + break; + } + + memset(&sessions[current_session_id], 0, sizeof(Scp_ctx)); + init_sessions[current_session_id] = 0; + + response->st.cmd = YHC_CLOSE_SESSION_R; + + break; + } + + case YHC_SESSION_MESSAGE: { + uint8_t encrypted_ctr[AES_BLOCK_SIZE] = {0}; + Msg inner_msg, inner_response; + uint8_t mac[SCP_PRF_LEN] = {0}; + uint16_t inner_response_padded_len = {0}; + + memset(&inner_msg, 0, sizeof(inner_msg)); + memset(&inner_response, 0, sizeof(inner_response)); + + current_session_id = msg->st.data[0]; + if (is_session_slot_initialized(current_session_id) == 0) { + response->st.cmd = YHC_ERROR; + break; + } + Scp_ctx *s = &sessions[current_session_id]; + + if (compute_mac(s, s->s_mac, msg, 3 + msg->st.len - SCP_MAC_LEN, 1, + mac) == false) { + response->st.cmd = YHC_ERROR; + break; + } + // update the session mac chaining value + memcpy(s->mac_chaining_value, mac, SCP_PRF_LEN); + + if (memcmp(mac, &msg->st.data[msg->st.len - SCP_MAC_LEN], SCP_MAC_LEN)) { + DBG_ERR("invalid mac during YHC_AUTHENTICATE_SESSION"); + } + + aes_set_key(s->s_enc, SCP_KEY_LEN, &aes_ctx); + aes_encrypt(s->ctr, encrypted_ctr, &aes_ctx); + increment_ctr(s->ctr, SCP_PRF_LEN); + + // decrypt the message + aes_cbc_decrypt(msg->st.data + 1, inner_msg.raw, + msg->st.len - SCP_MAC_LEN - 1, encrypted_ctr, &aes_ctx); + + /* recursive call to process the inner message + * + * if the inner_msg has command YHC_CLOSE_SESSION, then the + * session object will be zeroed and we will lose access to + * the associated key material, and the call to compute_mac + * will fail. + * + * for that situation, we should cache the session object before + * processing the YHC_CLOSE_SESSION command. + */ + Scp_ctx saved_session; + memcpy(&saved_session, s, sizeof(Scp_ctx)); + process_msg(&inner_msg, &inner_response); + + // set the response type + response->st.cmd = YHC_SESSION_MESSAGE_R; + + // copy over the session id to the expected value + response->st.data[0] = msg->st.data[0]; + + // encrypt the inner response + inner_response_padded_len = ntohs(inner_response.st.len) + 3; + aes_add_padding(inner_response.raw, sizeof(inner_response.raw), + &inner_response_padded_len); + aes_cbc_encrypt(inner_response.raw, response->st.data + 1, + inner_response_padded_len, encrypted_ctr, &aes_ctx); + response->st.len = 1 + inner_response_padded_len; + + aes_destroy(&aes_ctx); + + // authenticate the response + response->st.len += SCP_MAC_LEN; + if (response->st.len + 3 > sizeof(Msg)) { + // there is no place to add the mac at the end of the message + response->st.cmd = YHC_ERROR; + break; + } + + if (compute_mac(&saved_session, saved_session.s_rmac, response, + 3 + response->st.len - SCP_MAC_LEN, 1, mac) == false) { + response->st.cmd = YHC_ERROR; + break; + } + + // copy the mac into the response struct and update the length + memcpy(response->st.data + response->st.len - SCP_MAC_LEN, mac, + SCP_MAC_LEN); + + current_session_id = -1; + break; + } + + default: + /* inner messages such as YHC_GENERATE_ASYMMETRIC_KEY + * here put some fuzzer data which gets decrypted and processed on the + * host side + */ + uint8_t size_byte = 0; + if (backend_data_len > 0) { + size_byte = backend_data[0]; + backend_data += 1; + backend_data_len -= 1; + } + + // limit size_byte artificially + if (size_byte > SCP_MSG_BUF_SIZE - 32) { + size_byte %= SCP_MSG_BUF_SIZE - 32; + } + + response->st.len = size_byte; + if (size_byte > backend_data_len) { + size_byte = backend_data_len; + } + + memcpy(response->st.data, backend_data, size_byte); + backend_data += size_byte; + backend_data_len -= size_byte; + + break; + } + + response->st.len = htons(response->st.len); +} + +static void fuzz_backend_set_verbosity(uint8_t verbosity, FILE *output) { + _yh_verbosity = verbosity; + _yh_output = output; +} + +static yh_rc fuzz_backend_init(uint8_t verbosity, FILE *output) { + fuzz_backend_set_verbosity(verbosity, output); + + uint8_t keys[2 * SCP_KEY_LEN]; + pkcs5_pbkdf2_hmac((const uint8_t *) FUZZ_BACKEND_PASSWORD, + strlen(FUZZ_BACKEND_PASSWORD), + (const uint8_t *) YH_DEFAULT_SALT, strlen(YH_DEFAULT_SALT), + YH_DEFAULT_ITERS, _SHA256, keys, sizeof(keys)); + + memcpy(key_enc, keys, SCP_KEY_LEN); + memcpy(key_mac, keys + SCP_KEY_LEN, SCP_KEY_LEN); + + return YHR_SUCCESS; +} + +static yh_backend *fuzz_backend_create(void) { + yh_backend *backend = (yh_backend *) calloc(1, sizeof(yh_backend)); + return backend; +} + +static yh_rc fuzz_backend_connect(yh_connector *connector, int timeout) { + (void) connector; + (void) timeout; + + connector->has_device = 1; + + return YHR_SUCCESS; +} + +static void fuzz_backend_disconnect(yh_backend *connection) { + free(connection); +} + +static yh_rc fuzz_backend_send_msg(yh_backend *connection, Msg *msg, + Msg *response, const char *identifier) { + (void) connection; + (void) identifier; + + memset(response->raw, 0, sizeof(response->raw)); + + process_msg(msg, response); + + return YHR_SUCCESS; +} + +static void fuzz_backend_cleanup(void) {} + +static yh_rc fuzz_backend_option(yh_backend *connection, + yh_connector_option opt, const void *val) { + (void) connection; + (void) opt; + (void) val; + return YHR_CONNECTOR_ERROR; +} + +static struct backend_functions f = + {fuzz_backend_init, fuzz_backend_create, fuzz_backend_connect, + fuzz_backend_disconnect, fuzz_backend_send_msg, fuzz_backend_cleanup, + fuzz_backend_option, fuzz_backend_set_verbosity}; + +#ifdef STATIC +extern "C" struct backend_functions *fuzz_backend_functions(void) { +#else +extern "C" struct backend_functions *backend_functions(void) { +#endif + return &f; +} diff --git a/lib/fuzz/yubihsm_fuzz.h b/lib/fuzz/yubihsm_fuzz.h new file mode 100644 index 000000000..8b05c8b78 --- /dev/null +++ b/lib/fuzz/yubihsm_fuzz.h @@ -0,0 +1,21 @@ +#ifndef _FUZZER_H +#define _FUZZER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +extern yh_session *fuzz_session; +#define FUZZ_BACKEND_PASSWORD "fuzzfuzz" + +extern uint8_t *backend_data; +extern size_t backend_data_len; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/internal.h b/lib/internal.h index 351ab151f..c718bc954 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -78,6 +78,9 @@ struct backend_functions { #ifdef STATIC struct backend_functions YH_INTERNAL *usb_backend_functions(void); struct backend_functions YH_INTERNAL *http_backend_functions(void); +#ifdef FUZZING +struct backend_functions YH_INTERNAL *fuzz_backend_functions(void); +#endif #else struct backend_functions *backend_functions(void); #endif diff --git a/lib/scp.h b/lib/scp.h index 7f9042d3d..39d71100f 100644 --- a/lib/scp.h +++ b/lib/scp.h @@ -49,7 +49,12 @@ #define SCP_AUTHKEY_ID_LEN 2 -#define SCP_MSG_BUF_SIZE 3136 // Hard limit on the firmware side +#ifndef FUZZING +#define SCP_MSG_BUF_SIZE 3136 +#else +// in fuzzing builds make the data buffers smaller +#define SCP_MSG_BUF_SIZE 100 +#endif // Message #pragma pack(push, 1) diff --git a/lib/yubihsm.c b/lib/yubihsm.c index 1ce641ca0..d467b729b 100644 --- a/lib/yubihsm.c +++ b/lib/yubihsm.c @@ -44,6 +44,7 @@ #define STATIC_USB_BACKEND "usb" #define STATIC_HTTP_BACKEND "http" +#define STATIC_FUZZ_BACKEND "yhfuzz" // If any of the values in scp.h are changed // they should be mirrored in yubihsm.h @@ -65,8 +66,13 @@ _Static_assert(SCP_KEY_LEN == YH_KEY_LEN, "Message buffer size mismatch"); #define LIST_SEPARATORS ":,;|" +#ifdef FUZZING +uint8_t _yh_verbosity = 0; +FILE *_yh_output = NULL; +#else uint8_t _yh_verbosity YH_INTERNAL = 0; FILE *_yh_output YH_INTERNAL = NULL; +#endif static yh_rc compute_full_mac_ex(const uint8_t *data, uint16_t data_len, aes_context *aes_ctx, uint8_t *mac) { @@ -166,10 +172,13 @@ static yh_rc compute_cryptogram_ex(aes_context *aes_ctx, uint8_t type, return YHR_SUCCESS; } -static yh_rc compute_cryptogram(const uint8_t *key, uint16_t key_len, - uint8_t type, - const uint8_t context[SCP_CONTEXT_LEN], - uint16_t L, uint8_t *key_out) { +#ifndef FUZZING +static +#endif + yh_rc + compute_cryptogram(const uint8_t *key, uint16_t key_len, uint8_t type, + const uint8_t context[SCP_CONTEXT_LEN], uint16_t L, + uint8_t *key_out) { aes_context aes_ctx = {0}; if (aes_set_key(key, key_len, &aes_ctx)) { @@ -182,7 +191,11 @@ static yh_rc compute_cryptogram(const uint8_t *key, uint16_t key_len, return yrc; } -static void increment_ctr(uint8_t *ctr, uint16_t len) { +#ifndef FUZZING +static +#endif + void + increment_ctr(uint8_t *ctr, uint16_t len) { while (len > 0) { if (++ctr[--len]) { @@ -439,7 +452,8 @@ static yh_rc send_encrypted_msg(Scp_ctx *session, yh_cmd cmd, // Outer command { cmd | cmd_len | sid | encrypted payload | mac } if (3 + 1 + len + SCP_MAC_LEN > max_message_size) { - DBG_ERR("%s (%u > %u)", yh_strerror(YHR_BUFFER_TOO_SMALL), 3 + 1 + len + SCP_MAC_LEN, max_message_size); + DBG_ERR("%s (%u > %u)", yh_strerror(YHR_BUFFER_TOO_SMALL), + 3 + 1 + len + SCP_MAC_LEN, max_message_size); return YHR_BUFFER_TOO_SMALL; } @@ -1036,7 +1050,8 @@ yh_rc yh_finish_create_session(yh_session *session, const uint8_t *key_senc, return yrc; } - // Verify card cryptogram (after sending response, so we clean up the hsm session on failure) + // Verify card cryptogram (after sending response, so we clean up the hsm + // session on failure) if (memcmp(card_cryptogram, computed_cryptogram, SCP_CARD_CRYPTO_LEN)) { DBG_ERR("%s", yh_strerror(YHR_CRYPTOGRAM_MISMATCH)); return YHR_CRYPTOGRAM_MISMATCH; @@ -1658,7 +1673,7 @@ yh_rc yh_util_get_object_info(yh_session *session, uint16_t id, response.delegated_capabilities, YH_CAPABILITIES_LEN); } } else { - DBG_ERR("Wrong response length, expecting %lu or 0, received %lu", + DBG_ERR("Wrong response length, expecting %lu, received %lu", (unsigned long) sizeof(yh_object_descriptor), (unsigned long) response_len); return YHR_WRONG_LENGTH; @@ -4633,13 +4648,22 @@ static yh_rc load_backend(const char *name, if (name == NULL) { DBG_ERR("No name given to load_backend"); return YHR_GENERIC_ERROR; - } else if (strncmp(name, STATIC_USB_BACKEND, strlen(STATIC_USB_BACKEND)) == + } +#ifndef FUZZING + else if (strncmp(name, STATIC_USB_BACKEND, strlen(STATIC_USB_BACKEND)) == 0) { *bf = usb_backend_functions(); } else if (strncmp(name, STATIC_HTTP_BACKEND, strlen(STATIC_HTTP_BACKEND)) == 0) { *bf = http_backend_functions(); - } else { + } +#else + else if (strncmp(name, STATIC_FUZZ_BACKEND, strlen(STATIC_FUZZ_BACKEND)) == + 0) { + *bf = fuzz_backend_functions(); + } +#endif + else { DBG_ERR("Failed finding backend named '%s'", name); return YHR_GENERIC_ERROR; } @@ -4804,6 +4828,12 @@ yh_rc yh_init_connector(const char *url, yh_connector **connector) { DBG_INFO("Loading http backend"); load_backend(HTTP_LIB, &backend, &bf); } +#ifdef FUZZING + else if (strncmp(url, YH_FUZZ_URL_SCHEME, strlen(YH_FUZZ_URL_SCHEME)) == 0) { + DBG_INFO("Loading fuzzing backend"); + load_backend(STATIC_FUZZ_BACKEND, &backend, &bf); + } +#endif if (bf == NULL) { DBG_ERR("Failed loading the backend"); return YHR_GENERIC_ERROR; diff --git a/lib/yubihsm.h b/lib/yubihsm.h index 60c870c6e..077b71435 100644 --- a/lib/yubihsm.h +++ b/lib/yubihsm.h @@ -88,7 +88,12 @@ /// Length of host challenge for authentication #define YH_HOST_CHAL_LEN 8 /// Maximum length of message buffer +#ifndef FUZZING #define YH_MSG_BUF_SIZE 3136 +#else +// in fuzzing builds make the data buffers smaller +#define YH_MSG_BUF_SIZE 100 +#endif /// Length of authentication keys #define YH_KEY_LEN 16 /// Device vendor ID @@ -125,6 +130,8 @@ #define YH_LOG_DIGEST_SIZE 16 /// URL scheme used for direct USB access #define YH_USB_URL_SCHEME "yhusb://" +/// URL scheme used for fuzzing builds +#define YH_FUZZ_URL_SCHEME "yhfuzz://" // Debug levels /// Debug level quiet. No messages printed out @@ -3181,4 +3188,12 @@ yh_rc yh_domains_to_string(uint16_t domains, char *string, size_t max_len); #pragma strict_gs_check(on) #endif +#ifdef FUZZING +#include +yh_rc compute_cryptogram(const uint8_t *key, uint16_t key_len, uint8_t type, + const uint8_t context[SCP_CONTEXT_LEN], uint16_t L, + uint8_t *key_out); +void increment_ctr(uint8_t *ctr, uint16_t len); +#endif + #endif diff --git a/pkcs11/CMakeLists.txt b/pkcs11/CMakeLists.txt index 3a20901b1..a3668a46b 100644 --- a/pkcs11/CMakeLists.txt +++ b/pkcs11/CMakeLists.txt @@ -100,3 +100,40 @@ install(FILES pkcs11f.h DESTINATION "${YUBIHSM_INSTALL_INC_DIR}/pkcs11") install(FILES pkcs11y.h DESTINATION "${YUBIHSM_INSTALL_INC_DIR}/pkcs11") add_subdirectory (tests) + +if (FUZZING) + include_directories( + ../lib/fuzz + ) + add_executable(fuzz_get_attribute_value + # harness sources + fuzz/fuzz_get_attribute_value.cc + ../lib/fuzz/yubihsm_fuzz.cc + # pkcs11 sources + ../common/hash.c + ../common/util.c + ../common/parsing.c + ../common/openssl-compat.c + util_pkcs11.c + yubihsm_pkcs11.c + list.c + debug_p11.c + # libyubihsm sources + ../aes_cmac/aes.c + ../aes_cmac/aes_cmac.c + ../common/hash.c + ../common/pkcs5.c + ../common/rand.c + ../common/ecdh.c + ../common/openssl-compat.c + ../lib/error.c + ../lib/lib_util.c + ../lib/yubihsm.c + # cmdline sources + ${GGO_C} + ) + set_target_properties (fuzz_get_attribute_value PROPERTIES COMPILE_FLAGS "-DSTATIC -fsanitize=fuzzer") + set_target_properties (fuzz_get_attribute_value PROPERTIES LINK_FLAGS "-fsanitize=fuzzer") + target_link_libraries(fuzz_get_attribute_value ${LIBCRYPTO_LDFLAGS}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nested-anon-types") +endif (FUZZING) diff --git a/pkcs11/fuzz/fuzz_get_attribute_value.cc b/pkcs11/fuzz/fuzz_get_attribute_value.cc new file mode 100644 index 000000000..0b4896629 --- /dev/null +++ b/pkcs11/fuzz/fuzz_get_attribute_value.cc @@ -0,0 +1,234 @@ +#include +#include +#include + +#include "fuzzer/FuzzedDataProvider.h" +#include + +#include +#include + +#include "yubihsm_fuzz.h" + +extern "C" { +#include "pkcs11.h" +#include "yubihsm_pkcs11.h" + +uint8_t *backend_data; +size_t backend_data_len; +} + +yh_connector *connector; +CK_FUNCTION_LIST_PTR p11; +CK_SESSION_HANDLE session; +CK_OBJECT_HANDLE yh_pubkey, yh_privkey; + +#define ECDH_ATTRIBUTE_COUNT 2 + +static bool init_p11() { + CK_C_INITIALIZE_ARGS initArgs; + CK_RV rv; + + char config[] = "connector=yhfuzz://yubihsm_fuzz"; + // char config[] = "connector=yhfuzz://yubihsm_fuzz debug libdebug"; + + C_GetFunctionList(&p11); + + memset(&initArgs, 0, sizeof(initArgs)); + initArgs.pReserved = config; + + rv = p11->C_Initialize(&initArgs); + assert(rv == CKR_OK); + + return true; +} + +static void deinit_session() { + CK_RV rv; + + rv = p11->C_Logout(session); + assert(rv == CKR_OK); + + rv = p11->C_CloseSession(session); + assert(rv == CKR_OK); +} + +static void init_session() { + CK_RV rv; + char pin[20] = "0000"; + + strcat(pin, FUZZ_BACKEND_PASSWORD); + + memset(&session, 0, sizeof(session)); + + rv = p11->C_OpenSession(0, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, + &session); + assert(rv == CKR_OK); + + rv = p11->C_Login(session, CKU_USER, (CK_UTF8CHAR_PTR) pin, + (CK_ULONG) strlen(pin)); + assert(rv == CKR_OK); + + // rv = generate_ecdh_keypair(); + // assert(rv == CKR_OK); +}; + +static EVP_PKEY *generate_keypair_openssl() { + EVP_PKEY *pkey = NULL; + EC_KEY *eckey = NULL; + OpenSSL_add_all_algorithms(); + int eccgrp = OBJ_txt2nid("secp224r1"); + eckey = EC_KEY_new_by_curve_name(eccgrp); + if (!(EC_KEY_generate_key(eckey))) { + } else { + pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_EC_KEY(pkey, eckey)) { + } + } + return pkey; +} + +void populate_attribute_template(CK_ATTRIBUTE_PTR *attribute_array, + CK_ULONG attribute_count, + FuzzedDataProvider *fdp) { + CK_ATTRIBUTE_PTR new_array = new CK_ATTRIBUTE[attribute_count]; + memset(new_array, 0, sizeof(CK_ATTRIBUTE) * attribute_count); + + for (int i = 0; i < attribute_count; i++) { + uint8_t ulValueLen = fdp->ConsumeIntegral(); + + new_array[i].type = fdp->ConsumeIntegral(); + new_array[i].pValue = new uint8_t[ulValueLen]; + new_array[i].ulValueLen = ulValueLen; + } + + *attribute_array = new_array; +} + +void populate_derived_ecdh_key_template(CK_ATTRIBUTE_PTR *attribute_array, + FuzzedDataProvider *fdp) { + CK_ATTRIBUTE_PTR new_array = new CK_ATTRIBUTE[ECDH_ATTRIBUTE_COUNT]; + memset(new_array, 0, sizeof(CK_ATTRIBUTE) * ECDH_ATTRIBUTE_COUNT); + + uint8_t value_len = fdp->ConsumeIntegral(); + std::vector value = fdp->ConsumeBytes(value_len); + + new_array[0].type = CKA_VALUE_LEN; + new_array[0].ulValueLen = value_len; + new_array[0].pValue = new uint8_t[value_len]; + + memset(new_array[0].pValue, 0, value_len); + memcpy(new_array[0].pValue, &value[0], + std::min(value.size(), (size_t) value_len)); + + uint8_t label_len = fdp->ConsumeIntegral(); + + new_array[1].type = CKA_LABEL; + new_array[1].ulValueLen = label_len; + new_array[1].pValue = new uint8_t[label_len]; + + *attribute_array = new_array; +} + +void derive_ecdh_session_keys(uint8_t derived_key_count, + CK_ATTRIBUTE_PTR ecdh_attribute_array) { + + if (derived_key_count > 10) { + // artificial limitation on the number of derived keys + derived_key_count = 10; + } + + for (int i = 0; i < derived_key_count; i++) { + CK_OBJECT_HANDLE ecdh; + + CK_ECDH1_DERIVE_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.kdf = CKD_NULL; + params.pSharedData = NULL; + params.ulSharedDataLen = 0; + // TODO populate pPublicData and ulPublicDataLen from fuzzer generated data? + params.pPublicData = new uint8_t[50]; + params.ulPublicDataLen = 50; + + CK_MECHANISM mechanism; + memset(&mechanism, 0, sizeof(mechanism)); + mechanism.mechanism = CKM_ECDH1_DERIVE; + mechanism.pParameter = (void *) ¶ms; + mechanism.ulParameterLen = sizeof(params); + + p11->C_DeriveKey(session, &mechanism, yh_privkey, ecdh_attribute_array, + ECDH_ATTRIBUTE_COUNT, &ecdh); + + delete[] params.pPublicData; + } +} + +void free_attribute_template(CK_ATTRIBUTE_PTR attribute_array, + CK_ULONG attribute_count) { + for (unsigned int i = 0; i < attribute_count; i++) { + if (attribute_array[i].pValue != NULL) { + delete[] (uint8_t *) attribute_array[i].pValue; + } + } + delete[] attribute_array; +} + +extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { + typedef struct { + CK_ULONG attribute_count; + CK_OBJECT_HANDLE obj_handle; + uint8_t derived_ecdh_key_count; + } test_case_t; + + static bool p11_initialized = init_p11(); + + if (size < sizeof(test_case_t)) { + return 0; + } + + FuzzedDataProvider *fdp = new FuzzedDataProvider(data, size); + + test_case_t test_case; + memset(&test_case, 0, sizeof(test_case_t)); + test_case.attribute_count = fdp->ConsumeIntegral(); + test_case.obj_handle = fdp->ConsumeIntegral(); + test_case.derived_ecdh_key_count = fdp->ConsumeIntegral(); + + /* limit the number of request attributes to 10 + * this is an artificial limitation to make fuzzer iterations faster + */ + if (test_case.attribute_count > 10) { + test_case.attribute_count = 10; + } + + CK_ATTRIBUTE_PTR attribute_array; + CK_ATTRIBUTE_PTR ecdh_attribute_array; + populate_attribute_template(&attribute_array, test_case.attribute_count, fdp); + populate_derived_ecdh_key_template(&ecdh_attribute_array, fdp); + + // the rest of the data is used for responses sent back by the backend + std::vector backend_vector = fdp->ConsumeRemainingBytes(); + backend_data = &backend_vector[0]; + backend_data_len = backend_vector.size(); + + init_session(); + + /* objects of type ECDH_KEY_TYPE are treated differently by the + * C_GetAttributeValue logic in order to improve coverage, we derive several + * ECDH keys using C_DeriveKey + */ + derive_ecdh_session_keys(test_case.derived_ecdh_key_count, + ecdh_attribute_array); + + p11->C_GetAttributeValue(session, test_case.obj_handle, attribute_array, + test_case.attribute_count); + + deinit_session(); + free_attribute_template(attribute_array, test_case.attribute_count); + free_attribute_template(ecdh_attribute_array, ECDH_ATTRIBUTE_COUNT); + + delete fdp; + + fflush(stdout); + return 0; +} diff --git a/pkcs11/util_pkcs11.c b/pkcs11/util_pkcs11.c index bfb9a4816..c65b78618 100644 --- a/pkcs11/util_pkcs11.c +++ b/pkcs11/util_pkcs11.c @@ -1080,23 +1080,34 @@ bool create_session(yubihsm_pkcs11_slot *slot, CK_FLAGS flags, return list_append(&slot->pkcs11_sessions, &session); } -static void get_label_attribute(yh_object_descriptor *object, bool public, +static CK_RV get_label_attribute(yh_object_descriptor *object, bool public, pkcs11_meta_object *meta_object, CK_VOID_PTR value, CK_ULONG_PTR length) { if (meta_object != NULL && !public && meta_object->cka_label.len > 0) { + if (*length < meta_object->cka_label.len) { + return CKR_HOST_MEMORY; + } *length = meta_object->cka_label.len; memcpy(value, meta_object->cka_label.value, *length); } else if (meta_object != NULL && public && meta_object->cka_label_pubkey.len > 0) { + if (*length < meta_object->cka_label_pubkey.len) { + return CKR_HOST_MEMORY; + } *length = meta_object->cka_label_pubkey.len; memcpy(value, meta_object->cka_label_pubkey.value, *length); } else { - *length = strlen(object->label); + size_t label_len = strlen(object->label); + if (*length < label_len) { + return CKR_HOST_MEMORY; + } + *length = label_len; memcpy(value, object->label, *length); // NOTE(adma): we have seen some weird behvior with different // PKCS#11 tools. We decided not to add '\0' for now. This *seems* // to be a good solution ... } + return CKR_OK; } static void get_id_attribute(yh_object_descriptor *object, bool public, @@ -1281,7 +1292,7 @@ static CK_RV get_attribute_opaque(CK_ATTRIBUTE_TYPE type, break; case CKA_LABEL: - get_label_attribute(object, false, meta_object, value, length); + return get_label_attribute(object, false, meta_object, value, length); break; case CKA_ID: @@ -1362,7 +1373,7 @@ static CK_RV get_attribute_secret_key(CK_ATTRIBUTE_TYPE type, break; case CKA_LABEL: - get_label_attribute(object, false, meta_object, value, length); + return get_label_attribute(object, false, meta_object, value, length); break; // NOTE(adma): Key Objects attributes @@ -1579,7 +1590,7 @@ static CK_RV get_attribute_private_key(CK_ATTRIBUTE_TYPE type, break; case CKA_LABEL: - get_label_attribute(object, false, meta_object, value, length); + return get_label_attribute(object, false, meta_object, value, length); break; // NOTE(adma): Key Objects attributes @@ -2038,7 +2049,7 @@ static CK_RV get_attribute_public_key(CK_ATTRIBUTE_TYPE type, break; case CKA_LABEL: - get_label_attribute(object, true, meta_object, value, length); + return get_label_attribute(object, true, meta_object, value, length); break; // NOTE(adma): Key Objects attributes @@ -5920,7 +5931,16 @@ CK_RV populate_template(int type, void *object, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, yubihsm_pkcs11_session *session) { CK_RV rv = CKR_OK; +//#ifdef FUZZING +// /* TODO in fuzzing builds make the data buffers smaller +// * there are currently many locations in the code which make assumptions +// * that the size of the tmp buffer (and aliases) is large enough +// * decreasing the size now artificially will prevent the fuzzer from progressing well +// */ +// CK_BYTE tmp[20] = {0}; +//#else CK_BYTE tmp[8192] = {0}; +//#endif for (CK_ULONG i = 0; i < ulCount; i++) { DBG_INFO("Getting attribute 0x%lx", pTemplate[i].type); CK_ULONG len = sizeof(tmp); @@ -6037,7 +6057,12 @@ bool match_meta_attributes(yubihsm_pkcs11_session *session, uint16_t cka_id_len, uint8_t *cka_label, uint16_t cka_label_len) { CK_RV rv = CKR_OK; +#ifdef FUZZING + // in fuzzing builds make the data buffers smaller + CK_BYTE tmp[200] = {0}; +#else CK_BYTE tmp[8192] = {0}; +#endif CK_ULONG len = sizeof(tmp); if (cka_id_len > 0) { diff --git a/pkcs11/yubihsm_pkcs11.c b/pkcs11/yubihsm_pkcs11.c index b64961438..72b8e769f 100644 --- a/pkcs11/yubihsm_pkcs11.c +++ b/pkcs11/yubihsm_pkcs11.c @@ -5933,6 +5933,10 @@ CK_DEFINE_FUNCTION(CK_RV, C_DeriveKey) for (CK_ULONG i = 0; i < ulAttributeCount; i++) { switch (pTemplate[i].type) { case CKA_VALUE_LEN: + if (pTemplate[i].ulValueLen < sizeof(CK_ULONG)) { + rv = CKR_ATTRIBUTE_VALUE_INVALID; + goto c_drv_out; + } value_len = *((CK_ULONG *) pTemplate[i].pValue); break; case CKA_LABEL: @@ -6370,6 +6374,7 @@ CK_DEFINE_FUNCTION(CK_RV, C_LoginUser) list_iterate(&session->slot->pkcs11_sessions, login_sessions); populate_cache_with_data_opaques(session->slot); +#ifndef FUZZING yubihsm_pkcs11_object_desc *authkey_desc = _get_object_desc(session->slot, key_id, YH_AUTHENTICATION_KEY, 0xffff); if (authkey_desc == NULL) { @@ -6377,6 +6382,9 @@ CK_DEFINE_FUNCTION(CK_RV, C_LoginUser) goto c_l_out; } session->slot->authkey_domains = authkey_desc->object.domains; +#else + session->slot->authkey_domains = 0xffff; +#endif DOUT; diff --git a/resources/fuzzing/Docker.fuzzing_msan b/resources/fuzzing/Docker.fuzzing_msan new file mode 100644 index 000000000..30175392e --- /dev/null +++ b/resources/fuzzing/Docker.fuzzing_msan @@ -0,0 +1,83 @@ +FROM ubuntu:23.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt -y -q update +RUN apt -y -q install llvm-16 clang-16 lld-16 +RUN apt -y -q install cargo rustc + +RUN apt -y -q install build-essential cmake ninja-build pkg-config git + +RUN mkdir /llvm-msan +WORKDIR /llvm-msan + +RUN git clone -b llvmorg-16.0.6 --depth 1 https://github.com/llvm/llvm-project.git +WORKDIR /llvm-msan/llvm-project + +RUN cmake -GNinja -S llvm -B build-llvm \ + -DCMAKE_C_COMPILER=clang-16 \ + -DCMAKE_CXX_COMPILER=clang++-16 \ + -DLLVM_USE_LINKER=lld-16 \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_PROJECTS="clang;lld;clang-tools-extra" \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;compiler-rt;libunwind" \ + -DLLVM_INSTALL_TOOLCHAIN_ONLY=YES \ + -DLLVM_TARGETS_TO_BUILD="host" \ + -DCMAKE_INSTALL_PREFIX="/llvm-msan/install" \ + -DLLVM_ENABLE_LLVM_LIBC=YES \ + -DLLVM_ENABLE_LIBCXX=YES + +RUN cmake --build build-llvm +RUN cmake --install build-llvm + +RUN cmake -GNinja -S runtimes -B build-runtimes-msan \ + -DCMAKE_C_COMPILER=/llvm-msan/install/bin/clang \ + -DCMAKE_CXX_COMPILER=/llvm-msan/install/bin/clang++ \ + -DLLVM_USE_LINKER=/llvm-msan/install/bin/ld.lld \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;compiler-rt;libunwind" \ + -DLLVM_USE_SANITIZER=MemoryWithOrigins \ + -DLLVM_TARGETS_TO_BUILD="host" \ + -DCMAKE_INSTALL_PREFIX="/llvm-msan/install-runtimes-msan" \ + -DLLVM_ENABLE_LLVM_LIBC=YES \ + -DLLVM_ENABLE_LIBCXX=YES + +RUN cmake --build build-runtimes-msan +RUN cmake --install build-runtimes-msan + +ENV CC=/llvm-msan/install/bin/clang +ENV CXX=/llvm-msan/install/bin/clang++ + +ENV CCFLAGS="-fsanitize=memory -stdlib=libc++ -fuse-ld=/llvm-msan/install/bin/ld.lld -I/llvm-msan/install/include -I/llvm-msan/install/include/c++/v1 -I/llvm-msan/install/include/c++/v1/" +ENV CXXFLAGS="-fsanitize=memory -stdlib=libc++ -fuse-ld=/llvm-msan/install/bin/ld.lld -I/llvm-msan/install/include -I/llvm-msan/install/include/c++/v1 -I/llvm-msan/install/include/c++/v1/" +ENV LDFLAGS="-fsanitize=memory -stdlib=libc++ -lc++abi -lc++ -L/llvm-msan/install-runtimes-msan/lib" + +RUN mkdir /openssl-msan +WORKDIR /openssl-msan + +RUN git clone -b openssl-3.0.10 --depth 1 https://github.com/openssl/openssl.git +WORKDIR /openssl-msan/openssl + +RUN ./config --debug no-tests \ + -fsanitize=memory \ + -stdlib=libc++ -L/llvm-msan/install-runtimes-msan/lib \ + -I/llvm-msan/install-runtimes-msan/include \ + -I/llvm-msan/install-runtimes-msan/include/c++/v1 \ + --prefix=/openssl-msan/install \ + --openssldir=/openssl-msan/work \ + --libdir=lib \ + no-asm +RUN make -j6 LDCMD="${CXX} ${CXXFLAGS} ${LDFLAGS}" +RUN make install_sw + +ENV PKG_CONFIG_PATH=/openssl-msan/install/lib/pkgconfig + +RUN apt -y -q install libedit-dev libcurl4-openssl-dev libusb-1.0-0-dev libpcsclite-dev +RUN apt -y -q install gengetopt help2man + +ENV CCFLAGS="${CCFLAGS} -Wno-error=unused-command-line-argument -Wno-error=unused-variable -Wno-error=missing-prototypes" +ENV CXXFLAGS="${CXXFLAGS} -Wno-error=unused-command-line-argument -Wno-error=unused-variable -Wno-error=missing-prototypes" + +ENV MSAN_SYMBOLIZER_PATH=/llvm-msan/install/bin/llvm-symbolizer +ENV MSAN_OPTIONS="symbolize=1 symbol_line=1" + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a5e84055..0945c0642 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,7 +62,7 @@ add_definitions (-DVERSION="${yubihsm_shell_VERSION_MAJOR}.${yubihsm_shell_VERSI list(APPEND LCOV_REMOVE_PATTERNS "'${PROJECT_SOURCE_DIR}/src/cmdline.c'") add_executable (yubihsm-shell ${SOURCE}) -if (ENABLE_STATIC) +if (ENABLE_STATIC AND NOT FUZZING) add_executable (yubihsm-shell_static ${SOURCE}) set_target_properties (yubihsm-shell_static PROPERTIES COMPILE_FLAGS "-DSTATIC") target_link_libraries(yubihsm-shell_static