Merge pull request #25319 from zx2c4-forks/krngseed
boot: implement kernel EFI RNG seed protocol with proper hashing
This commit is contained in:
@@ -23,6 +23,7 @@ static inline uint32_t random_u32(void) {
|
||||
/* Some limits on the pool sizes when we deal with the kernel random pool */
|
||||
#define RANDOM_POOL_SIZE_MIN 32U
|
||||
#define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
|
||||
#define RANDOM_EFI_SEED_SIZE 32U
|
||||
|
||||
size_t random_pool_size(void);
|
||||
|
||||
|
||||
+11
-19
@@ -1886,8 +1886,6 @@ static int verb_status(int argc, char *argv[], void *userdata) {
|
||||
printf("\n");
|
||||
|
||||
printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal());
|
||||
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0;
|
||||
printf(" Passed to OS: %s\n", yes_no(have));
|
||||
have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0;
|
||||
printf(" System Token: %s\n", have ? "set" : "not set");
|
||||
|
||||
@@ -1977,10 +1975,10 @@ static int verb_list(int argc, char *argv[], void *userdata) {
|
||||
|
||||
static int install_random_seed(const char *esp) {
|
||||
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
||||
_cleanup_free_ void *buffer = NULL;
|
||||
unsigned char buffer[RANDOM_EFI_SEED_SIZE];
|
||||
_cleanup_free_ char *path = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
size_t sz, token_size;
|
||||
size_t token_size;
|
||||
ssize_t n;
|
||||
int r;
|
||||
|
||||
@@ -1990,13 +1988,7 @@ static int install_random_seed(const char *esp) {
|
||||
if (!path)
|
||||
return log_oom();
|
||||
|
||||
sz = random_pool_size();
|
||||
|
||||
buffer = malloc(sz);
|
||||
if (!buffer)
|
||||
return log_oom();
|
||||
|
||||
r = crypto_random_bytes(buffer, sz);
|
||||
r = crypto_random_bytes(buffer, sizeof(buffer));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire random seed: %m");
|
||||
|
||||
@@ -2017,10 +2009,10 @@ static int install_random_seed(const char *esp) {
|
||||
return log_error_errno(fd, "Failed to open random seed file for writing: %m");
|
||||
}
|
||||
|
||||
n = write(fd, buffer, sz);
|
||||
n = write(fd, buffer, sizeof(buffer));
|
||||
if (n < 0)
|
||||
return log_error_errno(errno, "Failed to write random seed file: %m");
|
||||
if ((size_t) n != sz)
|
||||
if ((size_t) n != sizeof(buffer))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file.");
|
||||
|
||||
if (rename(tmp, path) < 0)
|
||||
@@ -2028,7 +2020,7 @@ static int install_random_seed(const char *esp) {
|
||||
|
||||
tmp = mfree(tmp);
|
||||
|
||||
log_info("Random seed file %s successfully written (%zu bytes).", path, sz);
|
||||
log_info("Random seed file %s successfully written (%zu bytes).", path, sizeof(buffer));
|
||||
|
||||
if (!arg_touch_variables)
|
||||
return 0;
|
||||
@@ -2080,16 +2072,16 @@ static int install_random_seed(const char *esp) {
|
||||
if (r != -ENOENT)
|
||||
return log_error_errno(r, "Failed to test system token validity: %m");
|
||||
} else {
|
||||
if (token_size >= sz) {
|
||||
if (token_size >= sizeof(buffer)) {
|
||||
/* Let's avoid writes if we can, and initialize this only once. */
|
||||
log_debug("System token already written, not updating.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz);
|
||||
log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sizeof(buffer));
|
||||
}
|
||||
|
||||
r = crypto_random_bytes(buffer, sz);
|
||||
r = crypto_random_bytes(buffer, sizeof(buffer));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire random seed: %m");
|
||||
|
||||
@@ -2097,7 +2089,7 @@ static int install_random_seed(const char *esp) {
|
||||
* and possibly get identification information or too much insight into the kernel's entropy pool
|
||||
* state. */
|
||||
RUN_WITH_UMASK(0077) {
|
||||
r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sz);
|
||||
r = efi_set_variable(EFI_LOADER_VARIABLE(LoaderSystemToken), buffer, sizeof(buffer));
|
||||
if (r < 0) {
|
||||
if (!arg_graceful)
|
||||
return log_error_errno(r, "Failed to write 'LoaderSystemToken' EFI variable: %m");
|
||||
@@ -2107,7 +2099,7 @@ static int install_random_seed(const char *esp) {
|
||||
else
|
||||
log_warning_errno(r, "Unable to write 'LoaderSystemToken' EFI variable, ignoring: %m");
|
||||
} else
|
||||
log_info("Successfully initialized system token in EFI variable with %zu bytes.", sz);
|
||||
log_info("Successfully initialized system token in EFI variable with %zu bytes.", sizeof(buffer));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -119,6 +119,13 @@ static inline void *mempcpy(void * restrict dest, const void * restrict src, siz
|
||||
memcpy(dest, src, n);
|
||||
return (uint8_t *) dest + n;
|
||||
}
|
||||
|
||||
static inline void explicit_bzero_safe(void *bytes, size_t len) {
|
||||
if (!bytes || len == 0)
|
||||
return;
|
||||
memset(bytes, 0, len);
|
||||
__asm__ __volatile__("": :"r"(bytes) :"memory");
|
||||
}
|
||||
#else
|
||||
/* For unit testing. */
|
||||
int efi_memcmp(const void *p1, const void *p2, size_t n);
|
||||
|
||||
+149
-146
@@ -14,11 +14,24 @@
|
||||
|
||||
#define EFI_RNG_GUID &(const EFI_GUID) EFI_RNG_PROTOCOL_GUID
|
||||
|
||||
struct linux_efi_random_seed {
|
||||
uint32_t size;
|
||||
uint8_t seed[];
|
||||
};
|
||||
|
||||
#define LINUX_EFI_RANDOM_SEED_TABLE_GUID \
|
||||
{ 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } }
|
||||
|
||||
/* SHA256 gives us 256/8=32 bytes */
|
||||
#define HASH_VALUE_SIZE 32
|
||||
|
||||
static EFI_STATUS acquire_rng(UINTN size, void **ret) {
|
||||
_cleanup_free_ void *data = NULL;
|
||||
/* Linux's RNG is 256 bits, so let's provide this much */
|
||||
#define DESIRED_SEED_SIZE 32
|
||||
|
||||
/* Some basic domain separation in case somebody uses this data elsewhere */
|
||||
#define HASH_LABEL "systemd-boot random seed label v1"
|
||||
|
||||
static EFI_STATUS acquire_rng(void *ret, UINTN size) {
|
||||
EFI_RNG_PROTOCOL *rng;
|
||||
EFI_STATUS err;
|
||||
|
||||
@@ -32,126 +45,9 @@ static EFI_STATUS acquire_rng(UINTN size, void **ret) {
|
||||
if (!rng)
|
||||
return EFI_UNSUPPORTED;
|
||||
|
||||
data = xmalloc(size);
|
||||
|
||||
err = rng->GetRNG(rng, NULL, size, data);
|
||||
err = rng->GetRNG(rng, NULL, size, ret);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to acquire RNG data: %r", err);
|
||||
|
||||
*ret = TAKE_PTR(data);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static void hash_once(
|
||||
const void *old_seed,
|
||||
const void *rng,
|
||||
UINTN size,
|
||||
const void *system_token,
|
||||
UINTN system_token_size,
|
||||
uint64_t uefi_monotonic_counter,
|
||||
UINTN counter,
|
||||
uint8_t ret[static HASH_VALUE_SIZE]) {
|
||||
|
||||
/* This hashes together:
|
||||
*
|
||||
* 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. The UEFI "monotonic counter" that increases with each boot
|
||||
* 5. A supplied counter value
|
||||
*
|
||||
* And writes the result to the specified buffer.
|
||||
*/
|
||||
|
||||
struct sha256_ctx hash;
|
||||
|
||||
assert(old_seed);
|
||||
assert(system_token_size == 0 || system_token);
|
||||
|
||||
sha256_init_ctx(&hash);
|
||||
sha256_process_bytes(old_seed, size, &hash);
|
||||
if (rng)
|
||||
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);
|
||||
}
|
||||
|
||||
static EFI_STATUS hash_many(
|
||||
const void *old_seed,
|
||||
const void *rng,
|
||||
UINTN size,
|
||||
const void *system_token,
|
||||
UINTN system_token_size,
|
||||
uint64_t uefi_monotonic_counter,
|
||||
UINTN counter_start,
|
||||
UINTN n,
|
||||
void **ret) {
|
||||
|
||||
_cleanup_free_ void *output = NULL;
|
||||
|
||||
assert(old_seed);
|
||||
assert(system_token_size == 0 || system_token);
|
||||
assert(ret);
|
||||
|
||||
/* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the
|
||||
* range counter_start…counter_start+n-1. */
|
||||
|
||||
output = xmalloc_multiply(HASH_VALUE_SIZE, n);
|
||||
|
||||
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_t*) output + (i * HASH_VALUE_SIZE));
|
||||
|
||||
*ret = TAKE_PTR(output);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static EFI_STATUS mangle_random_seed(
|
||||
const void *old_seed,
|
||||
const void *rng,
|
||||
UINTN size,
|
||||
const void *system_token,
|
||||
UINTN system_token_size,
|
||||
uint64_t uefi_monotonic_counter,
|
||||
void **ret_new_seed,
|
||||
void **ret_for_kernel) {
|
||||
|
||||
_cleanup_free_ void *new_seed = NULL, *for_kernel = NULL;
|
||||
EFI_STATUS err;
|
||||
UINTN n;
|
||||
|
||||
assert(old_seed);
|
||||
assert(system_token_size == 0 || system_token);
|
||||
assert(ret_new_seed);
|
||||
assert(ret_for_kernel);
|
||||
|
||||
/* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an
|
||||
* (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them
|
||||
* together in counter mode, generating a new seed (to replace the file on disk) and the seed for the
|
||||
* kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and
|
||||
* RNG data. */
|
||||
|
||||
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, uefi_monotonic_counter, 0, n, &new_seed);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
|
||||
/* Continue counting at 'n' for the seed for the kernel */
|
||||
err = hash_many(old_seed, rng, size, system_token, system_token_size, uefi_monotonic_counter, n, n, &for_kernel);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
|
||||
*ret_new_seed = TAKE_PTR(new_seed);
|
||||
*ret_for_kernel = TAKE_PTR(for_kernel);
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -163,6 +59,7 @@ static EFI_STATUS acquire_system_token(void **ret, UINTN *ret_size) {
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
*ret_size = 0;
|
||||
err = efivar_get_raw(LOADER_GUID, L"LoaderSystemToken", &data, &size);
|
||||
if (err != EFI_SUCCESS) {
|
||||
if (err != EFI_NOT_FOUND)
|
||||
@@ -221,31 +118,83 @@ static void validate_sha256(void) {
|
||||
}
|
||||
|
||||
EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
||||
_cleanup_free_ void *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL;
|
||||
_cleanup_erase_ uint8_t random_bytes[DESIRED_SEED_SIZE], hash_key[HASH_VALUE_SIZE];
|
||||
_cleanup_free_ struct linux_efi_random_seed *new_seed_table = NULL;
|
||||
struct linux_efi_random_seed *previous_seed_table = NULL;
|
||||
_cleanup_free_ void *seed = NULL, *system_token = NULL;
|
||||
_cleanup_(file_closep) EFI_FILE *handle = NULL;
|
||||
UINTN size, rsize, wsize, system_token_size = 0;
|
||||
_cleanup_free_ EFI_FILE_INFO *info = NULL;
|
||||
_cleanup_erase_ struct sha256_ctx hash;
|
||||
uint64_t uefi_monotonic_counter = 0;
|
||||
size_t size, rsize, wsize;
|
||||
bool seeded_by_efi = false;
|
||||
EFI_STATUS err;
|
||||
EFI_TIME now;
|
||||
|
||||
assert(root_dir);
|
||||
assert_cc(DESIRED_SEED_SIZE == HASH_VALUE_SIZE);
|
||||
|
||||
validate_sha256();
|
||||
|
||||
if (mode == RANDOM_SEED_OFF)
|
||||
return EFI_NOT_FOUND;
|
||||
|
||||
/* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we
|
||||
* don't credit a random seed that is not authenticated. */
|
||||
if (secure_boot_enabled())
|
||||
return EFI_NOT_FOUND;
|
||||
/* hash = LABEL || sizeof(input1) || input1 || ... || sizeof(inputN) || inputN */
|
||||
sha256_init_ctx(&hash);
|
||||
|
||||
/* Some basic domain separation in case somebody uses this data elsewhere */
|
||||
sha256_process_bytes(HASH_LABEL, sizeof(HASH_LABEL) - 1, &hash);
|
||||
|
||||
for (size_t i = 0; i < ST->NumberOfTableEntries; ++i)
|
||||
if (memcmp(&(const EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID,
|
||||
&ST->ConfigurationTable[i].VendorGuid, sizeof(EFI_GUID)) == 0) {
|
||||
previous_seed_table = ST->ConfigurationTable[i].VendorTable;
|
||||
break;
|
||||
}
|
||||
if (!previous_seed_table) {
|
||||
size = 0;
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
} else {
|
||||
size = previous_seed_table->size;
|
||||
seeded_by_efi = size >= DESIRED_SEED_SIZE;
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
sha256_process_bytes(previous_seed_table->seed, size, &hash);
|
||||
|
||||
/* Zero and free the previous seed table only at the end after we've managed to install a new
|
||||
* one, so that in case this function fails or aborts, Linux still receives whatever the
|
||||
* previous bootloader chain set. So, the next line of this block is not an explicit_bzero()
|
||||
* call. */
|
||||
}
|
||||
|
||||
/* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
|
||||
* idea to use it because it helps us for cases where users mistakenly include a random seed in
|
||||
* golden master images that are replicated many times. */
|
||||
err = acquire_rng(random_bytes, sizeof(random_bytes));
|
||||
if (err != EFI_SUCCESS) {
|
||||
size = 0;
|
||||
/* If we can't get any randomness from EFI itself, then we'll only be relying on what's in
|
||||
* ESP. But ESP is mutable, so if secure boot is enabled, we probably shouldn't trust that
|
||||
* alone, in which case we bail out early. */
|
||||
if (!seeded_by_efi && secure_boot_enabled())
|
||||
return EFI_NOT_FOUND;
|
||||
} else {
|
||||
seeded_by_efi = true;
|
||||
size = sizeof(random_bytes);
|
||||
}
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
sha256_process_bytes(random_bytes, size, &hash);
|
||||
|
||||
/* Get some system specific seed that the installer might have placed in an EFI variable. We include
|
||||
* it in our hash. This is protection against golden master image sloppiness, and it remains on the
|
||||
* system, even when disk images are duplicated or swapped out. */
|
||||
err = acquire_system_token(&system_token, &system_token_size);
|
||||
if (mode != RANDOM_SEED_ALWAYS && err != EFI_SUCCESS)
|
||||
err = acquire_system_token(&system_token, &size);
|
||||
if (mode != RANDOM_SEED_ALWAYS && (err != EFI_SUCCESS || size < DESIRED_SEED_SIZE) && !seeded_by_efi)
|
||||
return err;
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
if (system_token) {
|
||||
sha256_process_bytes(system_token, size, &hash);
|
||||
explicit_bzero_safe(system_token, size);
|
||||
}
|
||||
|
||||
err = root_dir->Open(
|
||||
root_dir,
|
||||
@@ -261,7 +210,7 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
||||
|
||||
err = get_file_info_harder(handle, &info, NULL);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to get file info for random seed: %r");
|
||||
return log_error_status_stall(err, L"Failed to get file info for random seed: %r", err);
|
||||
|
||||
size = info->FileSize;
|
||||
if (size < RANDOM_MAX_SIZE_MIN)
|
||||
@@ -271,51 +220,105 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
|
||||
return log_error_status_stall(EFI_INVALID_PARAMETER, L"Random seed file is too large.");
|
||||
|
||||
seed = xmalloc(size);
|
||||
|
||||
rsize = size;
|
||||
err = handle->Read(handle, &rsize, seed);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to read random seed file: %r", err);
|
||||
if (rsize != size)
|
||||
if (rsize != size) {
|
||||
explicit_bzero_safe(seed, rsize);
|
||||
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short read on random seed file.");
|
||||
}
|
||||
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
sha256_process_bytes(seed, size, &hash);
|
||||
explicit_bzero_safe(seed, size);
|
||||
|
||||
err = handle->SetPosition(handle, 0);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
|
||||
|
||||
/* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
|
||||
* idea to use it because it helps us for cases where users mistakenly include a random seed in
|
||||
* 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 (err != EFI_SUCCESS)
|
||||
if (err != EFI_SUCCESS && !seeded_by_efi)
|
||||
return log_error_status_stall(err, L"Failed to acquire UEFI monotonic counter: %r", err);
|
||||
size = sizeof(uefi_monotonic_counter);
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
sha256_process_bytes(&uefi_monotonic_counter, size, &hash);
|
||||
err = RT->GetTime(&now, NULL);
|
||||
size = err == EFI_SUCCESS ? sizeof(now) : 0; /* Known to be flaky, so don't bark on error. */
|
||||
sha256_process_bytes(&size, sizeof(size), &hash);
|
||||
sha256_process_bytes(&now, size, &hash);
|
||||
|
||||
/* 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, uefi_monotonic_counter, &new_seed, &for_kernel);
|
||||
if (err != EFI_SUCCESS)
|
||||
return err;
|
||||
/* hash_key = HASH(hash) */
|
||||
sha256_finish_ctx(&hash, hash_key);
|
||||
|
||||
/* hash = hash_key || 0 */
|
||||
sha256_init_ctx(&hash);
|
||||
sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
|
||||
sha256_process_bytes(&(const uint8_t){ 0 }, sizeof(uint8_t), &hash);
|
||||
/* random_bytes = HASH(hash) */
|
||||
sha256_finish_ctx(&hash, random_bytes);
|
||||
|
||||
size = sizeof(random_bytes);
|
||||
/* If the file size is too large, zero out the remaining bytes on disk, and then truncate. */
|
||||
if (size < info->FileSize) {
|
||||
err = handle->SetPosition(handle, size);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to seek to offset of random seed file: %r", err);
|
||||
wsize = info->FileSize - size;
|
||||
err = handle->Write(handle, &wsize, seed /* All zeros now */);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
|
||||
if (wsize != info->FileSize - size)
|
||||
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
|
||||
err = handle->Flush(handle);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
|
||||
err = handle->SetPosition(handle, 0);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to seek to beginning of random seed file: %r", err);
|
||||
info->FileSize = size;
|
||||
err = handle->SetInfo(handle, &GenericFileInfo, info->Size, info);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to truncate random seed file: %r", err);
|
||||
}
|
||||
/* Update the random seed on disk before we use it */
|
||||
wsize = size;
|
||||
err = handle->Write(handle, &wsize, new_seed);
|
||||
err = handle->Write(handle, &wsize, random_bytes);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to write random seed file: %r", err);
|
||||
if (wsize != size)
|
||||
return log_error_status_stall(EFI_PROTOCOL_ERROR, L"Short write on random seed file.");
|
||||
|
||||
err = handle->Flush(handle);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to flush random seed file: %r", err);
|
||||
|
||||
/* We are good to go */
|
||||
err = efivar_set_raw(LOADER_GUID, L"LoaderRandomSeed", for_kernel, size, 0);
|
||||
err = BS->AllocatePool(EfiACPIReclaimMemory, sizeof(*new_seed_table) + DESIRED_SEED_SIZE,
|
||||
(void **) &new_seed_table);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to write random seed to EFI variable: %r", err);
|
||||
return log_error_status_stall(err, L"Failed to allocate EFI table for random seed: %r", err);
|
||||
new_seed_table->size = DESIRED_SEED_SIZE;
|
||||
|
||||
/* hash = hash_key || 1 */
|
||||
sha256_init_ctx(&hash);
|
||||
sha256_process_bytes(hash_key, sizeof(hash_key), &hash);
|
||||
sha256_process_bytes(&(const uint8_t){ 1 }, sizeof(uint8_t), &hash);
|
||||
/* new_seed_table->seed = HASH(hash) */
|
||||
sha256_finish_ctx(&hash, new_seed_table->seed);
|
||||
|
||||
err = BS->InstallConfigurationTable(&(EFI_GUID)LINUX_EFI_RANDOM_SEED_TABLE_GUID, new_seed_table);
|
||||
if (err != EFI_SUCCESS)
|
||||
return log_error_status_stall(err, L"Failed to install EFI table for random seed: %r", err);
|
||||
TAKE_PTR(new_seed_table);
|
||||
|
||||
if (previous_seed_table) {
|
||||
/* Now that we've succeeded in installing the new table, we can safely nuke the old one. */
|
||||
explicit_bzero_safe(previous_seed_table->seed, previous_seed_table->size);
|
||||
explicit_bzero_safe(previous_seed_table, sizeof(*previous_seed_table));
|
||||
free(previous_seed_table);
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,21 @@
|
||||
#define UINTN_MAX (~(UINTN)0)
|
||||
#define INTN_MAX ((INTN)(UINTN_MAX>>1))
|
||||
|
||||
#ifdef __OPTIMIZE__
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
#if __has_attribute(__error__)
|
||||
__attribute__((noreturn)) extern void __assert_cl_failure__(void) __attribute__((__error__("compile-time assertion failed")));
|
||||
#else
|
||||
__attribute__((noreturn)) extern void __assert_cl_failure__(void);
|
||||
#endif
|
||||
/* assert_cl generates a later-stage compile-time assertion when constant folding occurs. */
|
||||
#define assert_cl(condition) if (!(condition)) __assert_cl_failure__()
|
||||
#else
|
||||
#define assert_cl(condition) assert(condition)
|
||||
#endif
|
||||
|
||||
/* gnu-efi format specifiers for integers are fixed to either 64bit with 'l' and 32bit without a size prefix.
|
||||
* We rely on %u/%d/%x to format regular ints, so ensure the size is what we expect. At the same time, we also
|
||||
* need specifiers for (U)INTN which are native (pointer) sized. */
|
||||
@@ -43,6 +58,16 @@ static inline void freep(void *p) {
|
||||
|
||||
#define _cleanup_free_ _cleanup_(freep)
|
||||
|
||||
static __always_inline void erase_obj(void *p) {
|
||||
size_t l;
|
||||
assert_cl(p != NULL);
|
||||
l = __builtin_object_size(p, 0);
|
||||
assert_cl(l != (size_t) -1);
|
||||
explicit_bzero_safe(p, l);
|
||||
}
|
||||
|
||||
#define _cleanup_erase_ _cleanup_(erase_obj)
|
||||
|
||||
_malloc_ _alloc_(1) _returns_nonnull_ _warn_unused_result_
|
||||
static inline void *xmalloc(size_t size) {
|
||||
void *p;
|
||||
|
||||
+13
-69
@@ -12,79 +12,23 @@
|
||||
#include "random-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
/* If a random seed was passed by the boot loader in the LoaderRandomSeed EFI variable, let's credit it to
|
||||
* the kernel's random pool, but only once per boot. If this is run very early during initialization we can
|
||||
* instantly boot up with a filled random pool.
|
||||
*
|
||||
* This makes no judgement on the entropy passed, it's the job of the boot loader to only pass us a seed that
|
||||
* is suitably validated. */
|
||||
|
||||
static void lock_down_efi_variables(void) {
|
||||
void lock_down_efi_variables(void) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
int r;
|
||||
|
||||
fd = open(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), O_RDONLY|O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
if (errno != ENOENT)
|
||||
log_warning_errno(errno, "Unable to open LoaderSystemToken EFI variable, ignoring: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Paranoia: let's restrict access modes of these a bit, so that unprivileged users can't use them to
|
||||
* identify the system or gain too much insight into what we might have credited to the entropy
|
||||
* pool. */
|
||||
FOREACH_STRING(path,
|
||||
EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)),
|
||||
EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken))) {
|
||||
|
||||
r = chattr_path(path, 0, FS_IMMUTABLE_FL, NULL);
|
||||
if (r == -ENOENT)
|
||||
continue;
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from %s, ignoring: %m", path);
|
||||
|
||||
if (chmod(path, 0600) < 0)
|
||||
log_warning_errno(errno, "Failed to reduce access mode of %s, ignoring: %m", path);
|
||||
}
|
||||
}
|
||||
|
||||
int efi_take_random_seed(void) {
|
||||
_cleanup_free_ void *value = NULL;
|
||||
size_t size;
|
||||
int r;
|
||||
|
||||
/* Paranoia comes first. */
|
||||
lock_down_efi_variables();
|
||||
|
||||
if (access("/run/systemd/efi-random-seed-taken", F_OK) < 0) {
|
||||
if (errno != ENOENT) {
|
||||
log_warning_errno(errno, "Failed to determine whether we already used the random seed token, not using it.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ENOENT means we haven't used it yet. */
|
||||
} else {
|
||||
log_debug("EFI random seed already used, not using again.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = efi_get_variable(EFI_LOADER_VARIABLE(LoaderRandomSeed), NULL, &value, &size);
|
||||
if (r == -EOPNOTSUPP) {
|
||||
log_debug_errno(r, "System lacks EFI support, not initializing random seed from EFI variable.");
|
||||
return 0;
|
||||
}
|
||||
if (r == -ENOENT) {
|
||||
log_debug_errno(r, "Boot loader did not pass LoaderRandomSeed EFI variable, not crediting any entropy.");
|
||||
return 0;
|
||||
}
|
||||
r = chattr_fd(fd, 0, FS_IMMUTABLE_FL, NULL);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to read LoaderRandomSeed EFI variable, ignoring: %m");
|
||||
|
||||
if (size == 0)
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Random seed passed from boot loader has zero size? Ignoring.");
|
||||
|
||||
/* Before we use the seed, let's mark it as used, so that we never credit it twice. Also, it's a nice
|
||||
* way to let users known that we successfully acquired entropy from the boot loader. */
|
||||
r = touch("/run/systemd/efi-random-seed-taken");
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Unable to mark EFI random seed as used, not using it: %m");
|
||||
|
||||
r = random_write_entropy(-1, value, size, true);
|
||||
if (r < 0)
|
||||
return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
|
||||
|
||||
log_info("Successfully credited entropy passed from boot loader.");
|
||||
return 1;
|
||||
log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from LoaderSystemToken EFI variable, ignoring: %m");
|
||||
if (fchmod(fd, 0600) < 0)
|
||||
log_warning_errno(errno, "Failed to reduce access mode of LoaderSystemToken EFI variable, ignoring: %m");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int efi_take_random_seed(void);
|
||||
void lock_down_efi_variables(void);
|
||||
|
||||
+2
-2
@@ -2831,8 +2831,8 @@ int main(int argc, char *argv[]) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
/* The efivarfs is now mounted, let's read the random seed off it */
|
||||
(void) efi_take_random_seed();
|
||||
/* The efivarfs is now mounted, let's lock down the system token. */
|
||||
lock_down_efi_variables();
|
||||
|
||||
/* Cache command-line options passed from EFI variables */
|
||||
if (!skip_setup)
|
||||
|
||||
+111
-11
@@ -16,7 +16,10 @@
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "build.h"
|
||||
#include "chase-symlinks.h"
|
||||
#include "efi-loader.h"
|
||||
#include "fd-util.h"
|
||||
#include "find-esp.h"
|
||||
#include "fs-util.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
@@ -26,6 +29,7 @@
|
||||
#include "mkdir.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "random-util.h"
|
||||
#include "string-table.h"
|
||||
@@ -185,7 +189,7 @@ static int load_seed_file(
|
||||
if (ret_hash_state) {
|
||||
struct sha256_ctx *hash_state;
|
||||
|
||||
hash_state = malloc(sizeof(struct sha256_ctx));
|
||||
hash_state = new(struct sha256_ctx, 1);
|
||||
if (!hash_state)
|
||||
return log_oom();
|
||||
|
||||
@@ -311,6 +315,101 @@ static int save_seed_file(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int refresh_boot_seed(void) {
|
||||
uint8_t buffer[RANDOM_EFI_SEED_SIZE];
|
||||
struct sha256_ctx hash_state;
|
||||
_cleanup_free_ void *seed_file_bytes = NULL;
|
||||
_cleanup_free_ char *esp_path = NULL;
|
||||
_cleanup_close_ int seed_fd = -1;
|
||||
size_t len;
|
||||
ssize_t r;
|
||||
|
||||
assert_cc(RANDOM_EFI_SEED_SIZE == SHA256_DIGEST_SIZE);
|
||||
|
||||
r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path,
|
||||
NULL, NULL, NULL, NULL, NULL);
|
||||
if (r < 0) {
|
||||
if (r == -ENOKEY) {
|
||||
log_debug_errno(r, "Couldn't find any ESP, so not updating ESP random seed.");
|
||||
return 0;
|
||||
}
|
||||
return r; /* find_esp_and_warn() already logged */
|
||||
}
|
||||
|
||||
seed_fd = chase_symlinks_and_open("/loader/random-seed", esp_path,
|
||||
CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
|
||||
O_RDWR|O_CLOEXEC|O_NOCTTY, NULL);
|
||||
if (seed_fd == -ENOENT) {
|
||||
uint64_t features;
|
||||
|
||||
r = efi_loader_get_features(&features);
|
||||
if (r == 0 && FLAGS_SET(features, EFI_LOADER_FEATURE_RANDOM_SEED)) {
|
||||
int dir_fd = chase_symlinks_and_open("/loader", esp_path,
|
||||
CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS,
|
||||
O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
|
||||
if (dir_fd >= 0) {
|
||||
seed_fd = openat(dir_fd, "random-seed", O_CREAT|O_EXCL|O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
|
||||
close(dir_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seed_fd < 0) {
|
||||
log_debug_errno(seed_fd, "Failed to open EFI seed path: %m");
|
||||
return 0;
|
||||
}
|
||||
r = random_seed_size(seed_fd, &len);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine EFI seed path length: %m");
|
||||
seed_file_bytes = malloc(len);
|
||||
if (!seed_file_bytes)
|
||||
return log_oom();
|
||||
r = loop_read(seed_fd, seed_file_bytes, len, false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to read EFI seed file: %m");
|
||||
|
||||
/* Hash the old seed in so that we never regress in entropy. */
|
||||
sha256_init_ctx(&hash_state);
|
||||
sha256_process_bytes(&r, sizeof(r), &hash_state);
|
||||
sha256_process_bytes(seed_file_bytes, r, &hash_state);
|
||||
|
||||
/* We're doing this opportunistically, so if the seeding dance before didn't manage to initialize the
|
||||
* RNG, there's no point in doing it here. Secondly, getrandom(GRND_NONBLOCK) has been around longer
|
||||
* than EFI seeding anyway, so there's no point in having non-getrandom() fallbacks here. So if this
|
||||
* fails, just return early to cut our losses. */
|
||||
r = getrandom(buffer, sizeof(buffer), GRND_NONBLOCK);
|
||||
if (r < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
log_debug_errno(errno, "Random pool not initialized yet, so skipping EFI seed update");
|
||||
return 0;
|
||||
}
|
||||
if (errno == ENOSYS) {
|
||||
log_debug_errno(errno, "getrandom() not available, so skipping EFI seed update");
|
||||
return 0;
|
||||
}
|
||||
return log_error_errno(errno, "Failed to generate random bytes for EFI seed: %m");
|
||||
}
|
||||
assert(r == sizeof(buffer));
|
||||
|
||||
/* Hash the new seed into the state containing the old one to generate our final seed. */
|
||||
sha256_process_bytes(&r, sizeof(r), &hash_state);
|
||||
sha256_process_bytes(buffer, r, &hash_state);
|
||||
sha256_finish_ctx(&hash_state, buffer);
|
||||
|
||||
if (lseek(seed_fd, 0, SEEK_SET) < 0)
|
||||
return log_error_errno(errno, "Failed to seek to beginning of EFI seed file: %m");
|
||||
r = loop_write(seed_fd, buffer, sizeof(buffer), false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write new EFI seed file: %m");
|
||||
if (ftruncate(seed_fd, sizeof(buffer)) < 0)
|
||||
return log_error_errno(errno, "Failed to truncate EFI seed file: %m");
|
||||
r = fsync_full(seed_fd);
|
||||
if (r < 0)
|
||||
return log_error_errno(errno, "Failed to fsync EFI seed file: %m");
|
||||
|
||||
log_debug("Updated random seed in ESP");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
@@ -402,15 +501,15 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m");
|
||||
|
||||
random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY);
|
||||
if (random_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open /dev/urandom: %m");
|
||||
|
||||
/* When we load the seed we read it and write it to the device and then immediately update the saved
|
||||
* seed with new data, to make sure the next boot gets seeded differently. */
|
||||
|
||||
switch (arg_action) {
|
||||
case ACTION_LOAD:
|
||||
random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY);
|
||||
if (random_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open /dev/urandom: %m");
|
||||
|
||||
/* First, let's write the machine ID into /dev/urandom, not crediting entropy. See
|
||||
* load_machine_id() for an explanation why. */
|
||||
load_machine_id(random_fd);
|
||||
@@ -428,8 +527,10 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_full_errno(level, open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m");
|
||||
log_full_errno(level, errno, "Failed to open " RANDOM_SEED " for reading: %m");
|
||||
r = -errno;
|
||||
|
||||
return missing ? 0 : -errno;
|
||||
(void) refresh_boot_seed();
|
||||
return missing ? 0 : r;
|
||||
}
|
||||
} else
|
||||
write_seed_file = true;
|
||||
@@ -439,10 +540,7 @@ static int run(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
case ACTION_SAVE:
|
||||
random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
if (random_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open /dev/urandom: %m");
|
||||
|
||||
(void) refresh_boot_seed();
|
||||
seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
|
||||
if (seed_fd < 0)
|
||||
return log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m");
|
||||
@@ -460,9 +558,11 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (read_seed_file)
|
||||
if (read_seed_file) {
|
||||
r = load_seed_file(seed_fd, random_fd, seed_size,
|
||||
write_seed_file ? &hash_state : NULL);
|
||||
(void) refresh_boot_seed();
|
||||
}
|
||||
|
||||
if (r >= 0 && write_seed_file)
|
||||
r = save_seed_file(seed_fd, random_fd, seed_size, synchronous, hash_state);
|
||||
|
||||
Reference in New Issue
Block a user