| 1 | #if defined (UEFI) |
| 2 | |
| 3 | #include <stdint.h> |
| 4 | #include <stddef.h> |
| 5 | #include <stdbool.h> |
| 6 | #include <efi.h> |
| 7 | #include <lib/rng_seed.h> |
| 8 | #include <lib/misc.h> |
| 9 | #include <lib/print.h> |
| 10 | #include <lib/libc.h> |
| 11 | |
| 12 | #define LINUX_EFI_RANDOM_SEED_TABLE_GUID \ |
| 13 | { 0x1ce1e5bc, 0x7ceb, 0x42f2, { 0x81, 0xe5, 0x8a, 0xad, 0xf1, 0x80, 0xf5, 0x7b } } |
| 14 | |
| 15 | #define EFI_RANDOM_SEED_SIZE 32 |
| 16 | |
| 17 | struct linux_efi_random_seed { |
| 18 | uint32_t size; |
| 19 | uint8_t bits[]; |
| 20 | } __attribute__((packed)); |
| 21 | |
| 22 | // Pull entropy from EFI_RNG_PROTOCOL while boot services are alive, mix in |
| 23 | // the NVRAM-resident "RandomSeed" variable if the OS left one for us, and |
| 24 | // publish the result as the LINUX_EFI_RANDOM_SEED_TABLE configuration table |
| 25 | // for the kernel's RNG to consume during early boot. Limine bypasses the |
| 26 | // EFI stub that would normally do this. |
| 27 | void rng_seed_install(void) { |
| 28 | EFI_GUID rng_table_guid = LINUX_EFI_RANDOM_SEED_TABLE_GUID; |
| 29 | |
| 30 | // The RNG protocol is optional; the NVRAM seed alone is also a valid |
| 31 | // source. |
| 32 | EFI_GUID rng_guid = EFI_RNG_PROTOCOL_GUID; |
| 33 | EFI_RNG_PROTOCOL *rng = NULL; |
| 34 | if (gBS->LocateProtocol(&rng_guid, NULL, (void **)&rng) != EFI_SUCCESS) { |
| 35 | rng = NULL; |
| 36 | } |
| 37 | |
| 38 | // Probe the NVRAM "RandomSeed" variable; if the OS left one for us, |
| 39 | // we'll consume and delete it so it isn't reused on the next boot. |
| 40 | UINTN nv_seed_size = 0; |
| 41 | EFI_STATUS probe = gRT->GetVariable(L"RandomSeed", &rng_table_guid, |
| 42 | NULL, &nv_seed_size, NULL); |
| 43 | if (probe != EFI_BUFFER_TOO_SMALL || nv_seed_size > 512) { |
| 44 | nv_seed_size = 0; |
| 45 | } |
| 46 | |
| 47 | // A prior boot stage (shim, another stub) may have installed a seed |
| 48 | // already. Preserve it by concatenating rather than overwriting. |
| 49 | struct linux_efi_random_seed *prev_seed = NULL; |
| 50 | uint32_t prev_seed_size = 0; |
| 51 | for (UINTN i = 0; i < gST->NumberOfTableEntries; i++) { |
| 52 | if (memcmp(&gST->ConfigurationTable[i].VendorGuid, |
| 53 | &rng_table_guid, sizeof(EFI_GUID)) == 0) { |
| 54 | prev_seed = gST->ConfigurationTable[i].VendorTable; |
| 55 | if (prev_seed->size <= 512) { |
| 56 | prev_seed_size = prev_seed->size; |
| 57 | } |
| 58 | break; |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | UINTN rng_bytes = (rng != NULL) ? EFI_RANDOM_SEED_SIZE : 0; |
| 63 | |
| 64 | if (rng_bytes == 0 && nv_seed_size == 0 && prev_seed_size == 0) { |
| 65 | return; |
| 66 | } |
| 67 | |
| 68 | UINTN total_size = sizeof(struct linux_efi_random_seed) |
| 69 | + rng_bytes + nv_seed_size + prev_seed_size; |
| 70 | |
| 71 | struct linux_efi_random_seed *seed = NULL; |
| 72 | EFI_STATUS status = gBS->AllocatePool(EfiACPIReclaimMemory, total_size, |
| 73 | (void **)&seed); |
| 74 | if (status != EFI_SUCCESS) { |
| 75 | printv("rng: failed to allocate random seed table: %X\n", (uint64_t)status); |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | memset(seed, 0, total_size); |
| 80 | |
| 81 | UINTN offset = 0; |
| 82 | |
| 83 | // EFI_RNG_PROTOCOL output. Prefer the raw algorithm. |
| 84 | if (rng != NULL) { |
| 85 | EFI_GUID rng_algo_raw = EFI_RNG_ALGORITHM_RAW; |
| 86 | status = rng->GetRNG(rng, &rng_algo_raw, EFI_RANDOM_SEED_SIZE, seed->bits); |
| 87 | if (status == EFI_UNSUPPORTED) { |
| 88 | status = rng->GetRNG(rng, NULL, EFI_RANDOM_SEED_SIZE, seed->bits); |
| 89 | } |
| 90 | if (status == EFI_SUCCESS) { |
| 91 | offset += EFI_RANDOM_SEED_SIZE; |
| 92 | } else { |
| 93 | printv("rng: GetRNG failed: %X\n", (uint64_t)status); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | // NVRAM "RandomSeed" variable, then delete it so the same bytes |
| 98 | // aren't reused on the next boot. |
| 99 | if (nv_seed_size > 0) { |
| 100 | UINTN got_size = nv_seed_size; |
| 101 | status = gRT->GetVariable(L"RandomSeed", &rng_table_guid, NULL, |
| 102 | &got_size, seed->bits + offset); |
| 103 | if (status == EFI_SUCCESS) { |
| 104 | gRT->SetVariable(L"RandomSeed", &rng_table_guid, 0, 0, NULL); |
| 105 | offset += got_size; |
| 106 | } else { |
| 107 | // Read failed despite probe succeeding. Wipe the slot to avoid |
| 108 | // publishing stale heap contents. |
| 109 | volatile uint8_t *p = (volatile uint8_t *)(seed->bits + offset); |
| 110 | for (size_t i = 0; i < nv_seed_size; i++) { |
| 111 | p[i] = 0; |
| 112 | } |
| 113 | asm volatile ("" ::: "memory"); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // Previous-stage seed. |
| 118 | if (prev_seed_size > 0) { |
| 119 | memcpy(seed->bits + offset, prev_seed->bits, prev_seed_size); |
| 120 | offset += prev_seed_size; |
| 121 | } |
| 122 | |
| 123 | if (offset == 0) { |
| 124 | volatile uint8_t *p = (volatile uint8_t *)seed; |
| 125 | for (size_t i = 0; i < total_size; i++) { |
| 126 | p[i] = 0; |
| 127 | } |
| 128 | asm volatile ("" ::: "memory"); |
| 129 | gBS->FreePool(seed); |
| 130 | return; |
| 131 | } |
| 132 | |
| 133 | seed->size = (uint32_t)offset; |
| 134 | |
| 135 | status = gBS->InstallConfigurationTable(&rng_table_guid, seed); |
| 136 | if (status != EFI_SUCCESS) { |
| 137 | printv("rng: failed to install random seed table: %X\n", (uint64_t)status); |
| 138 | volatile uint8_t *p = (volatile uint8_t *)seed; |
| 139 | for (size_t i = 0; i < total_size; i++) { |
| 140 | p[i] = 0; |
| 141 | } |
| 142 | asm volatile ("" ::: "memory"); |
| 143 | gBS->FreePool(seed); |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | if (prev_seed_size > 0) { |
| 148 | volatile uint8_t *p = (volatile uint8_t *)prev_seed; |
| 149 | size_t prev_total = sizeof(struct linux_efi_random_seed) + prev_seed_size; |
| 150 | for (size_t i = 0; i < prev_total; i++) { |
| 151 | p[i] = 0; |
| 152 | } |
| 153 | asm volatile ("" ::: "memory"); |
| 154 | // Assumes the prior publisher used AllocatePool. |
| 155 | gBS->FreePool(prev_seed); |
| 156 | } |
| 157 | |
| 158 | printv("rng: installed %u-byte random seed as configuration table\n", |
| 159 | seed->size); |
| 160 | } |
| 161 | |
| 162 | #endif |