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