Software APIs
hardened_memory.c
1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
6
10
11// NOTE: The three hardened_mem* functions have similar contents, but the parts
12// that are shared between them are commented only in `memcpy()`.
13status_t hardened_memcpy(uint32_t *restrict dest, const uint32_t *restrict src,
14 size_t word_len) {
15 random_order_t order;
16 random_order_init(&order, word_len);
17
18 size_t count = 0;
19
20 // Immediately convert `src` and `dest` to addresses, which erases their
21 // provenance and causes their addresses to be exposed (in the provenance
22 // sense).
23 uintptr_t src_addr = (uintptr_t)src;
24 uintptr_t dest_addr = (uintptr_t)dest;
25
26 // We need to launder `count`, so that the SW.LOOP-COMPLETION check is not
27 // deleted by the compiler.
28 for (; launderw(count) < word_len; count = launderw(count) + 1) {
29 // The order values themselves are in units of words, but we need `byte_idx`
30 // to be in units of bytes.
31 //
32 // The value obtained from `advance()` is laundered, to prevent
33 // implementation details from leaking across procedures.
34 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
35
36 // Prevent the compiler from reordering the loop; this ensures a
37 // happens-before among indices consistent with `order`.
38 barrierw(byte_idx);
39
40 // Calculate pointers.
41 void *src = (void *)launderw(src_addr + byte_idx);
42 void *dest = (void *)launderw(dest_addr + byte_idx);
43
44 // Perform the copy, without performing a typed dereference operation.
45 write_32(read_32(src), dest);
46 }
48 HARDENED_CHECK_EQ(count, word_len);
49
50 return OTCRYPTO_OK;
51}
52
53status_t hardened_memshred(uint32_t *dest, size_t word_len) {
54 random_order_t order;
55 random_order_init(&order, word_len);
56
57 size_t count = 0;
58
59 uintptr_t data_addr = (uintptr_t)dest;
60
61 for (; count < word_len; count = launderw(count) + 1) {
62 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
63 barrierw(byte_idx);
64
65 // Calculate pointer.
66 void *data = (void *)launderw(data_addr + byte_idx);
67
68 // Write a freshly-generated random word to `*data`.
69 write_32(hardened_memshred_random_word(), data);
70 }
72
73 HARDENED_CHECK_EQ(count, word_len);
74
75 return OTCRYPTO_OK;
76}
77
78hardened_bool_t hardened_memeq(const uint32_t *lhs, const uint32_t *rhs,
79 size_t word_len) {
80 random_order_t order;
81 random_order_init(&order, word_len);
82
83 size_t count = 0;
84
85 uintptr_t lhs_addr = (uintptr_t)lhs;
86 uintptr_t rhs_addr = (uintptr_t)rhs;
87
88 uint32_t zeros = 0;
89 uint32_t ones = UINT32_MAX;
90
91 // The loop is almost token-for-token the one above, but the copy is
92 // replaced with something else.
93 for (; count < word_len; count = launderw(count) + 1) {
94 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
95 barrierw(byte_idx);
96
97 // Calculate pointers.
98 void *av = (void *)launderw(lhs_addr + byte_idx);
99 void *bv = (void *)launderw(rhs_addr + byte_idx);
100
101 uint32_t a = read_32(av);
102 uint32_t b = read_32(bv);
103
104 // Launder one of the operands, so that the compiler cannot cache the result
105 // of the xor for use in the next operation.
106 //
107 // We launder `zeroes` so that compiler cannot learn that `zeroes` has
108 // strictly more bits set at the end of the loop.
109 zeros = launder32(zeros) | (launder32(a) ^ b);
110
111 // Same as above. The compiler can cache the value of `a[offset]`, but it
112 // has no chance to strength-reduce this operation.
113 ones = launder32(ones) & (launder32(a) ^ ~b);
114 }
116
117 HARDENED_CHECK_EQ(count, word_len);
118 if (launder32(zeros) == 0) {
119 HARDENED_CHECK_EQ(ones, UINT32_MAX);
120 return kHardenedBoolTrue;
121 }
122
123 HARDENED_CHECK_NE(ones, UINT32_MAX);
124 return kHardenedBoolFalse;
125}
126
127hardened_bool_t consttime_memeq_byte(const void *lhs, const void *rhs,
128 size_t len) {
129 uint32_t zeros = 0;
130 uint32_t ones = UINT32_MAX;
131
132 random_order_t order;
133 random_order_init(&order, len);
134
135 size_t count = 0;
136
137 uintptr_t lhs_addr = (uintptr_t)lhs;
138 uintptr_t rhs_addr = (uintptr_t)rhs;
139
140 for (; launderw(count) < len; count = launderw(count) + 1) {
141 size_t byte_idx = launderw(random_order_advance(&order));
142 barrierw(byte_idx);
143
144 uint8_t *a = (uint8_t *)launderw(lhs_addr + byte_idx);
145 uint8_t *b = (uint8_t *)launderw(rhs_addr + byte_idx);
146
147 // Launder one of the operands, so that the compiler cannot cache the result
148 // of the xor for use in the next operation.
149 //
150 // We launder `zeroes` so that compiler cannot learn that `zeroes` has
151 // strictly more bits set at the end of the loop.
152 zeros = launder32(zeros) | (launder32((uint32_t)*a) ^ *b);
153
154 // Same as above. The compiler can cache the value of `a[offset]`, but it
155 // has no chance to strength-reduce this operation.
156 ones = launder32(ones) & (launder32((uint32_t)*a) ^ ~*b);
157 }
158
159 HARDENED_CHECK_EQ(count, len);
160
161 if (launder32(zeros) == 0) {
162 HARDENED_CHECK_EQ(ones, UINT32_MAX);
163 return kHardenedBoolTrue;
164 }
165
166 HARDENED_CHECK_NE(ones, UINT32_MAX);
167 return kHardenedBoolFalse;
168}
169
170status_t hardened_xor(const uint32_t *restrict x, const uint32_t *restrict y,
171 size_t word_len, uint32_t *restrict dest) {
172 // Randomize the content of the output buffer before writing to it.
173 hardened_memshred(dest, word_len);
174
175 // Create a random variable rand.
176 uint32_t rand[word_len];
177 hardened_memshred(rand, word_len);
178
179 // Cast pointers to `uintptr_t` to erase their provenance.
180 uintptr_t x_addr = (uintptr_t)x;
181 uintptr_t y_addr = (uintptr_t)y;
182 uintptr_t dest_addr = (uintptr_t)dest;
183 uintptr_t rand_addr = (uintptr_t)&rand;
184
185 // Generate a random ordering.
186 random_order_t order;
187 random_order_init(&order, word_len);
188 size_t count = 0;
189
190 // XOR the mask with the first share. This loop is modelled off the one in
191 // `hardened_memcpy`; see the comments there for more details.
192 for (; launderw(count) < word_len; count = launderw(count) + 1) {
193 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
194
195 // Prevent the compiler from re-ordering the loop.
196 barrierw(byte_idx);
197
198 // Calculate pointers.
199 uintptr_t xp = x_addr + byte_idx;
200 uintptr_t yp = y_addr + byte_idx;
201 uintptr_t destp = dest_addr + byte_idx;
202 uintptr_t randp = rand_addr + byte_idx;
203
204 // Set the pointers.
205 void *xv = (void *)launderw(xp);
206 void *yv = (void *)launderw(yp);
207 void *destv = (void *)launderw(destp);
208 void *randv = (void *)launderw(randp);
209
210 // Perform the XORs: dest = ((x ^ rand) ^ y) ^ rand
211 write_32(read_32(xv) ^ read_32(randv), destv);
212 write_32(read_32(destv) ^ read_32(yv), destv);
213 write_32(read_32(destv) ^ read_32(randv), destv);
214 }
216 HARDENED_CHECK_EQ(count, word_len);
217
218 return OTCRYPTO_OK;
219}
220
221status_t hardened_xor_in_place(uint32_t *restrict x, const uint32_t *restrict y,
222 size_t word_len) {
223 // Generate a random ordering.
224 random_order_t order;
225 random_order_init(&order, word_len);
226 size_t count = 0;
227
228 // Cast pointers to `uintptr_t` to erase their provenance.
229 uintptr_t x_addr = (uintptr_t)x;
230 uintptr_t y_addr = (uintptr_t)y;
231
232 // XOR the mask with the first share. This loop is modelled off the one in
233 // `hardened_memcpy`; see the comments there for more details.
234 for (; launderw(count) < word_len; count = launderw(count) + 1) {
235 size_t byte_idx = launderw(random_order_advance(&order)) * sizeof(uint32_t);
236
237 // Prevent the compiler from re-ordering the loop.
238 barrierw(byte_idx);
239
240 // Calculate pointers.
241 void *xv = (void *)launderw(x_addr + byte_idx);
242 void *yv = (void *)launderw(y_addr + byte_idx);
243
244 // Perform an XOR in the array.
245 write_32(read_32(xv) ^ read_32(yv), xv);
246 }
248 HARDENED_CHECK_EQ(count, word_len);
249
250 return OTCRYPTO_OK;
251}
252
253status_t randomized_bytecopy(void *restrict dest, const void *restrict src,
254 size_t byte_len) {
255 random_order_t order;
256 random_order_init(&order, byte_len);
257
258 size_t count = 0;
259
260 uintptr_t src_addr = (uintptr_t)src;
261 uintptr_t dest_addr = (uintptr_t)dest;
262
263 for (; launderw(count) < byte_len; count = launderw(count) + 1) {
264 size_t byte_idx = launderw(random_order_advance(&order));
265 barrierw(byte_idx);
266
267 uint8_t *src_byte_idx = (uint8_t *)launderw(src_addr + byte_idx);
268 uint8_t *dest_byte_idx = (uint8_t *)launderw(dest_addr + byte_idx);
269
270 *(dest_byte_idx) = *(src_byte_idx);
271 }
273 HARDENED_CHECK_EQ(count, byte_len);
274
275 return OTCRYPTO_OK;
276}
277
278status_t randomized_bytexor_in_place(void *restrict x, const void *restrict y,
279 size_t byte_len) {
280 random_order_t order;
281 random_order_init(&order, byte_len);
282
283 size_t count = 0;
284
285 uintptr_t x_addr = (uintptr_t)x;
286 uintptr_t y_addr = (uintptr_t)y;
287
288 for (; launderw(count) < byte_len; count = launderw(count) + 1) {
289 size_t byte_idx = launderw(random_order_advance(&order));
290 barrierw(byte_idx);
291
292 // TODO(#8815) byte writes vs. word-wise integrity
293 uint8_t *x_byte_idx = (uint8_t *)launderw(x_addr + byte_idx);
294 uint8_t *y_byte_idx = (uint8_t *)launderw(y_addr + byte_idx);
295
296 *(x_byte_idx) = *(x_byte_idx) ^ *(y_byte_idx);
297 }
299 HARDENED_CHECK_EQ(count, byte_len);
300
301 return OTCRYPTO_OK;
302}