]>
Commit | Line | Data |
---|---|---|
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 |
14 | struct 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 | 31 | static 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 | 51 | static 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 | 75 | static 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 | 116 | EFI_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 | } |