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