1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "missing_efi.h"
7 #include "random-seed.h"
8 #include "secure-boot.h"
12 #define RANDOM_MAX_SIZE_MIN (32U)
13 #define RANDOM_MAX_SIZE_MAX (32U*1024U)
15 #define EFI_RNG_GUID &(const EFI_GUID) EFI_RNG_PROTOCOL_GUID
17 struct linux_efi_random_seed
{
22 #define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
23 { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
25 /* SHA256 gives us 256/8=32 bytes */
26 #define HASH_VALUE_SIZE 32
28 /* Linux's RNG is 256 bits, so let's provide this much */
29 #define DESIRED_SEED_SIZE 32
31 /* Some basic domain separation in case somebody uses this data elsewhere */
32 #define HASH_LABEL "systemd-boot random seed label v1"
34 static EFI_STATUS
acquire_rng(void *ret
, UINTN size
) {
35 EFI_RNG_PROTOCOL
*rng
;
40 /* Try to acquire the specified number of bytes from the UEFI RNG */
42 err
= BS
->LocateProtocol((EFI_GUID
*) EFI_RNG_GUID
, NULL
, (void **) &rng
);
43 if (err
!= EFI_SUCCESS
)
46 return EFI_UNSUPPORTED
;
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
);
54 static EFI_STATUS
acquire_system_token(void **ret
, UINTN
*ret_size
) {
55 _cleanup_free_
char *data
= NULL
;
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
);
71 return log_error_status_stall(EFI_NOT_FOUND
, L
"System token too short, ignoring.");
73 *ret
= TAKE_PTR(data
);
79 static void validate_sha256(void) {
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. */
88 uint8_t hash
[HASH_VALUE_SIZE
];
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 }},
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 }},
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 }},
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 }},
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);
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;
135 assert_cc(DESIRED_SEED_SIZE
== HASH_VALUE_SIZE
);
139 if (mode
== RANDOM_SEED_OFF
)
140 return EFI_NOT_FOUND
;
142 /* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
143 sha256_init_ctx(&hash
);
145 /* Some basic domain separation in case somebody uses this data elsewhere */
146 sha256_process_bytes(HASH_LABEL
, sizeof(HASH_LABEL
) - 1, &hash
);
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
;
154 if (!previous_seed_table
) {
156 sha256_process_bytes(&size
, sizeof(size
), &hash
);
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
);
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()
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
) {
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
;
181 seeded_by_efi
= true;
182 size
= sizeof(random_bytes
);
184 sha256_process_bytes(&size
, sizeof(size
), &hash
);
185 sha256_process_bytes(random_bytes
, size
, &hash
);
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
)
193 sha256_process_bytes(&size
, sizeof(size
), &hash
);
195 sha256_process_bytes(system_token
, size
, &hash
);
196 explicit_bzero_safe(system_token
, size
);
199 err
= root_dir
->Open(
202 (char16_t
*) L
"\\loader\\random-seed",
203 EFI_FILE_MODE_READ
| EFI_FILE_MODE_WRITE
,
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
);
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
);
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.");
219 if (size
> RANDOM_MAX_SIZE_MAX
)
220 return log_error_status_stall(EFI_INVALID_PARAMETER
, L
"Random seed file is too large.");
222 seed
= xmalloc(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
);
228 explicit_bzero_safe(seed
, rsize
);
229 return log_error_status_stall(EFI_PROTOCOL_ERROR
, L
"Short read on random seed file.");
232 sha256_process_bytes(&size
, sizeof(size
), &hash
);
233 sha256_process_bytes(seed
, size
, &hash
);
234 explicit_bzero_safe(seed
, size
);
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
);
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
);
254 /* hash_key = HASH(hash) */
255 sha256_finish_ctx(&hash
, hash_key
);
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
);
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
);
287 /* Update the random seed on disk before we use it */
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
);
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
);
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
;
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
);
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
);
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
);