From f183c4f75a1ad6ff2051a0ad2a423356517ebe2a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 10 May 2022 16:20:48 +0200 Subject: [PATCH] efi: include UEFI monotonic boot counter in random seed UEFI provides a "monotonic boot counter" which is supposed to increase on each reboot. We can include this in our random seed hash logic, which makes things more robust in case our changes to the ESP end up not actually being as persistent as we assume. As long as the monotonic boot counter increases we should be good, as each boot we'll anyway end up with a new seed that way. This in fact should also pave the way that we can eventually enable the random seed logic even on SecureBoot enabled systems. Why that? With this change the input for the random seed hash is now: 1. the old seed file contents 2. (optionally) some bits from the UEFI RNG 3. (optionally) a per system random "token" stored in an UEFI variable, initialized at OS install 4. the UEFI monotonic counter 5. a counter integer used by the random seed logic. We can ignore #5 entirely for security considerations, it's always going to be a constant series of values determined by the random seed logic. The #1 file is under control of the attacker. (Since it resides in the unprotected ESP) The #2 data is possibly low quality. (it's hard enough to trust the quality of the Linux RNG, let's not go as far as trusting the UEFI one) The #3 data should not be under control of the attacker, and should only exist if explicitly set. Unless you have privileged access to the system you should not be able to read or set it. (well, within limits of flash chip security and its connectivity to the firmware) The #4 data is provided by the firmware, and should not be under control of the attacker. If it works correctly then it might still be guessable (i.e. a new system might have the counter close to zero). Thus: 1+2+5 are guessable/under control of attacker, but 3+4 should not be. Thus, if 3 is not known to attacker and not guessable, and 4 strictly monotonically increasing then it should be enough to guarantee that every boot will get a different seed passed in, that should not be known or guessable by the attacker. That all said, this patch does not enable the random seed logic on SecureBoot. That is left for a later patch. --- src/boot/efi/random-seed.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c index b007f41f7e..b01e232f63 100644 --- a/src/boot/efi/random-seed.c +++ b/src/boot/efi/random-seed.c @@ -48,6 +48,7 @@ static void hash_once( UINTN size, const void *system_token, UINTN system_token_size, + UINT64 uefi_monotonic_counter, UINTN counter, UINT8 ret[static HASH_VALUE_SIZE]) { @@ -56,7 +57,8 @@ static void hash_once( * 1. The contents of the old seed file * 2. Some random data acquired from the UEFI RNG (optional) * 3. Some 'system token' the installer installed as EFI variable (optional) - * 4. A counter value + * 4. The UEFI "monotonic counter" that increases with each boot + * 5. A supplied counter value * * And writes the result to the specified buffer. */ @@ -72,6 +74,7 @@ static void hash_once( sha256_process_bytes(rng, size, &hash); if (system_token_size > 0) sha256_process_bytes(system_token, system_token_size, &hash); + sha256_process_bytes(&uefi_monotonic_counter, sizeof(uefi_monotonic_counter), &hash); sha256_process_bytes(&counter, sizeof(counter), &hash); sha256_finish_ctx(&hash, ret); } @@ -82,6 +85,7 @@ static EFI_STATUS hash_many( UINTN size, const void *system_token, UINTN system_token_size, + UINT64 uefi_monotonic_counter, UINTN counter_start, UINTN n, void **ret) { @@ -100,6 +104,7 @@ static EFI_STATUS hash_many( for (UINTN i = 0; i < n; i++) hash_once(old_seed, rng, size, system_token, system_token_size, + uefi_monotonic_counter, counter_start + i, (UINT8*) output + (i * HASH_VALUE_SIZE)); @@ -113,6 +118,7 @@ static EFI_STATUS mangle_random_seed( UINTN size, const void *system_token, UINTN system_token_size, + UINT64 uefi_monotonic_counter, void **ret_new_seed, void **ret_for_kernel) { @@ -134,12 +140,12 @@ static EFI_STATUS mangle_random_seed( n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE; /* Begin hashing in counter mode at counter 0 for the new seed for the disk */ - err = hash_many(old_seed, rng, size, system_token, system_token_size, 0, n, &new_seed); + err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, 0, n, &new_seed); if (EFI_ERROR(err)) return err; /* Continue counting at 'n' for the seed for the kernel */ - err = hash_many(old_seed, rng, size, system_token, system_token_size, n, n, &for_kernel); + err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, n, n, &for_kernel); if (EFI_ERROR(err)) return err; @@ -228,6 +234,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) { _cleanup_(file_closep) EFI_FILE *handle = NULL; UINTN size, rsize, wsize, system_token_size = 0; _cleanup_freepool_ EFI_FILE_INFO *info = NULL; + UINT64 uefi_monotonic_counter = 0; EFI_STATUS err; assert(root_dir); @@ -285,8 +292,15 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) { * golden master images that are replicated many times. */ (void) acquire_rng(size, &rng); /* It's fine if this fails */ + /* Let's also include the UEFI monotonic counter (which is supposedly increasing on every single + * boot) in the hash, so that even if the changes to the ESP for some reason should not be + * persistent, the random seed we generate will still be different on every single boot. */ + err = BS->GetNextMonotonicCount(&uefi_monotonic_counter); + if (EFI_ERROR(err)) + return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err); + /* Calculate new random seed for the disk and what to pass to the kernel */ - err = mangle_random_seed(seed, rng, size, system_token, system_token_size, &new_seed, &for_kernel); + err = mangle_random_seed(seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, &new_seed, &for_kernel); if (EFI_ERROR(err)) return err;