diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/fih/CMakeLists.txt | 31 | ||||
-rw-r--r-- | lib/fih/inc/fih.h | 474 | ||||
-rw-r--r-- | lib/fih/src/fih.c | 107 |
3 files changed, 612 insertions, 0 deletions
diff --git a/lib/fih/CMakeLists.txt b/lib/fih/CMakeLists.txt new file mode 100644 index 0000000000..e9e1a9c676 --- /dev/null +++ b/lib/fih/CMakeLists.txt @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------------- +# Copyright (c) 2020-2021, Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +#------------------------------------------------------------------------------- + +cmake_minimum_required(VERSION 3.15) + +add_library(tfm_fih STATIC) + +target_sources(tfm_fih + PRIVATE + src/fih.c +) + +target_include_directories(tfm_fih + PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc> +) + +target_link_libraries(tfm_fih + PRIVATE + platform_s +) + +target_compile_definitions(tfm_fih + PUBLIC + TFM_FIH_PROFILE_${TFM_FIH_PROFILE} + $<$<NOT:$<STREQUAL:${TFM_FIH_PROFILE},OFF>>:TFM_FIH_PROFILE_ON> +) diff --git a/lib/fih/inc/fih.h b/lib/fih/inc/fih.h new file mode 100644 index 0000000000..5fd7b70a42 --- /dev/null +++ b/lib/fih/inc/fih.h @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2020-2021, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#ifndef __FAULT_INJECTION_HARDENING_H__ +#define __FAULT_INJECTION_HARDENING_H__ + +#include <stddef.h> +#include <stdint.h> + +/* + * Fault injection mitigation library. + * + * Has support for different measures, which can either be enabled/disabled + * separately or by defining one of the TFM_FIH_PROFILEs. + * + * NOTE: It is not guaranteed that these constructs against fault injection + * attacks can be preserved in all compilers. + * + * FIH_ENABLE_DOUBLE_VARS makes critical variables into a tuple (x, x ^ msk). + * Then the correctness of x can be checked by XORing the two tuple values + * together. This also means that comparisons between fih_ints can be verified + * by doing x == y && x_msk == y_msk. + * + * FIH_ENABLE_GLOBAL_FAIL makes all while(1) failure loops redirect to a global + * failure loop. This loop has mitigations against loop escapes / unlooping. + * This also means that any unlooping won't immediately continue executing the + * function that was executing before the failure. + * + * FIH_ENABLE_CFI (Control Flow Integrity) creates a global counter that is + * incremented before every FIH_CALL of vulnerable functions. On the function + * return the counter is decremented, and after the return it is verified that + * the counter has the same value as before this process. This can be used to + * verify that the function has actually been called. This protection is + * intended to discover that important functions are called in an expected + * sequence and none of them is missed due to an instruction skip which could + * be a result of glitching attack. It does not provide protection against ROP + * or JOP attacks. + * + * FIH_ENABLE_DELAY causes random delays. This makes it hard to cause faults + * precisely. It requires an RNG. An mbedtls integration is provided in + * fault_injection_hardening_delay_mbedtls.h, but any RNG that has an entropy + * source can be used by implementing the fih_delay_random_uchar function. + * + * The basic call pattern is: + * + * fih_int fih_rc = FIH_FAILURE; + * FIH_CALL(vulnerable_function, fih_rc, arg1, arg2); + * if (fih_not_eq(fih_rc, FIH_SUCCESS)) { + * error_handling(); + * } + * + * If a fault injection is detected, call FIH_PANIC to trap the execution. + * + * Note that any function called by FIH_CALL must only return using FIH_RET, + * as otherwise the CFI counter will not be decremented and the CFI check will + * fail causing a panic. + */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#undef FIH_ENABLE_GLOBAL_FAIL +#undef FIH_ENABLE_CFI +#undef FIH_ENABLE_DOUBLE_VARS +#undef FIH_ENABLE_DELAY + +#ifdef TFM_FIH_PROFILE_ON +#if defined(TFM_FIH_PROFILE_LOW) +#define FIH_ENABLE_GLOBAL_FAIL +#define FIH_ENABLE_CFI + +#elif defined(TFM_FIH_PROFILE_MEDIUM) +#define FIH_ENABLE_DOUBLE_VARS +#define FIH_ENABLE_GLOBAL_FAIL +#define FIH_ENABLE_CFI + +#elif defined(TFM_FIH_PROFILE_HIGH) +#define FIH_ENABLE_DELAY /* Requires an hardware entropy source */ +#define FIH_ENABLE_DOUBLE_VARS +#define FIH_ENABLE_GLOBAL_FAIL +#define FIH_ENABLE_CFI + +#else +#error "Invalid FIH Profile configuration" +#endif /* TFM_FIH_PROFILE */ + +#define FIH_TRUE 0xC35A +#define FIH_FALSE 0x0 + +#ifdef FIH_ENABLE_DOUBLE_VARS +#define FIH_POSITIVE_VALUE 0x5555AAAA +#define FIH_NEGATIVE_VALUE 0xAAAA5555 + +/* + * A volatile mask is used to prevent compiler optimization - the mask is xored + * with the variable to create the backup and the integrity can be checked with + * another xor. The mask value doesn't _really_ matter that much, as long as + * it has reasonably high hamming weight. + */ +#define _FIH_MASK_VALUE 0xA5C35A3C + +/* + * All ints are replaced with two int - the normal one and a backup which is + * XORed with the mask. + */ +typedef volatile struct { + volatile int32_t val; + volatile int32_t msk; +} fih_int; + +#define FIH_INT_INIT(x) {(x), (x) ^ _FIH_MASK_VALUE} +#else /* FIH_ENABLE_DOUBLE_VARS */ +#define FIH_POSITIVE_VALUE 0x0 +#define FIH_NEGATIVE_VALUE 0xAAAA5555 + +typedef volatile int32_t fih_int; + +#define FIH_INT_INIT(x) (x) +#endif /* FIH_ENABLE_DOUBLE_VARS */ + +extern fih_int FIH_SUCCESS; +extern fih_int FIH_FAILURE; + +#ifdef FIH_ENABLE_GLOBAL_FAIL +/* + * Global failure handler - more resistant to unlooping. noinline and used are + * used to prevent optimization. + * + * NOTE + * This failure handler shall be used as FIH specific error handling to capture + * FI attacks. Error handling in SPM and SP should be enhanced respectively. + */ +__attribute__((noinline)) __attribute__((used)) void fih_panic_loop(void); +#define FIH_PANIC fih_panic_loop() +#else /* FIH_ENABLE_GLOBAL_FAIL */ +#define FIH_PANIC while (1) {} +#endif /* FIH_ENABLE_GLOBAL_FAIL */ + +/* + * NOTE + * For functions to be inlined outside their compilation unit they have to + * have the body in the header file. This is required as function calls are easy + * to skip. + */ +#ifdef FIH_ENABLE_DELAY +/** + * @brief Set up the RNG for use with random delays. Called once at startup. + */ +void fih_delay_init(void); + +/** + * Get a random uint8_t value from an RNG seeded with an entropy source. + * + * NOTE + * Do not directly call this function. + */ +uint8_t fih_delay_random(void); + +/* Delaying logic, with randomness from a CSPRNG */ +__attribute__((always_inline)) inline +void fih_delay(void) +{ + uint32_t i = 0; + volatile uint32_t delay = FIH_NEGATIVE_VALUE; + volatile uint32_t counter = 0; + + delay = fih_delay_random(); + + if (delay == FIH_NEGATIVE_VALUE) { + FIH_PANIC; + } + + delay &= 0xFF; + + for (i = 0; i < delay; i++) { + counter++; + } + + if (counter != delay) { + FIH_PANIC; + } +} +#else /* FIH_ENABLE_DELAY */ +#define fih_delay_init() + +#define fih_delay() +#endif /* FIH_ENABLE_DELAY */ + +#ifdef FIH_ENABLE_DOUBLE_VARS +__attribute__((always_inline)) inline +void fih_int_validate(fih_int x) +{ + if (x.val != (x.msk ^ _FIH_MASK_VALUE)) { + FIH_PANIC; + } +} + +/* Convert a fih_int to an int. Validate for tampering. */ +__attribute__((always_inline)) inline +int32_t fih_int_decode(fih_int x) +{ + fih_int_validate(x); + return x.val; +} + +/* Convert an int to a fih_int, can be used to encode specific error codes. */ +__attribute__((always_inline)) inline +fih_int fih_int_encode(int32_t x) +{ + fih_int ret = {x, x ^ _FIH_MASK_VALUE}; + return ret; +} + +/* Standard equality. If A == B then 1, else 0 */ +__attribute__((always_inline)) inline +int32_t fih_eq(fih_int x, fih_int y) +{ + volatile int32_t rc1 = FIH_FALSE; + volatile int32_t rc2 = FIH_FALSE; + + fih_int_validate(x); + fih_int_validate(y); + + if (x.val == y.val) { + rc1 = FIH_TRUE; + } + + fih_delay(); + + if (x.msk == y.msk) { + rc2 = FIH_TRUE; + } + + fih_delay(); + + if (rc1 != rc2) { + FIH_PANIC; + } + + return rc1; +} + +__attribute__((always_inline)) inline +int32_t fih_not_eq(fih_int x, fih_int y) +{ + volatile int32_t rc1 = FIH_FALSE; + volatile int32_t rc2 = FIH_FALSE; + + fih_int_validate(x); + fih_int_validate(y); + + if (x.val != y.val) { + rc1 = FIH_TRUE; + } + + fih_delay(); + + if (x.msk != y.msk) { + rc2 = FIH_TRUE; + } + + fih_delay(); + + if (rc1 != rc2) { + FIH_PANIC; + } + + return rc1; +} +#else /* FIH_ENABLE_DOUBLE_VARS */ +/* NOOP */ +#define fih_int_validate(x) + +/* NOOP */ +#define fih_int_decode(x) (x) + +/* NOOP */ +#define fih_int_encode(x) (x) + +__attribute__((always_inline)) inline +int32_t fih_eq(fih_int x, fih_int y) +{ + volatile int32_t rc = FIH_FALSE; + + if (x == y) { + rc = FIH_TRUE; + } + + fih_delay(); + + if (x != y) { + rc = FIH_FALSE; + } + + return rc; +} + +__attribute__((always_inline)) inline +int32_t fih_not_eq(fih_int x, fih_int y) +{ + volatile int32_t rc = FIH_FALSE; + + if (x != y) { + rc = FIH_TRUE; + } + + fih_delay(); + + if (x == y) { + rc = FIH_FALSE; + } + + return rc; +} +#endif /* FIH_ENABLE_DOUBLE_VARS */ + +/* + * C has a common return pattern where 0 is a correct value and all others are + * errors. This function converts 0 to FIH_SUCCESS and any other number to a + * value that is not FIH_SUCCESS + */ +__attribute__((always_inline)) inline +fih_int fih_int_encode_zero_equality(int32_t x) +{ + if (x) { + return FIH_FAILURE; + } else { + return FIH_SUCCESS; + } +} + +/* + * Increment the CFI counter by one, and return the value before the increment. + * + * NOTE + * This function shall not be called directly. + */ +fih_int fih_cfi_get_and_increment(void); + +/* + * Validate that the saved precall value is the same as the value of the global + * counter. For this to be the case, a fih_ret must have been called between + * these functions being executed. If the values aren't the same then panic. + * + * NOTE + * This function shall not be called directly. + */ +void fih_cfi_validate(fih_int saved); + +/* + * Decrement the global CFI counter by one, so that it has the same value as + * before the cfi_precall. + * + * NOTE + * This function shall not be called directly. + */ +void fih_cfi_decrement(void); + +#ifdef FIH_ENABLE_CFI +/* Global Control Flow Integrity counter */ +extern fih_int _fih_cfi_ctr; + +/* + * Macro wrappers for functions - Even when the functions have zero body this + * saves a few bytes on noop functions as it doesn't generate the call/ret + * + * CFI precall function saves the CFI counter and then increments it - the + * postcall then checks if the counter is equal to the saved value. In order for + * this to be the case a FIH_RET must have been performed inside the called + * function in order to decrement the counter, so the function must have been + * called. + */ +#define FIH_CFI_PRECALL_BLOCK \ + fih_int _fih_cfi_precall_saved_value = fih_cfi_get_and_increment() + +#define FIH_CFI_POSTCALL_BLOCK \ + fih_cfi_validate(_fih_cfi_precall_saved_value) + +#define FIH_CFI_PRERET \ + fih_cfi_decrement() +#else /* FIH_ENABLE_CFI */ +#define FIH_CFI_PRECALL_BLOCK +#define FIH_CFI_POSTCALL_BLOCK +#define FIH_CFI_PRERET +#endif /* FIH_ENABLE_CFI */ + +/* + * Label for interacting with FIH testing tool. Can be parsed from the elf file + * after compilation. Does not require debug symbols. + */ +#define FIH_LABEL(str) __asm volatile ("FIH_LABEL_" str "_%=:" ::); + +/* + * Main FIH calling macro. return variable is second argument. Does some setup + * before and validation afterwards. Inserts labels for use with testing script. + * + * First perform the precall step - this gets the current value of the CFI + * counter and saves it to a local variable, and then increments the counter. + * + * Then set the return variable to FIH_FAILURE as a base case. + * + * Then perform the function call. As part of the function FIH_RET must be + * called which will decrement the counter. + * + * The postcall step gets the value of the counter and compares it to the + * previously saved value. If this is equal then the function call and all child + * function calls were performed. + */ +#define FIH_CALL(f, ret, ...) \ + do { \ + FIH_LABEL("FIH_CALL_START"); \ + FIH_CFI_PRECALL_BLOCK; \ + ret = FIH_FAILURE; \ + fih_delay(); \ + ret = f(__VA_ARGS__); \ + FIH_CFI_POSTCALL_BLOCK; \ + fih_int_validate(ret); \ + FIH_LABEL("FIH_CALL_END"); \ + } while (0) + +/* + * FIH return changes the state of the internal state machine. If you do a + * FIH_CALL then you need to do a FIH_RET else the state machine will detect + * tampering and panic. + */ +#define FIH_RET(ret) \ + do { \ + FIH_CFI_PRERET; \ + return ret; \ + } while (0) + +#else /* TFM_FIH_PROFILE_ON */ +typedef int32_t fih_int; + +#define FIH_INT_INIT(x) (x) + +#define FIH_SUCCESS 0 +#define FIH_FAILURE -1 + +#define fih_int_validate(x) + +#define fih_int_decode(x) (x) + +#define fih_int_encode(x) (x) + +#define fih_eq(x, y) ((x) == (y)) + +#define fih_not_eq(x, y) ((x) != (y)) + +#define fih_delay_init() +#define fih_delay() + +#define FIH_CALL(f, ret, ...) \ + do { \ + ret = f(__VA_ARGS__); \ + } while (0) + +#define FIH_RET(ret) \ + do { \ + return ret; \ + } while (0) + +#endif /* TFM_FIH_PROFILE_ON */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __FAULT_INJECTION_HARDENING_H__ */ diff --git a/lib/fih/src/fih.c b/lib/fih/src/fih.c new file mode 100644 index 0000000000..780715efe4 --- /dev/null +++ b/lib/fih/src/fih.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020-2021, Arm Limited. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + */ + +#include "fih.h" +#include "tfm_hal_defs.h" +#include "tfm_hal_platform.h" + +#ifdef TFM_FIH_PROFILE_ON +fih_int FIH_SUCCESS = FIH_INT_INIT(FIH_POSITIVE_VALUE); +fih_int FIH_FAILURE = FIH_INT_INIT(FIH_NEGATIVE_VALUE); +#endif + +#ifdef FIH_ENABLE_CFI +fih_int _fih_cfi_ctr = FIH_INT_INIT(0); + +fih_int fih_cfi_get_and_increment(void) +{ + fih_int saved_ctr = _fih_cfi_ctr; + + if (fih_int_decode(_fih_cfi_ctr) < 0) { + FIH_PANIC; + } + + /* Overflow */ + if (fih_int_decode(_fih_cfi_ctr) > (fih_int_decode(_fih_cfi_ctr) + 1)) { + FIH_PANIC; + } + + _fih_cfi_ctr = fih_int_encode(fih_int_decode(_fih_cfi_ctr) + 1); + + fih_int_validate(_fih_cfi_ctr); + fih_int_validate(saved_ctr); + + return saved_ctr; +} + +void fih_cfi_validate(fih_int saved) +{ + volatile int32_t rc = FIH_FALSE; + + rc = fih_eq(saved, _fih_cfi_ctr); + if (rc != FIH_TRUE) { + FIH_PANIC; + } +} + +void fih_cfi_decrement(void) +{ + if (fih_int_decode(_fih_cfi_ctr) < 1) { + FIH_PANIC; + } + + _fih_cfi_ctr = fih_int_encode(fih_int_decode(_fih_cfi_ctr) - 1); + + fih_int_validate(_fih_cfi_ctr); +} +#endif /* FIH_ENABLE_CFI */ + +#ifdef FIH_ENABLE_GLOBAL_FAIL +/* Global failure loop for bootloader code. Uses attribute used to prevent + * compiler removing due to non-standard calling procedure. Multiple loop jumps + * used to make unlooping difficult. + */ +__attribute__((used)) +__attribute__((noinline)) +void fih_panic_loop(void) +{ + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); + __asm volatile ("b fih_panic_loop"); +} +#endif /* FIH_ENABLE_GLOBAL_FAIL */ + +#ifdef FIH_ENABLE_DELAY +void fih_delay_init(void) +{ + volatile int32_t ret = TFM_HAL_ERROR_GENERIC; + + ret = tfm_hal_random_init(); + if (ret != TFM_HAL_SUCCESS) { + FIH_PANIC; + } +} + +uint8_t fih_delay_random(void) +{ + volatile int32_t ret = TFM_HAL_ERROR_GENERIC; + uint8_t rand_value = 0xFF; + + ret = tfm_hal_random_generate(&rand_value, sizeof(rand_value)); + if (ret != TFM_HAL_SUCCESS) { + FIH_PANIC; + } + + return rand_value; +} +#endif /* FIH_ENABLE_DELAY */ |