]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/random-seed.c
boot: fix build with gnu-efi older than 3.0.5
[thirdparty/systemd.git] / src / boot / efi / random-seed.c
1 #include <efi.h>
2 #include <efilib.h>
3
4 #include "missing_efi.h"
5 #include "random-seed.h"
6 #include "sha256.h"
7 #include "util.h"
8 #include "shim.h"
9
10 #define RANDOM_MAX_SIZE_MIN (32U)
11 #define RANDOM_MAX_SIZE_MAX (32U*1024U)
12
13 static const EFI_GUID rng_protocol_guid = EFI_RNG_PROTOCOL_GUID;
14
15 /* SHA256 gives us 256/8=32 bytes */
16 #define HASH_VALUE_SIZE 32
17
18 static EFI_STATUS acquire_rng(UINTN size, VOID **ret) {
19 _cleanup_freepool_ VOID *data = NULL;
20 EFI_RNG_PROTOCOL *rng;
21 EFI_STATUS err;
22
23 /* Try to acquire the specified number of bytes from the UEFI RNG */
24
25 err = LibLocateProtocol((EFI_GUID*) &rng_protocol_guid, (VOID**) &rng);
26 if (EFI_ERROR(err)) {
27 Print(L"Failed to acquire RNG protocol: %r\n", err);
28 return err;
29 }
30 if (!rng) {
31 /* Print(L"RNG protocol not available.\n"); */
32 return EFI_UNSUPPORTED;
33 }
34
35 data = AllocatePool(size);
36 if (!data)
37 return log_oom();
38
39 err = uefi_call_wrapper(rng->GetRNG, 3, rng, NULL, size, data);
40 if (EFI_ERROR(err)) {
41 Print(L"Failed to acquire RNG data: %r\n", err);
42 return err;
43 }
44
45 *ret = TAKE_PTR(data);
46 return EFI_SUCCESS;
47 }
48
49 static VOID hash_once(
50 const VOID *old_seed,
51 const VOID *rng,
52 UINTN size,
53 const VOID *system_token,
54 UINTN system_token_size,
55 UINTN counter,
56 UINT8 ret[static HASH_VALUE_SIZE]) {
57
58 /* This hashes together:
59 *
60 * 1. The contents of the old seed file
61 * 2. Some random data acquired from the UEFI RNG (optional)
62 * 3. Some 'system token' the installer installed as EFI variable (optional)
63 * 4. A counter value
64 *
65 * And writes the result to the specified buffer.
66 */
67
68 struct sha256_ctx hash;
69
70 sha256_init_ctx(&hash);
71 sha256_process_bytes(old_seed, size, &hash);
72 if (rng)
73 sha256_process_bytes(rng, size, &hash);
74 if (system_token_size > 0)
75 sha256_process_bytes(system_token, system_token_size, &hash);
76 sha256_process_bytes(&counter, sizeof(counter), &hash);
77 sha256_finish_ctx(&hash, ret);
78 }
79
80 static EFI_STATUS hash_many(
81 const VOID *old_seed,
82 const VOID *rng,
83 UINTN size,
84 const VOID *system_token,
85 UINTN system_token_size,
86 UINTN counter_start,
87 UINTN n,
88 VOID **ret) {
89
90 _cleanup_freepool_ VOID *output = NULL;
91 UINTN i;
92
93 /* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the
94 * range counter_start…counter_start+n-1. */
95
96 output = AllocatePool(n * HASH_VALUE_SIZE);
97 if (!output)
98 return log_oom();
99
100 for (i = 0; i < n; i++)
101 hash_once(old_seed, rng, size,
102 system_token, system_token_size,
103 counter_start + i,
104 (UINT8*) output + (i * HASH_VALUE_SIZE));
105
106 *ret = TAKE_PTR(output);
107 return EFI_SUCCESS;
108 }
109
110 static EFI_STATUS mangle_random_seed(
111 const VOID *old_seed,
112 const VOID *rng,
113 UINTN size,
114 const VOID *system_token,
115 UINTN system_token_size,
116 VOID **ret_new_seed,
117 VOID **ret_for_kernel) {
118
119 _cleanup_freepool_ VOID *new_seed = NULL, *for_kernel = NULL;
120 EFI_STATUS err;
121 UINTN n;
122
123 /* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an
124 * (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them
125 * together in counter mode, generating a new seed (to replace the file on disk) and the seed for the
126 * kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and
127 * RNG data. */
128
129 n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE;
130
131 /* Begin hashing in counter mode at counter 0 for the new seed for the disk */
132 err = hash_many(old_seed, rng, size, system_token, system_token_size, 0, n, &new_seed);
133 if (EFI_ERROR(err))
134 return err;
135
136 /* Continue counting at 'n' for the seed for the kernel */
137 err = hash_many(old_seed, rng, size, system_token, system_token_size, n, n, &for_kernel);
138 if (EFI_ERROR(err))
139 return err;
140
141 *ret_new_seed = TAKE_PTR(new_seed);
142 *ret_for_kernel = TAKE_PTR(for_kernel);
143
144 return EFI_SUCCESS;
145 }
146
147 EFI_STATUS acquire_system_token(VOID **ret, UINTN *ret_size) {
148 _cleanup_freepool_ CHAR8 *data = NULL;
149 EFI_STATUS err;
150 UINTN size;
151
152 err = efivar_get_raw(&loader_guid, L"LoaderSystemToken", &data, &size);
153 if (EFI_ERROR(err)) {
154 if (err != EFI_NOT_FOUND)
155 Print(L"Failed to read LoaderSystemToken EFI variable: %r", err);
156 return err;
157 }
158
159 if (size <= 0) {
160 Print(L"System token too short, ignoring.");
161 return EFI_NOT_FOUND;
162 }
163
164 *ret = TAKE_PTR(data);
165 *ret_size = size;
166
167 return EFI_SUCCESS;
168 }
169
170 static VOID validate_sha256(void) {
171
172 #ifndef __OPTIMIZE__
173 /* Let's validate our SHA256 implementation. We stole it from glibc, and converted it to UEFI
174 * style. We better check whether it does the right stuff. We use the simpler test vectors from the
175 * SHA spec. Note that we strip this out in optimization builds. */
176
177 static const struct {
178 const char *string;
179 uint8_t hash[HASH_VALUE_SIZE];
180 } array[] = {
181 { "abc",
182 { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
183 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
184 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
185 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad }},
186
187 { "",
188 { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
189 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
190 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
191 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }},
192
193 { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
194 { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
195 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
196 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
197 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 }},
198
199 { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
200 { 0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80,
201 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37,
202 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51,
203 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1 }},
204 };
205
206 UINTN i;
207
208 for (i = 0; i < ELEMENTSOF(array); i++) {
209 struct sha256_ctx hash;
210 uint8_t result[HASH_VALUE_SIZE];
211
212 sha256_init_ctx(&hash);
213 sha256_process_bytes(array[i].string, strlena((const CHAR8*) array[i].string), &hash);
214 sha256_finish_ctx(&hash, result);
215
216 if (CompareMem(result, array[i].hash, HASH_VALUE_SIZE) != 0) {
217 Print(L"SHA256 failed validation.\n");
218 uefi_call_wrapper(BS->Stall, 1, 120 * 1000 * 1000);
219 return;
220 }
221 }
222
223 Print(L"SHA256 validated\n");
224 #endif
225 }
226
227 EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
228 _cleanup_freepool_ VOID *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL;
229 _cleanup_(FileHandleClosep) EFI_FILE_HANDLE handle = NULL;
230 UINTN size, rsize, wsize, system_token_size = 0;
231 _cleanup_freepool_ EFI_FILE_INFO *info = NULL;
232 EFI_STATUS err;
233
234 validate_sha256();
235
236 if (mode == RANDOM_SEED_OFF) {
237 /* Print(L"Random seed handling turned off.\n"); */
238 return EFI_NOT_FOUND;
239 }
240
241 /* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we
242 * don't credit a random seed that is not authenticated. */
243 if (secure_boot_enabled()) {
244 /* Print(L"Not loading random seed, because we are in SecureBoot mode.\n"); */
245 return EFI_NOT_FOUND;
246 }
247
248 /* Get some system specific seed that the installer might have placed in an EFI variable. We include
249 * it in our hash. This is protection against golden master image sloppiness, and it remains on the
250 * system, even when disk images are duplicated or swapped out. */
251 err = acquire_system_token(&system_token, &system_token_size);
252 if (mode != RANDOM_SEED_ALWAYS) {
253 /* if (err == EFI_NOT_FOUND) */
254 /* Print(L"Not loading random seed, because no system token is set.\n"); */
255 if (EFI_ERROR(err))
256 return err; /* in all other error cases we already logged */
257 }
258
259 err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, L"\\loader\\random-seed", EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
260 if (EFI_ERROR(err)) {
261 if (err != EFI_NOT_FOUND)
262 Print(L"Failed to open random seed file: %r\n", err);
263 /* else */
264 /* Print(L"Not loading random seed, because there is none.\n"); */
265
266 return err;
267 }
268
269 info = LibFileInfo(handle);
270 if (!info)
271 return log_oom();
272
273 size = info->FileSize;
274 if (size < RANDOM_MAX_SIZE_MIN) {
275 Print(L"Random seed file is too short?\n");
276 return EFI_INVALID_PARAMETER;
277 }
278
279 if (size > RANDOM_MAX_SIZE_MAX) {
280 Print(L"Random seed file is too large?\n");
281 return EFI_INVALID_PARAMETER;
282 }
283
284 seed = AllocatePool(size);
285 if (!seed)
286 return log_oom();
287
288 rsize = size;
289 err = uefi_call_wrapper(handle->Read, 3, handle, &rsize, seed);
290 if (EFI_ERROR(err)) {
291 Print(L"Failed to read random seed file: %r\n", err);
292 return err;
293 }
294 if (rsize != size) {
295 Print(L"Short read on random seed file\n");
296 return EFI_PROTOCOL_ERROR;
297 }
298
299 err = uefi_call_wrapper(handle->SetPosition, 2, handle, 0);
300 if (EFI_ERROR(err)) {
301 Print(L"Failed to seek to beginning of random seed file: %r\n", err);
302 return err;
303 }
304
305 /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
306 * idea to use it because it helps us for cases where users mistakenly include a random seed in
307 * golden master images that are replicated many times. */
308 (VOID) acquire_rng(size, &rng); /* It's fine if this fails */
309
310 /* Calculate new random seed for the disk and what to pass to the kernel */
311 err = mangle_random_seed(seed, rng, size, system_token, system_token_size, &new_seed, &for_kernel);
312 if (EFI_ERROR(err))
313 return err;
314
315 /* Update the random seed on disk before we use it */
316 wsize = size;
317 err = uefi_call_wrapper(handle->Write, 3, handle, &wsize, new_seed);
318 if (EFI_ERROR(err)) {
319 Print(L"Failed to write random seed file: %r\n", err);
320 return err;
321 }
322 if (wsize != size) {
323 Print(L"Short write on random seed file\n");
324 return EFI_PROTOCOL_ERROR;
325 }
326
327 err = uefi_call_wrapper(handle->Flush, 1, handle);
328 if (EFI_ERROR(err)) {
329 Print(L"Failed to flush random seed file: %r\n");
330 return err;
331 }
332
333 /* We are good to go */
334 err = efivar_set_raw(&loader_guid, L"LoaderRandomSeed", for_kernel, size, FALSE);
335 if (EFI_ERROR(err)) {
336 Print(L"Failed to write random seed to EFI variable: %r\n", err);
337 return err;
338 }
339
340 return EFI_SUCCESS;
341 }