diff --git a/CMakeLists.txt b/CMakeLists.txt index 30e1117fae..a840ac1730 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1107,7 +1107,7 @@ if(BUILD_TESTING) add_custom_target(fips_specific_tests_if_any) endif() - # Add macho parser tests if FIPS and on MacOS + # Add relevant tests if FIPS and on MacOS if(FIPS AND APPLE) add_custom_target( macho_parser_tests @@ -1116,6 +1116,14 @@ if(BUILD_TESTING) DEPENDS test_macho_parser ) add_dependencies(fips_specific_tests_if_any macho_parser_tests) + + add_custom_target( + inject_hash_tests + COMMAND test_inject_hash + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}/util/fipstools/inject_hash/tests/ + DEPENDS test_inject_hash + ) + add_dependencies(fips_specific_tests_if_any inject_hash_tests) endif() # Read util/go_tests.txt into a CMake variable. diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index b4a9154b4d..967bbb426e 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -315,6 +315,12 @@ else() file(COPY ${GENERATE_CODE_ROOT}/err_data.c DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) endif() +# We need to create a object library off of the generated err_data.c in so we can mark it as a dependency for our C inject hash implementation +if(FIPS) + add_library(generated_err_data OBJECT err_data.c) + target_include_directories(generated_err_data PRIVATE ${PROJECT_SOURCE_DIR}/include) +endif() + set(DILITHIUM_SOURCES) if(ENABLE_DILITHIUM) set( @@ -585,7 +591,6 @@ add_library( decrepit/x509/x509_decrepit.c ${CRYPTO_ARCH_SOURCES} - ${CRYPTO_ARCH_OBJECTS} ) target_compile_definitions(crypto_objects PRIVATE BORINGSSL_IMPLEMENTATION) @@ -654,24 +659,35 @@ if(FIPS_SHARED) build_libcrypto(crypto $<TARGET_OBJECTS:generated_fipsmodule>) else() - # On Apple and Linux platforms inject_hash.go can parse libcrypto and inject + # On Apple and Linux platforms inject_hash.go (inject_hash for Apple) can parse libcrypto and inject # the hash directly into the final library. build_libcrypto(crypto $<TARGET_OBJECTS:fipsmodule>) if (APPLE) - set(INJECT_HASH_APPLE_FLAG "-apple") - endif() + # Add subdirectory that handles building a stripped-down version of AWS-LC for use in calculating and injecting the FIPS integrity hash + add_subdirectory(fips_hashing) + add_subdirectory(${PROJECT_SOURCE_DIR}/util/fipstools/inject_hash inject_hash) - add_custom_command( - TARGET crypto POST_BUILD - COMMAND ${GO_EXECUTABLE} run - ${PROJECT_SOURCE_DIR}/util/fipstools/inject_hash/inject_hash.go - -o $<TARGET_FILE:crypto> -in-object $<TARGET_FILE:crypto> ${INJECT_HASH_APPLE_FLAG} - # The DEPENDS argument to a POST_BUILD rule appears to be ignored. Thus - # go_executable isn't used (as it doesn't get built), but we list this - # dependency anyway in case it starts working in some CMake version. - DEPENDS ../util/fipstools/inject_hash/inject_hash.go - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + set(INJECT_HASH_APPLE_FLAG "-f") + add_custom_command( + TARGET crypto POST_BUILD + COMMAND inject_hash + -p $<TARGET_FILE:crypto> -o $<TARGET_FILE:crypto> ${INJECT_HASH_APPLE_FLAG} + DEPENDS inject_hash + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} ) + else() + add_custom_command( + TARGET crypto POST_BUILD + COMMAND ${GO_EXECUTABLE} run + ${PROJECT_SOURCE_DIR}/util/fipstools/inject_hash/go/inject_hash.go + -o $<TARGET_FILE:crypto> -in-object $<TARGET_FILE:crypto> ${INJECT_HASH_APPLE_FLAG} + # The DEPENDS argument to a POST_BUILD rule appears to be ignored. Thus + # go_executable isn't used (as it doesn't get built), but we list this + # dependency anyway in case it starts working in some CMake version. + DEPENDS ../util/fipstools/inject_hash/go/inject_hash.go + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) + endif() # On macOS 11 and higher on Apple Silicon, codesigning is mandatory for # binaries to run. This applies to both executables and dylibs. An ad-hoc diff --git a/crypto/fips_hashing/CMakeLists.txt b/crypto/fips_hashing/CMakeLists.txt new file mode 100644 index 0000000000..b826dc69ae --- /dev/null +++ b/crypto/fips_hashing/CMakeLists.txt @@ -0,0 +1,23 @@ +if(FIPS AND APPLE) + add_definitions(-DOPENSSL_NO_ASM=1) + remove_definitions(-DBORINGSSL_FIPS -DFIPS_ENTROPY_SOURCE_JITTER_CPU -DFIPS_ENTROPY_SOURCE_PASSIVE) + + add_library( + fips_hashing + + STATIC + + fips_hashing.c + + ../mem.c + ../thread_none.c + ../thread_pthread.c + + ../err/err.c + $<TARGET_OBJECTS:generated_err_data> + ../decrepit/ripemd/ripemd.c + ) + + SET_TARGET_PROPERTIES(fips_hashing PROPERTIES LINKER_LANGUAGE C) + target_include_directories(fips_hashing PUBLIC $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>) +endif() diff --git a/crypto/fips_hashing/fips_hashing.c b/crypto/fips_hashing/fips_hashing.c new file mode 100644 index 0000000000..620739fb3b --- /dev/null +++ b/crypto/fips_hashing/fips_hashing.c @@ -0,0 +1,16 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include "../fipsmodule/delocate.h" + +#include "../fipsmodule/evp/p_hmac.c" +#include "../fipsmodule/digest/digest.c" +#include "../fipsmodule/digest/digests.c" +#include "../fipsmodule/hmac/hmac.c" +#include "../fipsmodule/md4/md4.c" +#include "../fipsmodule/md5/md5.c" +#include "../fipsmodule/sha/keccak1600.c" +#include "../fipsmodule/sha/sha1.c" +#include "../fipsmodule/sha/sha256.c" +#include "../fipsmodule/sha/sha3.c" +#include "../fipsmodule/sha/sha512.c" diff --git a/crypto/fipsmodule/CMakeLists.txt b/crypto/fipsmodule/CMakeLists.txt index 220ef5d47b..2b99ebc76b 100644 --- a/crypto/fipsmodule/CMakeLists.txt +++ b/crypto/fipsmodule/CMakeLists.txt @@ -404,7 +404,7 @@ if(FIPS_DELOCATE) set_target_properties(bcm_hashunset PROPERTIES LINKER_LANGUAGE C) go_executable(inject_hash - boringssl.googlesource.com/boringssl/util/fipstools/inject_hash) + boringssl.googlesource.com/boringssl/util/fipstools/inject_hash/go) add_custom_command( OUTPUT bcm.o COMMAND ./inject_hash -o bcm.o -in-archive $<TARGET_FILE:bcm_hashunset> diff --git a/util/fipstools/CMakeLists.txt b/util/fipstools/CMakeLists.txt index 6a0d83efba..3947dca23b 100644 --- a/util/fipstools/CMakeLists.txt +++ b/util/fipstools/CMakeLists.txt @@ -7,5 +7,6 @@ if(FIPS AND BUILD_TESTING) target_link_libraries(test_fips crypto) target_include_directories(test_fips BEFORE PRIVATE ${PROJECT_BINARY_DIR}/symbol_prefix_include) + add_subdirectory(inject_hash/tests) add_subdirectory(inject_hash/macho_parser/tests) endif() diff --git a/util/fipstools/inject_hash/CMakeLists.txt b/util/fipstools/inject_hash/CMakeLists.txt new file mode 100644 index 0000000000..bb115168a3 --- /dev/null +++ b/util/fipstools/inject_hash/CMakeLists.txt @@ -0,0 +1,11 @@ +if(FIPS AND APPLE) + add_executable( + inject_hash + + inject_hash.c + inject_hash_lib.c + macho_parser/macho_parser.c + ) + target_link_libraries(inject_hash PUBLIC fips_hashing) + target_include_directories(inject_hash PRIVATE ${PROJECT_SOURCE_DIR}/include) +endif() diff --git a/util/fipstools/inject_hash/macho_parser/common.h b/util/fipstools/inject_hash/common.h similarity index 60% rename from util/fipstools/inject_hash/macho_parser/common.h rename to util/fipstools/inject_hash/common.h index d1d81fbaaa..4954c32f5f 100644 --- a/util/fipstools/inject_hash/macho_parser/common.h +++ b/util/fipstools/inject_hash/common.h @@ -3,6 +3,10 @@ #ifndef COMMON_H #define COMMON_H +#ifdef __cplusplus +extern "C" +{ +#endif #include <stdio.h> #include <stdlib.h> @@ -14,4 +18,11 @@ fprintf(stderr, "\n"); \ } while(0) +int inject_hash_no_write(const char *o_input, int apple_flag, + uint8_t **object_bytes, size_t *object_bytes_size); +int inject_hash(int argc, char *argv[]); + +#ifdef __cplusplus +} // extern "C" +#endif #endif diff --git a/util/fipstools/inject_hash/inject_hash.go b/util/fipstools/inject_hash/go/inject_hash.go similarity index 100% rename from util/fipstools/inject_hash/inject_hash.go rename to util/fipstools/inject_hash/go/inject_hash.go diff --git a/util/fipstools/inject_hash/inject_hash.c b/util/fipstools/inject_hash/inject_hash.c new file mode 100644 index 0000000000..ae165b38f3 --- /dev/null +++ b/util/fipstools/inject_hash/inject_hash.c @@ -0,0 +1,11 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include "common.h" + +int main(int argc, char *argv[]) { + if (!inject_hash(argc, argv)) { + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); +} diff --git a/util/fipstools/inject_hash/inject_hash_lib.c b/util/fipstools/inject_hash/inject_hash_lib.c new file mode 100644 index 0000000000..c6ed41fad2 --- /dev/null +++ b/util/fipstools/inject_hash/inject_hash_lib.c @@ -0,0 +1,355 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include <unistd.h> + +#include "common.h" +#include "macho_parser/macho_parser.h" + +#include <openssl/base.h> +#include <openssl/hmac.h> +#include <openssl/mem.h> + +static uint8_t* read_object(const char *filename, size_t *size) { + FILE *file = fopen(filename, "rb"); + uint8_t *object_bytes = NULL; + if (file == NULL) { + LOG_ERROR("Error opening file %s", filename); + goto end; + } + + fseek(file, 0, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + object_bytes = malloc(file_size); + if (object_bytes == NULL) { + LOG_ERROR("Error allocating object_bytes memory"); + goto end; + } + + *size = fread(object_bytes, 1, file_size, file); + if (*size != file_size) { + LOG_ERROR("Error reading from file %s", filename); + free(object_bytes); + object_bytes = NULL; + goto end; + } + +end: + fclose(file); + return object_bytes; +} + +static int write_object(const char *filename, uint8_t *bytes, size_t size) { + int ret = 0; + + FILE *file = fopen(filename, "wb"); + if (file == NULL) { + LOG_ERROR("Error opening file %s", filename); + goto end; + } + + size_t written = fwrite(bytes, sizeof(uint8_t), size, file); + if (written != size) { + LOG_ERROR("Error writing file %s", filename); + goto end; + } + + ret = 1; + +end: + fclose(file); + return ret; +} + +static uint32_t find_hash(uint8_t *object_bytes, size_t object_bytes_size, uint8_t *hash, size_t hash_size) { + uint8_t *ptr = memmem(object_bytes, object_bytes_size, hash, hash_size); + if (ptr == NULL) { + LOG_ERROR("Error finding hash in object"); + return 0; + } + + return ptr - object_bytes; +} + +static void size_to_little_endian_bytes(size_t size, uint8_t *result) { + for (int i = 0; i < 8; i++) { + result[i] = (size >> (i * 8)) & 0xFF; + } +} + +static int do_apple(const char *object_file, uint8_t **text_module, size_t *text_module_size, uint8_t **rodata_module, size_t *rodata_module_size) { + uint8_t *text_section = NULL; + size_t text_section_size; + uint32_t text_section_offset; + + uint8_t *rodata_section = NULL; + size_t rodata_section_size; + uint32_t rodata_section_offset; + + uint8_t *symbol_table = NULL; + size_t symbol_table_size; + + uint8_t *string_table = NULL; + size_t string_table_size; + + uint32_t text_start; + uint32_t text_end; + uint32_t rodata_start; + uint32_t rodata_end; + + machofile *macho = NULL; + + int ret = 0; + + macho = malloc(sizeof(machofile)); + if (macho == NULL) { + LOG_ERROR("Error allocating memory for machofile"); + goto end; + } + if (read_macho_file(object_file, macho)) { + text_section = get_macho_section_data(object_file, macho, "__text", &text_section_size, &text_section_offset); + if (text_section == NULL) { + LOG_ERROR("Error getting text_section"); + goto end; + } + // Not guaranteed to have a rodata section so we won't error out if this returns NULL + rodata_section = get_macho_section_data(object_file, macho, "__const", &rodata_section_size, &rodata_section_offset); + symbol_table = get_macho_section_data(object_file, macho, "__symbol_table", &symbol_table_size, NULL); + if (symbol_table == NULL) { + LOG_ERROR("Error getting symbol table"); + goto end; + } + string_table = get_macho_section_data(object_file, macho, "__string_table", &string_table_size, NULL); + if (string_table == NULL) { + LOG_ERROR("Error getting string table"); + goto end; + } + + if(!find_macho_symbol_index(symbol_table, symbol_table_size, string_table, string_table_size, "_BORINGSSL_bcm_text_start", &text_section_offset, &text_start)) { + LOG_ERROR("Could not find .text module start symbol in object"); + goto end; + } + if (!find_macho_symbol_index(symbol_table, symbol_table_size, string_table, string_table_size, "_BORINGSSL_bcm_text_end", &text_section_offset, &text_end)) { + LOG_ERROR("Could not find .text module end symbol in object"); + goto end; + } + find_macho_symbol_index(symbol_table, symbol_table_size, string_table, string_table_size, "_BORINGSSL_bcm_rodata_start", &rodata_section_offset, &rodata_start); + find_macho_symbol_index(symbol_table, symbol_table_size, string_table, string_table_size, "_BORINGSSL_bcm_rodata_end", &rodata_section_offset, &rodata_end); + + + if ((!rodata_start) != (!rodata_section)) { + LOG_ERROR(".rodata start marker inconsistent with rodata section presence"); + goto end; + } + + if ((!rodata_start) != (!rodata_end)) { + LOG_ERROR(".rodata marker presence inconsistent"); + goto end; + } + + if (text_start > text_section_size || text_start > text_end || text_end > text_section_size) { + LOG_ERROR("Invalid .text module boundaires: start %x, end: %x, max: %zx", rodata_start, rodata_end, rodata_section_size); + goto end; + } + + // Get text and rodata modules from text_section/rodata_section using obtained indices + *text_module_size = text_end - text_start; + *text_module = malloc(*text_module_size); + if (*text_module == NULL) { + LOG_ERROR("Error allocating memory for text_module"); + goto end; + } + memcpy(*text_module, text_section + text_start, *text_module_size); + + if (rodata_section != NULL) { + *rodata_module_size = rodata_end - rodata_start; + *rodata_module = malloc(*rodata_module_size); + if (*rodata_module == NULL) { + LOG_ERROR("Error allocating memory for rodata module"); + goto end; + } + memcpy(*rodata_module, rodata_section + rodata_start, *rodata_module_size); + } + ret = 1; + } else { + LOG_ERROR("Error reading Mach-O file"); + goto end; + } + +end: + free(text_section); + free(rodata_section); + free(symbol_table); + free(string_table); + free(macho->sections); + free(macho); + + return ret; +} + +int inject_hash_no_write(const char *o_input, int apple_flag, + uint8_t **object_bytes, size_t *object_bytes_size) { + int ret = 0; + + uint8_t uninit_hash[] = { + 0xae, 0x2c, 0xea, 0x2a, 0xbd, 0xa6, 0xf3, 0xec, + 0x97, 0x7f, 0x9b, 0xf6, 0x94, 0x9a, 0xfc, 0x83, + 0x68, 0x27, 0xcb, 0xa0, 0xa0, 0x9f, 0x6b, 0x6f, + 0xde, 0x52, 0xcd, 0xe2, 0xcd, 0xff, 0x31, 0x80, + }; + + uint8_t *text_module = NULL; + size_t text_module_size; + uint8_t *rodata_module = NULL; + size_t rodata_module_size; + + uint8_t *calculated_hash = NULL; + uint8_t length_bytes[8]; + + uint32_t hash_index; + + + *object_bytes = read_object(o_input, object_bytes_size); + if (object_bytes == NULL) { + LOG_ERROR("Error reading object bytes from %s", o_input); + goto end; + } + + if (apple_flag == 1) { + if (!do_apple(o_input, &text_module, &text_module_size, &rodata_module, &rodata_module_size)) { + LOG_ERROR("Error getting text and rodata modules from Apple OS object"); + goto end; + } + } + // TODO: else handle linux, not needed for Apple platforms + + if (text_module == NULL || rodata_module == NULL) { + LOG_ERROR("Error getting text or rodata section"); + goto end; + } + + hash_index = find_hash(*object_bytes, *object_bytes_size, uninit_hash, sizeof(uninit_hash)); + if (!hash_index) { + LOG_ERROR("Error finding hash"); + goto end; + } + + uint8_t zero_key[64] = {0}; + HMAC_CTX ctx; + if (!HMAC_Init(&ctx, &zero_key, sizeof(zero_key), EVP_sha256())) { + LOG_ERROR("Error in HMAC_Init()"); + goto end; + } + + if(rodata_module != NULL) { + size_to_little_endian_bytes(text_module_size, length_bytes); + if (!HMAC_Update(&ctx, length_bytes, 8)) { + LOG_ERROR("Error in HMAC_Update() of textModuleSize"); + goto end; + } + if (!HMAC_Update(&ctx, text_module, text_module_size)) { + LOG_ERROR("Error in HMAC_Update() of textModule"); + goto end; + } + size_to_little_endian_bytes(rodata_module_size, length_bytes); + if (!HMAC_Update(&ctx, length_bytes, 8)) { + LOG_ERROR("Error in HMAC_Update() of rodataModuleSize"); + goto end; + } + if (!HMAC_Update(&ctx, rodata_module, rodata_module_size)) { + LOG_ERROR("Error in HMAC_Update() of rodataModule"); + goto end; + } + } else { + if (!HMAC_Update(&ctx, text_module, text_module_size)) { + LOG_ERROR("Error in HMAC_Update() of textModule"); + goto end; + } + } + + calculated_hash = malloc(HMAC_size(&ctx)); + if (calculated_hash == NULL) { + LOG_ERROR("Error allocating memory for calculated hash"); + goto end; + } + unsigned int calculated_hash_len; + if (!HMAC_Final(&ctx, calculated_hash, &calculated_hash_len)) { + LOG_ERROR("Error in HMAC_Final()"); + goto end; + } + + // Print calculated hash for reference + printf("Calculated hash: "); + for (unsigned int i = 0; i < calculated_hash_len; i++) { + printf("%02X", calculated_hash[i]); + } + printf("\n"); + + memcpy(*object_bytes + hash_index, calculated_hash, calculated_hash_len); + + ret = 1; + +end: + free(text_module); + free(rodata_module); + free(calculated_hash); + return ret; +} + +int inject_hash(int argc, char *argv[]) { + char *ar_input = NULL; + char *o_input = NULL; + char *out_path = NULL; + int apple_flag = 0; + + int ret = 0; + + uint8_t *object_bytes = NULL; + size_t object_bytes_size; + + int opt; + while ((opt = getopt(argc, argv, "a:o:p:f")) != -1) { + switch(opt) { + case 'a': + ar_input = optarg; + break; + case 'o': + o_input = optarg; + break; + case 'p': + out_path = optarg; + break; + case 'f': + apple_flag = 1; + break; + case '?': + default: + LOG_ERROR("Usage: %s [-a in-archive] [-o in-object] [-p out-path] [-f apple-flag]", argv[0]); + goto end; + } + } + + if ((ar_input == NULL && o_input == NULL) || out_path == NULL) { + LOG_ERROR("Usage: %s [-a in-archive] [-o in-object] [-p out-path] [-f apple-flag]", argv[0]); + LOG_ERROR("Note that either the -a or -o option and -p options are required."); + goto end; + } + + if (!inject_hash_no_write(o_input, apple_flag, &object_bytes, + &object_bytes_size)) { + LOG_ERROR("Error encountered while injecting hash"); + goto end; + } + + if (!write_object(out_path, object_bytes, object_bytes_size)) { + LOG_ERROR("Error writing file %s", out_path); + goto end; + } + + ret = 1; +end: + free(object_bytes); + return ret; +} diff --git a/util/fipstools/inject_hash/macho_parser/macho_parser.c b/util/fipstools/inject_hash/macho_parser/macho_parser.c index b24c343403..94272eedc6 100644 --- a/util/fipstools/inject_hash/macho_parser/macho_parser.c +++ b/util/fipstools/inject_hash/macho_parser/macho_parser.c @@ -3,7 +3,7 @@ #include <stdint.h> -#include "common.h" +#include "../common.h" #include "macho_parser.h" #define TEXT_INDEX 0 @@ -106,15 +106,13 @@ int read_macho_file(const char *filename, machofile *macho) { if (file != NULL) { fclose(file); } + if (!ret) { + free(macho->sections); + macho->sections = NULL; + } return ret; } -void free_macho_file(machofile *macho) { - free(macho->sections); - free(macho); - macho = NULL; -} - uint8_t* get_macho_section_data(const char *filename, machofile *macho, const char *section_name, size_t *size, uint32_t *offset) { FILE *file = NULL; uint8_t *ret = NULL; @@ -174,9 +172,9 @@ uint8_t* get_macho_section_data(const char *filename, machofile *macho, const ch return ret; } -uint32_t find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table_size, uint8_t *string_table_data, size_t string_table_size, const char *symbol_name, uint32_t *base) { +int find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table_size, uint8_t *string_table_data, size_t string_table_size, const char *symbol_name, uint32_t *base, uint32_t *index) { char* string_table = NULL; - uint32_t ret = 0; + int ret = 0; if (symbol_table_data == NULL || string_table_data == NULL) { LOG_ERROR("Symbol and string table pointers cannot be null to find the symbol index"); @@ -191,12 +189,25 @@ uint32_t find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table memcpy(string_table, string_table_data, string_table_size); int found = 0; - size_t index = 0; for (size_t i = 0; i < symbol_table_size / sizeof(struct nlist_64); i++) { struct nlist_64 *symbol = (struct nlist_64 *)(symbol_table_data + i * sizeof(struct nlist_64)); + + // Skip debugging symbols + // + // #define N_STAB 0xe0 /* if any of these bits set, a symbolic debugging entry */ + // + // "Only symbolic debugging entries have some of the N_STAB bits set and if any of these bits are set then it is + // a symbolic debugging entry (a stab). In which case then the values of the n_type field (the entire field) + // are given in <mach-o/stab.h>" + // + // https://github.com/apple-oss-distributions/xnu/blob/main/EXTERNAL_HEADERS/mach-o/nlist.h + if (symbol->n_type & N_STAB) { + continue; + } + if (strcmp(symbol_name, &string_table[symbol->n_un.n_strx]) == 0) { if (found == 0) { - index = symbol->n_value; + *index = symbol->n_value; found = 1; } else { LOG_ERROR("Duplicate symbol %s found", symbol_name); @@ -209,9 +220,9 @@ uint32_t find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table goto end; } if (base != NULL) { - index = index - *base; + *index = *index - *base; } - ret = index; + ret = 1; end: free(string_table); diff --git a/util/fipstools/inject_hash/macho_parser/macho_parser.h b/util/fipstools/inject_hash/macho_parser/macho_parser.h index a11463fc87..0d788ebac6 100644 --- a/util/fipstools/inject_hash/macho_parser/macho_parser.h +++ b/util/fipstools/inject_hash/macho_parser/macho_parser.h @@ -24,20 +24,19 @@ typedef struct { } machofile; // read_macho_file reads a Mach-O file [in] and populates a machofile struct [out] with its contents. +// Assume that the machofile pointer provided is already allocated but is otherwise untouched. // It returns 0 on failure, 1 on success. int read_macho_file(const char *filename, machofile *macho); -// free_macho_file frees the memory allocated to a machofile struct [in] -void free_macho_file(machofile *macho); - // get_macho_section_data retrieves data from a specific section [in] the provided Mach-O file [in]. // In addition to returning a pointer to the retrieved data, or NULL if it doesn't find said section, // it also populates the size [out] & offset [out] pointers provided they are not NULL. uint8_t* get_macho_section_data(const char* filename, machofile *macho, const char *section_name, size_t *size, uint32_t *offset); // find_macho_symbol_index finds the index of a symbol [in] in the Mach-O file's [in] symbol table. -// It returns the index on success, and 0 on failure. -uint32_t find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table_size, uint8_t *string_table_data, size_t string_table_size, const char *symbol_name, uint32_t *base); +// It stores the result in index [out]. +// It returns 1 on success, and 0 on failure. +int find_macho_symbol_index(uint8_t *symbol_table_data, size_t symbol_table_size, uint8_t *string_table_data, size_t string_table_size, const char *symbol_name, uint32_t *base, uint32_t *index); #ifdef __cplusplus } // extern "C" diff --git a/util/fipstools/inject_hash/macho_parser/tests/macho_tests.cc b/util/fipstools/inject_hash/macho_parser/tests/macho_tests.cc index 38c921ba12..c46e527533 100644 --- a/util/fipstools/inject_hash/macho_parser/tests/macho_tests.cc +++ b/util/fipstools/inject_hash/macho_parser/tests/macho_tests.cc @@ -3,7 +3,7 @@ #include <assert.h> -#include "../common.h" +#include "../../common.h" #include "macho_tests.h" #define TEST_FILE "test_macho" @@ -60,7 +60,10 @@ TEST_F(MachoTestFixture, TestFindMachoSymbolIndex) { symbol_table.reset(get_macho_section_data(TEST_FILE, expected_macho, "__symbol_table", &symbol_table_size, NULL)); string_table.reset(get_macho_section_data(TEST_FILE, expected_macho, "__string_table", &string_table_size, NULL)); - uint32_t symbol1_index = find_macho_symbol_index(symbol_table.get(), symbol_table_size, string_table.get(), string_table_size, "symbol1", NULL); + uint32_t symbol1_index; + if (!find_macho_symbol_index(symbol_table.get(), symbol_table_size, string_table.get(), string_table_size, "symbol1", NULL, &symbol1_index)) { + LOG_ERROR("Could not find symbol index"); + } ASSERT_EQ(symbol1_index, expected_symbol1_ind); } diff --git a/util/fipstools/inject_hash/macho_parser/tests/macho_tests.h b/util/fipstools/inject_hash/macho_parser/tests/macho_tests.h index 33783f60a5..d5c58f29f2 100644 --- a/util/fipstools/inject_hash/macho_parser/tests/macho_tests.h +++ b/util/fipstools/inject_hash/macho_parser/tests/macho_tests.h @@ -269,7 +269,8 @@ class MachoTestFixture : public ::testing::Test { } static void TearDownTestSuite() { - free_macho_file(expected_macho); + free(expected_macho->sections); + free(expected_macho); free(expected_symtab); if (remove(TEST_FILE) != 0) { LOG_ERROR("Error deleting %s", TEST_FILE); diff --git a/util/fipstools/inject_hash/tests/CMakeLists.txt b/util/fipstools/inject_hash/tests/CMakeLists.txt new file mode 100644 index 0000000000..39b071d14a --- /dev/null +++ b/util/fipstools/inject_hash/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +if(FIPS AND APPLE) + add_subdirectory(test_libs) + + add_executable( + test_inject_hash + + inject_hash_tests.cc + ../inject_hash_lib.c + ../macho_parser/macho_parser.c + ) + add_dependencies( + test_inject_hash + + good_lib + bad_hash_lib + bad_marker_lib + ) + + target_link_libraries( + test_inject_hash + + test_support_lib + boringssl_gtest_main + ) +endif() diff --git a/util/fipstools/inject_hash/tests/inject_hash_tests.cc b/util/fipstools/inject_hash/tests/inject_hash_tests.cc new file mode 100644 index 0000000000..6a53ddb28c --- /dev/null +++ b/util/fipstools/inject_hash/tests/inject_hash_tests.cc @@ -0,0 +1,66 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include <assert.h> +#include <gtest/gtest.h> +#include <iostream> +#include <string> + +#include "../common.h" + +// Paths below are based on expected location of library artifacts relative +// to the location of this test executable. +// TODO: change this based on platform, we only care about Apple for now +#define GOOD_LIB_NAME "test_libs/libgood_lib.dylib" +#define BAD_HASH_LIB_NAME "test_libs/libbad_hash_lib.dylib" +#define BAD_MARKER_LIB_NAME "test_libs/libbad_marker_lib.dylib" + +class InjectHashTestFixture : public ::testing::Test { +protected: + const std::string good_lib_filename = GOOD_LIB_NAME; + const std::string bad_hash_lib_filename = BAD_HASH_LIB_NAME; + const std::string bad_marker_lib_filename = BAD_MARKER_LIB_NAME; +}; + +TEST_F(InjectHashTestFixture, TestGoodLib) { + std::unique_ptr<uint8_t> object_bytes(nullptr); + size_t object_bytes_size; + + uint8_t *object_bytes_ptr = object_bytes.get(); + + ASSERT_EQ(1, inject_hash_no_write(good_lib_filename.c_str(), 1, &object_bytes_ptr, &object_bytes_size)); +} + +TEST_F(InjectHashTestFixture, TestBadHashLib) { + std::unique_ptr<uint8_t> object_bytes(nullptr); + size_t object_bytes_size; + + uint8_t *object_bytes_ptr = object_bytes.get(); + + int inject_hash_ret; + testing::internal::CaptureStderr(); + + inject_hash_ret = inject_hash_no_write(bad_hash_lib_filename.c_str(), 1, + &object_bytes_ptr, &object_bytes_size); + std::string captured_stderr = testing::internal::GetCapturedStderr(); + + ASSERT_EQ(0, inject_hash_ret); + EXPECT_TRUE(captured_stderr.find("Error finding hash") != std::string::npos); +} + +TEST_F(InjectHashTestFixture, TestBadMarkerLib) { + std::unique_ptr<uint8_t> object_bytes(nullptr); + size_t object_bytes_size; + + uint8_t *object_bytes_ptr = object_bytes.get(); + + int inject_hash_ret; + testing::internal::CaptureStderr(); + + inject_hash_ret = inject_hash_no_write(bad_marker_lib_filename.c_str(), 1, + &object_bytes_ptr, &object_bytes_size); + std::string captured_stderr = testing::internal::GetCapturedStderr(); + + ASSERT_EQ(0, inject_hash_ret); + EXPECT_TRUE(captured_stderr.find("Could not find .text module start symbol in object") != std::string::npos); +} diff --git a/util/fipstools/inject_hash/tests/test_libs/CMakeLists.txt b/util/fipstools/inject_hash/tests/test_libs/CMakeLists.txt new file mode 100644 index 0000000000..f253dc75bf --- /dev/null +++ b/util/fipstools/inject_hash/tests/test_libs/CMakeLists.txt @@ -0,0 +1,19 @@ +if(FIPS AND APPLE) + set(INJECT_HASH_TEST_LIB_COMPILE_OPTIONS + "-Wno-unused-function" + "-Wno-missing-prototypes" + ) + + function(create_test_lib name) + add_library(${name} testlib.c) + target_compile_options(${name} PRIVATE ${INJECT_HASH_TEST_LIB_COMPILE_OPTIONS}) + + if(NOT "${ARGN}" STREQUAL "") + target_compile_definitions(${name} PRIVATE ${ARGN}) + endif() + endfunction() + + create_test_lib(good_lib) + create_test_lib(bad_hash_lib "-DBAD_HASH") + create_test_lib(bad_marker_lib "-DBAD_MARKER") +endif() diff --git a/util/fipstools/inject_hash/tests/test_libs/testlib.c b/util/fipstools/inject_hash/tests/test_libs/testlib.c new file mode 100644 index 0000000000..2ebbac06d9 --- /dev/null +++ b/util/fipstools/inject_hash/tests/test_libs/testlib.c @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 OR ISC + +#include <stdio.h> +#include <stdint.h> + +#ifndef BAD_MARKER +const uint8_t *BORINGSSL_bcm_text_start(void) { + return NULL; +} +#endif +const uint8_t *BORINGSSL_bcm_text_end(void){ + return NULL; +} + + +const uint8_t BORINGSSL_bcm_text_hash[32] = { + 0xae, 0x2c, 0xea, 0x2a, 0xbd, 0xa6, 0xf3, 0xec, 0x97, 0x7f, 0x9b, + 0xf6, 0x94, 0x9a, 0xfc, 0x83, 0x68, 0x27, 0xcb, 0xa0, 0xa0, 0x9f, + 0x6b, 0x6f, 0xde, 0x52, 0xcd, 0xe2, 0xcd, 0xff, 0x31, +#ifndef BAD_HASH + 0x80, +#else + 0X81, +#endif +}; + +const uint8_t BORINGSSL_bcm_rodata_start[16] = + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; +const uint8_t BORINGSSL_bcm_rodata_end[16] = + {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};