Path: blob/master/drivers/firmware/efi/libstub/random.c
26483 views
// SPDX-License-Identifier: GPL-2.01/*2* Copyright (C) 2016 Linaro Ltd; <[email protected]>3*/45#include <linux/efi.h>6#include <asm/efi.h>78#include "efistub.h"910typedef union efi_rng_protocol efi_rng_protocol_t;1112union efi_rng_protocol {13struct {14efi_status_t (__efiapi *get_info)(efi_rng_protocol_t *,15unsigned long *,16efi_guid_t *);17efi_status_t (__efiapi *get_rng)(efi_rng_protocol_t *,18efi_guid_t *, unsigned long,19u8 *out);20};21struct {22u32 get_info;23u32 get_rng;24} mixed_mode;25};2627/**28* efi_get_random_bytes() - fill a buffer with random bytes29* @size: size of the buffer30* @out: caller allocated buffer to receive the random bytes31*32* The call will fail if either the firmware does not implement the33* EFI_RNG_PROTOCOL or there are not enough random bytes available to fill34* the buffer.35*36* Return: status code37*/38efi_status_t efi_get_random_bytes(unsigned long size, u8 *out)39{40efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;41efi_status_t status;42efi_rng_protocol_t *rng = NULL;4344status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);45if (status != EFI_SUCCESS)46return status;4748return efi_call_proto(rng, get_rng, NULL, size, out);49}5051/**52* efi_random_get_seed() - provide random seed as configuration table53*54* The EFI_RNG_PROTOCOL is used to read random bytes. These random bytes are55* saved as a configuration table which can be used as entropy by the kernel56* for the initialization of its pseudo random number generator.57*58* If the EFI_RNG_PROTOCOL is not available or there are not enough random bytes59* available, the configuration table will not be installed and an error code60* will be returned.61*62* Return: status code63*/64efi_status_t efi_random_get_seed(void)65{66efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID;67efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW;68efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID;69struct linux_efi_random_seed *prev_seed, *seed = NULL;70int prev_seed_size = 0, seed_size = EFI_RANDOM_SEED_SIZE;71unsigned long nv_seed_size = 0, offset = 0;72efi_rng_protocol_t *rng = NULL;73efi_status_t status;7475status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng);76if (status != EFI_SUCCESS)77seed_size = 0;7879// Call GetVariable() with a zero length buffer to obtain the size80get_efi_var(L"RandomSeed", &rng_table_guid, NULL, &nv_seed_size, NULL);81if (!seed_size && !nv_seed_size)82return status;8384seed_size += nv_seed_size;8586/*87* Check whether a seed was provided by a prior boot stage. In that88* case, instead of overwriting it, let's create a new buffer that can89* hold both, and concatenate the existing and the new seeds.90* Note that we should read the seed size with caution, in case the91* table got corrupted in memory somehow.92*/93prev_seed = get_efi_config_table(rng_table_guid);94if (prev_seed && prev_seed->size <= 512U) {95prev_seed_size = prev_seed->size;96seed_size += prev_seed_size;97}9899/*100* Use EFI_ACPI_RECLAIM_MEMORY here so that it is guaranteed that the101* allocation will survive a kexec reboot (although we refresh the seed102* beforehand)103*/104status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY,105struct_size(seed, bits, seed_size),106(void **)&seed);107if (status != EFI_SUCCESS) {108efi_warn("Failed to allocate memory for RNG seed.\n");109goto err_warn;110}111112if (rng) {113status = efi_call_proto(rng, get_rng, &rng_algo_raw,114EFI_RANDOM_SEED_SIZE, seed->bits);115116if (status == EFI_UNSUPPORTED)117/*118* Use whatever algorithm we have available if the raw algorithm119* is not implemented.120*/121status = efi_call_proto(rng, get_rng, NULL,122EFI_RANDOM_SEED_SIZE, seed->bits);123124if (status == EFI_SUCCESS)125offset = EFI_RANDOM_SEED_SIZE;126}127128if (nv_seed_size) {129status = get_efi_var(L"RandomSeed", &rng_table_guid, NULL,130&nv_seed_size, seed->bits + offset);131132if (status == EFI_SUCCESS)133/*134* We delete the seed here, and /hope/ that this causes135* EFI to also zero out its representation on disk.136* This is somewhat idealistic, but overwriting the137* variable with zeros is likely just as fraught too.138* TODO: in the future, maybe we can hash it forward139* instead, and write a new seed.140*/141status = set_efi_var(L"RandomSeed", &rng_table_guid, 0,1420, NULL);143144if (status == EFI_SUCCESS)145offset += nv_seed_size;146else147memzero_explicit(seed->bits + offset, nv_seed_size);148}149150if (!offset)151goto err_freepool;152153if (prev_seed_size) {154memcpy(seed->bits + offset, prev_seed->bits, prev_seed_size);155offset += prev_seed_size;156}157158seed->size = offset;159status = efi_bs_call(install_configuration_table, &rng_table_guid, seed);160if (status != EFI_SUCCESS)161goto err_freepool;162163if (prev_seed_size) {164/* wipe and free the old seed if we managed to install the new one */165memzero_explicit(prev_seed->bits, prev_seed_size);166efi_bs_call(free_pool, prev_seed);167}168return EFI_SUCCESS;169170err_freepool:171memzero_explicit(seed, struct_size(seed, bits, seed_size));172efi_bs_call(free_pool, seed);173efi_warn("Failed to obtain seed from EFI_RNG_PROTOCOL or EFI variable\n");174err_warn:175if (prev_seed)176efi_warn("Retaining bootloader-supplied seed only");177return status;178}179180181