1 /*
2 * SPDX-License-Identifier: Apache-2.0
3 *
4 * Copyright (c) 2020 Arm Limited
5 */
6
7 #ifndef __FAULT_INJECTION_HARDENING_H__
8 #define __FAULT_INJECTION_HARDENING_H__
9
10 /* Fault injection mitigation library.
11 *
12 * Has support for different measures, which can either be enabled/disabled
13 * separately or by defining one of the MCUBOOT_FIH_PROFILEs.
14 *
15 * NOTE: These constructs against fault injection attacks are not guaranteed to
16 * be secure for all compilers, but execution is going to be correct and
17 * including them will certainly help to harden the code.
18 *
19 * FIH_ENABLE_DOUBLE_VARS makes critical variables into a tuple (x, x ^ msk).
20 * Then the correctness of x can be checked by XORing the two tuple values
21 * together. This also means that comparisons between fih_ints can be verified
22 * by doing x == y && x_msk == y_msk.
23 *
24 * FIH_ENABLE_GLOBAL_FAIL makes all while(1) failure loops redirect to a global
25 * failure loop. This loop has mitigations against loop escapes / unlooping.
26 * This also means that any unlooping won't immediately continue executing the
27 * function that was executing before the failure.
28 *
29 * FIH_ENABLE_CFI (Control Flow Integrity) creates a global counter that is
30 * incremented before every FIH_CALL of vulnerable functions. On the function
31 * return the counter is decremented, and after the return it is verified that
32 * the counter has the same value as before this process. This can be used to
33 * verify that the function has actually been called. This protection is
34 * intended to discover that important functions are called in an expected
35 * sequence and neither of them is missed due to an instruction skip which could
36 * be a result of glitching attack. It does not provide protection against ROP
37 * or JOP attacks.
38 *
39 * FIH_ENABLE_DELAY causes random delays. This makes it hard to cause faults
40 * precisely. It requires an RNG. An mbedtls integration is provided in
41 * fault_injection_hardening_delay_mbedtls.h, but any RNG that has an entropy
42 * source can be used by implementing the fih_delay_random_uchar function.
43 *
44 * The basic call pattern is:
45 *
46 * FIH_DECLARE(fih_rc, FIH_FAILURE);
47 * FIH_CALL(vulnerable_function, fih_rc, arg1, arg2);
48 * if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
49 * FIH_PANIC;
50 * }
51 *
52 * Note that any function called by FIH_CALL must only return using FIH_RETURN,
53 * as otherwise the CFI counter will not be decremented and the CFI check will
54 * fail causing a panic.
55 */
56
57 #include "mcuboot_config/mcuboot_config.h"
58
59 #if defined(MCUBOOT_FIH_PROFILE_HIGH)
60
61 #define FIH_ENABLE_DELAY /* Requires an entropy source */
62 #define FIH_ENABLE_DOUBLE_VARS
63 #define FIH_ENABLE_GLOBAL_FAIL
64 #define FIH_ENABLE_CFI
65
66 #elif defined(MCUBOOT_FIH_PROFILE_MEDIUM)
67
68 #define FIH_ENABLE_DOUBLE_VARS
69 #define FIH_ENABLE_GLOBAL_FAIL
70 #define FIH_ENABLE_CFI
71
72 #elif defined(MCUBOOT_FIH_PROFILE_LOW)
73
74 #define FIH_ENABLE_GLOBAL_FAIL
75 #define FIH_ENABLE_CFI
76
77 #elif !defined(MCUBOOT_FIH_PROFILE_OFF)
78 #define MCUBOOT_FIH_PROFILE_OFF
79 #endif /* MCUBOOT_FIH_PROFILE */
80
81 #ifdef FIH_ENABLE_DELAY
82 #include "fault_injection_hardening_delay_rng.h"
83 #endif /* FIH_ENABLE_DELAY */
84
85
86 #ifdef __cplusplus
87 extern "C" {
88 #endif /* __cplusplus */
89
90 /* Non-zero success value to defend against register resets. Zero is the most
91 * common value for a corrupted register so complex bit-patterns are used
92 */
93 #ifndef MCUBOOT_FIH_PROFILE_OFF
94 #define FIH_POSITIVE_VALUE 0x1AAAAAAA
95 #define FIH_NEGATIVE_VALUE 0x15555555
96 #define FIH_CONST1 0x1FCDEA88
97 #define FIH_CONST2 0x19C1F6E1
98 #else
99 #define FIH_POSITIVE_VALUE 0
100 #define FIH_NEGATIVE_VALUE -1
101 #define FIH_CONST1 1
102 #define FIH_CONST2 1
103 #endif
104
105 /* A volatile mask is used to prevent compiler optimization - the mask is xored
106 * with the variable to create the backup and the integrity can be checked with
107 * another xor. The mask value doesn't _really_ matter that much, as long as
108 * it has reasonably high hamming weight.
109 */
110 #define _FIH_MASK_VALUE 0xBEEF
111
112 #ifdef FIH_ENABLE_DOUBLE_VARS
113
114 /* All ints are replaced with two int - the normal one and a backup which is
115 * XORed with the mask.
116 */
117 extern volatile int _fih_mask;
118 typedef volatile struct {
119 volatile int val;
120 volatile int msk;
121 } fih_int;
122 typedef volatile int fih_ret;
123
124 #else
125
126 typedef int fih_int;
127 typedef int fih_ret;
128
129 #endif /* FIH_ENABLE_DOUBLE_VARS */
130
131 extern fih_ret FIH_SUCCESS;
132 extern fih_ret FIH_FAILURE;
133 extern fih_ret FIH_NO_BOOTABLE_IMAGE;
134 extern fih_ret FIH_BOOT_HOOK_REGULAR;
135
136 #ifdef FIH_ENABLE_GLOBAL_FAIL
137 /* Global failure handler - more resistant to unlooping. noinline and used are
138 * used to prevent optimization
139 */
140 __attribute__((noinline)) __attribute__((used))
141 void fih_panic_loop(void);
142 #define FIH_PANIC fih_panic_loop()
143 #else
144 #define FIH_PANIC while (1) {}
145 #endif /* FIH_ENABLE_GLOBAL_FAIL */
146
147 /* NOTE: For functions to be inlined outside their compilation unit they have to
148 * have the body in the header file. This is required as function calls are easy
149 * to skip.
150 */
151 #ifdef FIH_ENABLE_DELAY
152
153 /* Delaying logic, with randomness from a CSPRNG */
154 __attribute__((always_inline)) inline
fih_delay(void)155 int fih_delay(void)
156 {
157 unsigned char delay;
158 int foo = 0;
159 volatile int rc;
160
161 delay = fih_delay_random_uchar();
162
163 for (volatile int i = 0; i < delay; i++) {
164 foo++;
165 }
166
167 rc = 1;
168
169 /* rc is volatile so if it is the return value then the function cannot be
170 * optimized
171 */
172 return rc;
173 }
174
175 #else
176
177 __attribute__((always_inline)) inline
fih_delay_init(void)178 int fih_delay_init(void)
179 {
180 return 1;
181 }
182
183 __attribute__((always_inline)) inline
fih_delay(void)184 int fih_delay(void)
185 {
186 return 1;
187 }
188 #endif /* FIH_ENABLE_DELAY */
189
190 #ifdef FIH_ENABLE_DOUBLE_VARS
191
192 __attribute__((always_inline)) inline
fih_int_validate(fih_int x)193 void fih_int_validate(fih_int x)
194 {
195 if (x.val != (x.msk ^ _fih_mask)) {
196 FIH_PANIC;
197 }
198 }
199
200 /* Convert a fih_int to an int. Validate for tampering. */
201 __attribute__((always_inline)) inline
fih_int_decode(fih_int x)202 int fih_int_decode(fih_int x)
203 {
204 fih_int_validate(x);
205 return x.val;
206 }
207
208 /* Convert an int to a fih_int, can be used to encode specific error codes. */
209 __attribute__((always_inline)) inline
fih_int_encode(int x)210 fih_int fih_int_encode(int x)
211 {
212 fih_int ret = {x, x ^ _fih_mask};
213 return ret;
214 }
215
216 /* Standard equality. If A == B then 1, else 0 */
217 #define FIH_EQ(x, y) ((x == y) && fih_delay() && !(y != x))
218 #define FIH_NOT_EQ(x, y) ((x != y) || !fih_delay() || !(y == x))
219 #define FIH_SET(x, y) x = y; if(fih_delay() && (x != y)) FIH_PANIC
220
221 #else
222
223 /* NOOP */
224 __attribute__((always_inline)) inline
fih_int_validate(fih_int x)225 void fih_int_validate(fih_int x)
226 {
227 (void) x;
228 return;
229 }
230
231 /* NOOP */
232 __attribute__((always_inline)) inline
fih_int_decode(fih_int x)233 int fih_int_decode(fih_int x)
234 {
235 return x;
236 }
237
238 /* NOOP */
239 __attribute__((always_inline)) inline
fih_int_encode(int x)240 fih_int fih_int_encode(int x)
241 {
242 return x;
243 }
244
245 #define FIH_EQ(x, y) (x == y)
246 #define FIH_NOT_EQ(x, y) (x != y)
247 #define FIH_SET(x, y) x = y
248
249 #endif /* FIH_ENABLE_DOUBLE_VARS */
250
251 #define FIH_DECLARE(var, val) \
252 fih_ret FIH_SET(var, val)
253
254 /* C has a common return pattern where 0 is a correct value and all others are
255 * errors. This function converts 0 to FIH_SUCCESS and any other number to a
256 * value that is not FIH_SUCCESS
257 */
258 __attribute__((always_inline)) inline
fih_ret_encode_zero_equality(int x)259 fih_ret fih_ret_encode_zero_equality(int x)
260 {
261 if (x) {
262 return FIH_FAILURE;
263 } else {
264 return FIH_SUCCESS;
265 }
266 }
267
268 #ifdef FIH_ENABLE_CFI
269 extern fih_int _fih_cfi_ctr;
270 #endif /* FIH_ENABLE_CFI */
271
272 fih_int fih_cfi_get_and_increment(void);
273 void fih_cfi_validate(fih_int saved);
274 void fih_cfi_decrement(void);
275
276 /* Label for interacting with FIH testing tool. Can be parsed from the elf file
277 * after compilation. Does not require debug symbols.
278 */
279 #if defined(__ICCARM__)
280 #define FIH_LABEL(str, lin, cnt) __asm volatile ("FIH_LABEL_" str "_" #lin "_" #cnt "::" ::);
281 #else
282 #define FIH_LABEL(str) __asm volatile ("FIH_LABEL_" str "_%=:" ::);
283 #endif
284
285 /* Main FIH calling macro. return variable is second argument. Does some setup
286 * before and validation afterwards. Inserts labels for use with testing script.
287 *
288 * First perform the precall step - this gets the current value of the CFI
289 * counter and saves it to a local variable, and then increments the counter.
290 *
291 * Then set the return variable to FIH_FAILURE as a base case.
292 *
293 * Then perform the function call. As part of the funtion FIH_RET must be called
294 * which will decrement the counter.
295 *
296 * The postcall step gets the value of the counter and compares it to the
297 * previously saved value. If this is equal then the function call and all child
298 * function calls were performed.
299 */
300 #if defined(__ICCARM__)
301 #define FIH_CALL(f, ret, ...) FIH_CALL2(f, ret, __LINE__, __COUNTER__, __VA_ARGS__)
302
303 #define FIH_CALL2(f, ret, l, c, ...) \
304 do { \
305 FIH_LABEL("FIH_CALL_START", l, c); \
306 FIH_CFI_PRECALL_BLOCK; \
307 ret = FIH_FAILURE; \
308 if (fih_delay()) { \
309 ret = f(__VA_ARGS__); \
310 } \
311 FIH_CFI_POSTCALL_BLOCK; \
312 FIH_LABEL("FIH_CALL_END", l, c); \
313 } while (0)
314
315 #else
316
317 #define FIH_CALL(f, ret, ...) \
318 do { \
319 FIH_LABEL("FIH_CALL_START"); \
320 FIH_CFI_PRECALL_BLOCK; \
321 ret = FIH_FAILURE; \
322 if (fih_delay()) { \
323 ret = f(__VA_ARGS__); \
324 } \
325 FIH_CFI_POSTCALL_BLOCK; \
326 FIH_LABEL("FIH_CALL_END"); \
327 } while (0)
328 #endif
329
330 /* FIH return changes the state of the internal state machine. If you do a
331 * FIH_CALL then you need to do a FIH_RET else the state machine will detect
332 * tampering and panic.
333 */
334 #define FIH_RET(ret) \
335 do { \
336 FIH_CFI_PRERET; \
337 return ret; \
338 } while (0)
339
340
341 #ifdef FIH_ENABLE_CFI
342 /* Macro wrappers for functions - Even when the functions have zero body this
343 * saves a few bytes on noop functions as it doesn't generate the call/ret
344 *
345 * CFI precall function saves the CFI counter and then increments it - the
346 * postcall then checks if the counter is equal to the saved value. In order for
347 * this to be the case a FIH_RET must have been performed inside the called
348 * function in order to decrement the counter, so the function must have been
349 * called.
350 */
351 #define FIH_CFI_PRECALL_BLOCK \
352 fih_int _fih_cfi_saved_value = fih_cfi_get_and_increment()
353
354 #define FIH_CFI_POSTCALL_BLOCK \
355 fih_cfi_validate(_fih_cfi_saved_value)
356
357 #define FIH_CFI_PRERET \
358 fih_cfi_decrement()
359 #else
360 #define FIH_CFI_PRECALL_BLOCK
361 #define FIH_CFI_POSTCALL_BLOCK
362 #define FIH_CFI_PRERET
363 #endif /* FIH_ENABLE_CFI */
364
365 #ifdef __cplusplus
366 }
367 #endif /* __cplusplus */
368
369 #endif /* __FAULT_INJECTION_HARDENING_H__ */
370