]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/random-seed.c
boot: implement kernel EFI RNG seed protocol with proper hashing
[thirdparty/systemd.git] / src / boot / efi / random-seed.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <efi.h>
4 #include <efilib.h>
5
6 #include "missing_efi.h"
7 #include "random-seed.h"
8 #include "secure-boot.h"
9 #include "sha256.h"
10 #include "util.h"
11
12 #define RANDOM_MAX_SIZE_MIN (32U)
13 #define RANDOM_MAX_SIZE_MAX (32U*1024U)
14
15 #define EFI_RNG_GUID &(const EFI_GUID) EFI_RNG_PROTOCOL_GUID
16
17 struct linux_efi_random_seed {
18 uint32_t size;
19 uint8_t seed[];
20 };
21
22 #define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
23 { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
24
25 /* SHA256 gives us 256/8=32 bytes */
26 #define HASH_VALUE_SIZE 32
27
28 /* Linux's RNG is 256 bits, so let's provide this much */
29 #define DESIRED_SEED_SIZE 32
30
31 /* Some basic domain separation in case somebody uses this data elsewhere */
32 #define HASH_LABEL "systemd-boot random seed label v1"
33
34 static EFI_STATUS acquire_rng(void *ret, UINTN size) {
35 EFI_RNG_PROTOCOL *rng;
36 EFI_STATUS err;
37
38 assert(ret);
39
40 /* Try to acquire the specified number of bytes from the UEFI RNG */
41
42 err = BS->LocateProtocol((EFI_GUID *) EFI_RNG_GUID, NULL, (void **) &rng);
43 if (err != EFI_SUCCESS)
44 return err;
45 if (!rng)
46 return EFI_UNSUPPORTED;
47
48 err = rng->GetRNG(rng, NULL, size, ret);
49 if (err != EFI_SUCCESS)
50 return log_error_status_stall(err, L"Failed to acquire RNG data: %r", err);
51 return EFI_SUCCESS;
52 }
53
54 static EFI_STATUS acquire_system_token(void **ret, UINTN *ret_size) {
55 _cleanup_free_ char *data = NULL;
56 EFI_STATUS err;
57 UINTN size;
58
59 assert(ret);
60 assert(ret_size);
61
62 *ret_size = 0;
63 err = efivar_get_raw(LOADER_GUID, L"LoaderSystemToken", &data, &size);
64 if (err != EFI_SUCCESS) {
65 if (err != EFI_NOT_FOUND)
66 log_error_stall(L"Failed to read LoaderSystemToken EFI variable: %r", err);
67 return err;
68 }
69
70 if (size <= 0)
71 return log_error_status_stall(EFI_NOT_FOUND, L"System token too short, ignoring.");
72
73 *ret = TAKE_PTR(data);
74 *ret_size = size;
75
76 return EFI_SUCCESS;
77 }
78
79 static void validate_sha256(void) {
80
81 #ifdef EFI_DEBUG
82 /* Let's validate our SHA256 implementation. We stole it from glibc, and converted it to UEFI
83 * style. We better check whether it does the right stuff. We use the simpler test vectors from the
84 * SHA spec. Note that we strip this out in optimization builds. */
85
86 static const struct {
87 const char *string;
88 uint8_t hash[HASH_VALUE_SIZE];
89 } array[] = {
90 { "abc",
91 { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
92 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
93 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
94 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad }},
95
96 { "",
97 { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
98 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
99 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
100 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }},
101
102 { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
103 { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
104 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
105 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
106 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 }},
107
108 { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
109 { 0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80,
110 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37,
111 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51,
112 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1 }},
113 };
114
115 for (UINTN i = 0; i < ELEMENTSOF(array); i++)
116 assert(memcmp(SHA256_DIRECT(array[i].string, strlen8(array[i].string)), array[i].hash, HASH_VALUE_SIZE) == 0);
117 #endif
118 }
119
120 EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
121 _cleanup_erase_ uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
122 _cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
123 struct linux_efi_random_seed *previous_seed_table = NULL;
124 _cleanup_free_ void *seed = NULL, *system_token = NULL;
125 _cleanup_(file_closep) EFI_FILE *handle = NULL;
126 _cleanup_free_ EFI_FILE_INFO *info = NULL;
127 _cleanup_erase_ struct sha256_ctx hash;
128 uint64_t uefi_monotonic_counter = 0;
129 size_t size, rsize, wsize;
130 bool seeded_by_efi = false;
131 EFI_STATUS err;
132 EFI_TIME now;
133
134 assert(root_dir);
135 assert_cc(DESIRED_SEED_SIZE == HASH_VALUE_SIZE);
136
137 validate_sha256();
138
139 if (mode == RANDOM_SEED_OFF)
140 return EFI_NOT_FOUND;
141
142 /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
143 sha256_init_ctx(&hash);
144
145 /* Some basic domain separation in case somebody uses this data elsewhere */
146 sha256_process_bytes(HASH_LABEL, sizeof(HASH_LABEL) - 1, &hash);
147
148 for (size_t i = 0; i < ST->NumberOfTableEntries; ++i)
149 if (memcmp(&(const EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID,
150 &ST->ConfigurationTable[i].VendorGuid, sizeof(EFI_GUID)) == 0) {
151 previous_seed_table = ST->ConfigurationTable[i].VendorTable;
152 break;
153 }
154 if (!previous_seed_table) {
155 size = 0;
156 sha256_process_bytes(&size, sizeof(size), &hash);
157 } else {
158 size = previous_seed_table->size;
159 seeded_by_efi = size >= DESIRED_SEED_SIZE;
160 sha256_process_bytes(&size, sizeof(size), &hash);
161 sha256_process_bytes(previous_seed_table->seed, size, &hash);
162
163 /* Zero and free the previous seed table only at the end after we've managed to install a new
164 * one, so that in case this function fails or aborts, Linux still receives whatever the
165 * previous bootloader chain set. So, the next line of this block is not an explicit_bzero()
166 * call. */
167 }
168
169 /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
170 * idea to use it because it helps us for cases where users mistakenly include a random seed in
171 * golden master images that are replicated many times. */
172 err = acquire_rng(random_bytes, sizeof(random_bytes));
173 if (err != EFI_SUCCESS) {
174 size = 0;
175 /* If we can't get any randomness from EFI itself, then we'll only be relying on what's in
176 * ESP. But ESP is mutable, so if secure boot is enabled, we probably shouldn't trust that
177 * alone, in which case we bail out early. */
178 if (!seeded_by_efi && secure_boot_enabled())
179 return EFI_NOT_FOUND;
180 } else {
181 seeded_by_efi = true;
182 size = sizeof(random_bytes);
183 }
184 sha256_process_bytes(&size, sizeof(size), &hash);
185 sha256_process_bytes(random_bytes, size, &hash);
186
187 /* Get some system specific seed that the installer might have placed in an EFI variable. We include
188 * it in our hash. This is protection against golden master image sloppiness, and it remains on the
189 * system, even when disk images are duplicated or swapped out. */
190 err = acquire_system_token(&system_token, &size);
191 if (mode != RANDOM_SEED_ALWAYS && (err != EFI_SUCCESS || size < DESIRED_SEED_SIZE) && !seeded_by_efi)
192 return err;
193 sha256_process_bytes(&size, sizeof(size), &hash);
194 if (system_token) {
195 sha256_process_bytes(system_token, size, &hash);
196 explicit_bzero_safe(system_token, size);
197 }
198
199 err = root_dir->Open(
200 root_dir,
201 &handle,
202 (char16_t *) L"\\loader\\random-seed",
203 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
204 0);
205 if (err != EFI_SUCCESS) {
206 if (err != EFI_NOT_FOUND && err != EFI_WRITE_PROTECTED)
207 log_error_stall(L"Failed to open random seed file: %r", err);
208 return err;
209 }
210
211 err = get_file_info_harder(handle, &info, NULL);
212 if (err != EFI_SUCCESS)
213 return log_error_status_stall(err, L"Failed to get file info for random seed: %r", err);
214
215 size = info->FileSize;
216 if (size < RANDOM_MAX_SIZE_MIN)
217 return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too short.");
218
219 if (size > RANDOM_MAX_SIZE_MAX)
220 return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too large.");
221
222 seed = xmalloc(size);
223 rsize = size;
224 err = handle->Read(handle, &rsize, seed);
225 if (err != EFI_SUCCESS)
226 return log_error_status_stall(err, L"Failed to read random seed file: %r", err);
227 if (rsize != size) {
228 explicit_bzero_safe(seed, rsize);
229 return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short read on random seed file.");
230 }
231
232 sha256_process_bytes(&size, sizeof(size), &hash);
233 sha256_process_bytes(seed, size, &hash);
234 explicit_bzero_safe(seed, size);
235
236 err = handle->SetPosition(handle, 0);
237 if (err != EFI_SUCCESS)
238 return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
239
240 /* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single
241 * boot) in the hash, so that even if the changes to the ESP for some reason should not be
242 * persistent, the random seed we generate will still be different on every single boot. */
243 err = BS->GetNextMonotonicCount(&uefi_monotonic_counter);
244 if (err != EFI_SUCCESS && !seeded_by_efi)
245 return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err);
246 size = sizeof(uefi_monotonic_counter);
247 sha256_process_bytes(&size, sizeof(size), &hash);
248 sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
249 err = RT->GetTime(&now, NULL);
250 size = err == EFI_SUCCESS ? sizeof(now) : 0; /* Known to be flaky, so don't bark on error. */
251 sha256_process_bytes(&size, sizeof(size), &hash);
252 sha256_process_bytes(&now, size, &hash);
253
254 /* hash_key = HASH(hash) */
255 sha256_finish_ctx(&hash, hash_key);
256
257 /* hash = hash_key || 0 */
258 sha256_init_ctx(&hash);
259 sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
260 sha256_process_bytes(&(const uint8_t){ 0 }, sizeof(uint8_t), &hash);
261 /* random_bytes = HASH(hash) */
262 sha256_finish_ctx(&hash, random_bytes);
263
264 size = sizeof(random_bytes);
265 /* If the file size is too large, zero out the remaining bytes on disk, and then truncate. */
266 if (size < info->FileSize) {
267 err = handle->SetPosition(handle, size);
268 if (err != EFI_SUCCESS)
269 return log_error_status_stall(err, L"Failed to seek to offset of random seed file: %r", err);
270 wsize = info->FileSize - size;
271 err = handle->Write(handle, &wsize, seed /* All zeros now */);
272 if (err != EFI_SUCCESS)
273 return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
274 if (wsize != info->FileSize - size)
275 return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
276 err = handle->Flush(handle);
277 if (err != EFI_SUCCESS)
278 return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
279 err = handle->SetPosition(handle, 0);
280 if (err != EFI_SUCCESS)
281 return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
282 info->FileSize = size;
283 err = handle->SetInfo(handle, &GenericFileInfo, info->Size, info);
284 if (err != EFI_SUCCESS)
285 return log_error_status_stall(err, L"Failed to truncate random seed file: %r", err);
286 }
287 /* Update the random seed on disk before we use it */
288 wsize = size;
289 err = handle->Write(handle, &wsize, random_bytes);
290 if (err != EFI_SUCCESS)
291 return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
292 if (wsize != size)
293 return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
294 err = handle->Flush(handle);
295 if (err != EFI_SUCCESS)
296 return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
297
298 err = BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*new_seed_table) + DESIRED_SEED_SIZE,
299 (void **) &new_seed_table);
300 if (err != EFI_SUCCESS)
301 return log_error_status_stall(err, L"Failed to allocate EFI table for random seed: %r", err);
302 new_seed_table->size = DESIRED_SEED_SIZE;
303
304 /* hash = hash_key || 1 */
305 sha256_init_ctx(&hash);
306 sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
307 sha256_process_bytes(&(const uint8_t){ 1 }, sizeof(uint8_t), &hash);
308 /* new_seed_table->seed = HASH(hash) */
309 sha256_finish_ctx(&hash, new_seed_table->seed);
310
311 err = BS->InstallConfigurationTable(&(EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID, new_seed_table);
312 if (err != EFI_SUCCESS)
313 return log_error_status_stall(err, L"Failed to install EFI table for random seed: %r", err);
314 TAKE_PTR(new_seed_table);
315
316 if (previous_seed_table) {
317 /* Now that we've succeeded in installing the new table, we can safely nuke the old one. */
318 explicit_bzero_safe(previous_seed_table->seed, previous_seed_table->size);
319 explicit_bzero_safe(previous_seed_table, sizeof(*previous_seed_table));
320 free(previous_seed_table);
321 }
322
323 return EFI_SUCCESS;
324 }