Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2016 Linaro Ltd; <ard.biesheuvel@linaro.org> */ #include <linux/efi.h> #include <asm/efi.h> #include "efistub.h" typedef union efi_rng_protocol efi_rng_protocol_t; union efi_rng_protocol { struct { efi_status_t (__efiapi *get_info)(efi_rng_protocol_t *, unsigned long *, efi_guid_t *); efi_status_t (__efiapi *get_rng)(efi_rng_protocol_t *, efi_guid_t *, unsigned long, u8 *out); }; struct { u32 get_info; u32 get_rng; } mixed_mode; }; /** * efi_get_random_bytes() - fill a buffer with random bytes * @size: size of the buffer * @out: caller allocated buffer to receive the random bytes * * The call will fail if either the firmware does not implement the * EFI_RNG_PROTOCOL or there are not enough random bytes available to fill * the buffer. * * Return: status code */ efi_status_t efi_get_random_bytes(unsigned long size, u8 *out) { efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; efi_status_t status; efi_rng_protocol_t *rng = NULL; status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng); if (status != EFI_SUCCESS) return status; return efi_call_proto(rng, get_rng, NULL, size, out); } /** * efi_random_get_seed() - provide random seed as configuration table * * The EFI_RNG_PROTOCOL is used to read random bytes. These random bytes are * saved as a configuration table which can be used as entropy by the kernel * for the initialization of its pseudo random number generator. * * If the EFI_RNG_PROTOCOL is not available or there are not enough random bytes * available, the configuration table will not be installed and an error code * will be returned. * * Return: status code */ efi_status_t efi_random_get_seed(void) { efi_guid_t rng_proto = EFI_RNG_PROTOCOL_GUID; efi_guid_t rng_algo_raw = EFI_RNG_ALGORITHM_RAW; efi_guid_t rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID; struct linux_efi_random_seed *prev_seed, *seed = NULL; int prev_seed_size = 0, seed_size = EFI_RANDOM_SEED_SIZE; unsigned long nv_seed_size = 0, offset = 0; efi_rng_protocol_t *rng = NULL; efi_status_t status; status = efi_bs_call(locate_protocol, &rng_proto, NULL, (void **)&rng); if (status != EFI_SUCCESS) seed_size = 0; // Call GetVariable() with a zero length buffer to obtain the size get_efi_var(L"RandomSeed", &rng_table_guid, NULL, &nv_seed_size, NULL); if (!seed_size && !nv_seed_size) return status; seed_size += nv_seed_size; /* * Check whether a seed was provided by a prior boot stage. In that * case, instead of overwriting it, let's create a new buffer that can * hold both, and concatenate the existing and the new seeds. * Note that we should read the seed size with caution, in case the * table got corrupted in memory somehow. */ prev_seed = get_efi_config_table(rng_table_guid); if (prev_seed && prev_seed->size <= 512U) { prev_seed_size = prev_seed->size; seed_size += prev_seed_size; } /* * Use EFI_ACPI_RECLAIM_MEMORY here so that it is guaranteed that the * allocation will survive a kexec reboot (although we refresh the seed * beforehand) */ status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY, struct_size(seed, bits, seed_size), (void **)&seed); if (status != EFI_SUCCESS) { efi_warn("Failed to allocate memory for RNG seed.\n"); goto err_warn; } if (rng) { status = efi_call_proto(rng, get_rng, &rng_algo_raw, EFI_RANDOM_SEED_SIZE, seed->bits); if (status == EFI_UNSUPPORTED) /* * Use whatever algorithm we have available if the raw algorithm * is not implemented. */ status = efi_call_proto(rng, get_rng, NULL, EFI_RANDOM_SEED_SIZE, seed->bits); if (status == EFI_SUCCESS) offset = EFI_RANDOM_SEED_SIZE; } if (nv_seed_size) { status = get_efi_var(L"RandomSeed", &rng_table_guid, NULL, &nv_seed_size, seed->bits + offset); if (status == EFI_SUCCESS) /* * We delete the seed here, and /hope/ that this causes * EFI to also zero out its representation on disk. * This is somewhat idealistic, but overwriting the * variable with zeros is likely just as fraught too. * TODO: in the future, maybe we can hash it forward * instead, and write a new seed. */ status = set_efi_var(L"RandomSeed", &rng_table_guid, 0, 0, NULL); if (status == EFI_SUCCESS) offset += nv_seed_size; else memzero_explicit(seed->bits + offset, nv_seed_size); } if (!offset) goto err_freepool; if (prev_seed_size) { memcpy(seed->bits + offset, prev_seed->bits, prev_seed_size); offset += prev_seed_size; } seed->size = offset; status = efi_bs_call(install_configuration_table, &rng_table_guid, seed); if (status != EFI_SUCCESS) goto err_freepool; if (prev_seed_size) { /* wipe and free the old seed if we managed to install the new one */ memzero_explicit(prev_seed->bits, prev_seed_size); efi_bs_call(free_pool, prev_seed); } return EFI_SUCCESS; err_freepool: memzero_explicit(seed, struct_size(seed, bits, seed_size)); efi_bs_call(free_pool, seed); efi_warn("Failed to obtain seed from EFI_RNG_PROTOCOL or EFI variable\n"); err_warn: if (prev_seed) efi_warn("Retaining bootloader-supplied seed only"); return status; } |