aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/fih/CMakeLists.txt31
-rw-r--r--lib/fih/inc/fih.h474
-rw-r--r--lib/fih/src/fih.c107
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 */