]>
Commit | Line | Data |
---|---|---|
e4dcf7aa LP |
1 | #include <efi.h> |
2 | #include <efilib.h> | |
3 | ||
4c858c6f | 4 | #include "missing_efi.h" |
e4dcf7aa LP |
5 | #include "random-seed.h" |
6 | #include "sha256.h" | |
7 | #include "util.h" | |
8 | #include "shim.h" | |
9 | ||
10 | #define RANDOM_MAX_SIZE_MIN (32U) | |
11 | #define RANDOM_MAX_SIZE_MAX (32U*1024U) | |
12 | ||
13 | static const EFI_GUID rng_protocol_guid = EFI_RNG_PROTOCOL_GUID; | |
14 | ||
15 | /* SHA256 gives us 256/8=32 bytes */ | |
16 | #define HASH_VALUE_SIZE 32 | |
17 | ||
18 | static EFI_STATUS acquire_rng(UINTN size, VOID **ret) { | |
19 | _cleanup_freepool_ VOID *data = NULL; | |
20 | EFI_RNG_PROTOCOL *rng; | |
21 | EFI_STATUS err; | |
22 | ||
23 | /* Try to acquire the specified number of bytes from the UEFI RNG */ | |
24 | ||
25 | err = LibLocateProtocol((EFI_GUID*) &rng_protocol_guid, (VOID**) &rng); | |
26 | if (EFI_ERROR(err)) { | |
27 | Print(L"Failed to acquire RNG protocol: %r\n", err); | |
28 | return err; | |
29 | } | |
30 | if (!rng) { | |
31 | /* Print(L"RNG protocol not available.\n"); */ | |
32 | return EFI_UNSUPPORTED; | |
33 | } | |
34 | ||
35 | data = AllocatePool(size); | |
36 | if (!data) | |
37 | return log_oom(); | |
38 | ||
39 | err = uefi_call_wrapper(rng->GetRNG, 3, rng, NULL, size, data); | |
40 | if (EFI_ERROR(err)) { | |
41 | Print(L"Failed to acquire RNG data: %r\n", err); | |
42 | return err; | |
43 | } | |
44 | ||
45 | *ret = TAKE_PTR(data); | |
46 | return EFI_SUCCESS; | |
47 | } | |
48 | ||
49 | static VOID hash_once( | |
50 | const VOID *old_seed, | |
51 | const VOID *rng, | |
52 | UINTN size, | |
53 | const VOID *system_token, | |
54 | UINTN system_token_size, | |
55 | UINTN counter, | |
56 | UINT8 ret[static HASH_VALUE_SIZE]) { | |
57 | ||
58 | /* This hashes together: | |
59 | * | |
60 | * 1. The contents of the old seed file | |
61 | * 2. Some random data acquired from the UEFI RNG (optional) | |
62 | * 3. Some 'system token' the installer installed as EFI variable (optional) | |
63 | * 4. A counter value | |
64 | * | |
65 | * And writes the result to the specified buffer. | |
66 | */ | |
67 | ||
68 | struct sha256_ctx hash; | |
69 | ||
70 | sha256_init_ctx(&hash); | |
71 | sha256_process_bytes(old_seed, size, &hash); | |
72 | if (rng) | |
73 | sha256_process_bytes(rng, size, &hash); | |
74 | if (system_token_size > 0) | |
75 | sha256_process_bytes(system_token, system_token_size, &hash); | |
76 | sha256_process_bytes(&counter, sizeof(counter), &hash); | |
77 | sha256_finish_ctx(&hash, ret); | |
78 | } | |
79 | ||
80 | static EFI_STATUS hash_many( | |
81 | const VOID *old_seed, | |
82 | const VOID *rng, | |
83 | UINTN size, | |
84 | const VOID *system_token, | |
85 | UINTN system_token_size, | |
86 | UINTN counter_start, | |
87 | UINTN n, | |
88 | VOID **ret) { | |
89 | ||
90 | _cleanup_freepool_ VOID *output = NULL; | |
91 | UINTN i; | |
92 | ||
93 | /* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the | |
94 | * range counter_start…counter_start+n-1. */ | |
95 | ||
96 | output = AllocatePool(n * HASH_VALUE_SIZE); | |
97 | if (!output) | |
98 | return log_oom(); | |
99 | ||
100 | for (i = 0; i < n; i++) | |
101 | hash_once(old_seed, rng, size, | |
102 | system_token, system_token_size, | |
103 | counter_start + i, | |
104 | (UINT8*) output + (i * HASH_VALUE_SIZE)); | |
105 | ||
106 | *ret = TAKE_PTR(output); | |
107 | return EFI_SUCCESS; | |
108 | } | |
109 | ||
110 | static EFI_STATUS mangle_random_seed( | |
111 | const VOID *old_seed, | |
112 | const VOID *rng, | |
113 | UINTN size, | |
114 | const VOID *system_token, | |
115 | UINTN system_token_size, | |
116 | VOID **ret_new_seed, | |
117 | VOID **ret_for_kernel) { | |
118 | ||
119 | _cleanup_freepool_ VOID *new_seed = NULL, *for_kernel = NULL; | |
120 | EFI_STATUS err; | |
121 | UINTN n; | |
122 | ||
123 | /* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an | |
124 | * (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them | |
125 | * together in counter mode, generating a new seed (to replace the file on disk) and the seed for the | |
126 | * kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and | |
127 | * RNG data. */ | |
128 | ||
129 | n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE; | |
130 | ||
131 | /* Begin hashing in counter mode at counter 0 for the new seed for the disk */ | |
132 | err = hash_many(old_seed, rng, size, system_token, system_token_size, 0, n, &new_seed); | |
133 | if (EFI_ERROR(err)) | |
134 | return err; | |
135 | ||
136 | /* Continue counting at 'n' for the seed for the kernel */ | |
137 | err = hash_many(old_seed, rng, size, system_token, system_token_size, n, n, &for_kernel); | |
138 | if (EFI_ERROR(err)) | |
139 | return err; | |
140 | ||
141 | *ret_new_seed = TAKE_PTR(new_seed); | |
142 | *ret_for_kernel = TAKE_PTR(for_kernel); | |
143 | ||
144 | return EFI_SUCCESS; | |
145 | } | |
146 | ||
147 | EFI_STATUS acquire_system_token(VOID **ret, UINTN *ret_size) { | |
148 | _cleanup_freepool_ CHAR8 *data = NULL; | |
149 | EFI_STATUS err; | |
150 | UINTN size; | |
151 | ||
152 | err = efivar_get_raw(&loader_guid, L"LoaderSystemToken", &data, &size); | |
153 | if (EFI_ERROR(err)) { | |
154 | if (err != EFI_NOT_FOUND) | |
155 | Print(L"Failed to read LoaderSystemToken EFI variable: %r", err); | |
156 | return err; | |
157 | } | |
158 | ||
159 | if (size <= 0) { | |
160 | Print(L"System token too short, ignoring."); | |
161 | return EFI_NOT_FOUND; | |
162 | } | |
163 | ||
164 | *ret = TAKE_PTR(data); | |
165 | *ret_size = size; | |
166 | ||
167 | return EFI_SUCCESS; | |
168 | } | |
169 | ||
170 | static VOID validate_sha256(void) { | |
171 | ||
172 | #ifndef __OPTIMIZE__ | |
173 | /* Let's validate our SHA256 implementation. We stole it from glibc, and converted it to UEFI | |
174 | * style. We better check whether it does the right stuff. We use the simpler test vectors from the | |
175 | * SHA spec. Note that we strip this out in optimization builds. */ | |
176 | ||
177 | static const struct { | |
178 | const char *string; | |
179 | uint8_t hash[HASH_VALUE_SIZE]; | |
180 | } array[] = { | |
181 | { "abc", | |
182 | { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, | |
183 | 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, | |
184 | 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, | |
185 | 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad }}, | |
186 | ||
187 | { "", | |
188 | { 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, | |
189 | 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, | |
190 | 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, | |
191 | 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }}, | |
192 | ||
193 | { "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", | |
194 | { 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, | |
195 | 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39, | |
196 | 0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, | |
197 | 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 }}, | |
198 | ||
199 | { "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", | |
200 | { 0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80, | |
201 | 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37, | |
202 | 0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51, | |
203 | 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1 }}, | |
204 | }; | |
205 | ||
206 | UINTN i; | |
207 | ||
208 | for (i = 0; i < ELEMENTSOF(array); i++) { | |
209 | struct sha256_ctx hash; | |
210 | uint8_t result[HASH_VALUE_SIZE]; | |
211 | ||
212 | sha256_init_ctx(&hash); | |
213 | sha256_process_bytes(array[i].string, strlena((const CHAR8*) array[i].string), &hash); | |
214 | sha256_finish_ctx(&hash, result); | |
215 | ||
216 | if (CompareMem(result, array[i].hash, HASH_VALUE_SIZE) != 0) { | |
217 | Print(L"SHA256 failed validation.\n"); | |
218 | uefi_call_wrapper(BS->Stall, 1, 120 * 1000 * 1000); | |
219 | return; | |
220 | } | |
221 | } | |
222 | ||
223 | Print(L"SHA256 validated\n"); | |
224 | #endif | |
225 | } | |
226 | ||
227 | EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) { | |
228 | _cleanup_freepool_ VOID *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL; | |
229 | _cleanup_(FileHandleClosep) EFI_FILE_HANDLE handle = NULL; | |
230 | UINTN size, rsize, wsize, system_token_size = 0; | |
231 | _cleanup_freepool_ EFI_FILE_INFO *info = NULL; | |
232 | EFI_STATUS err; | |
233 | ||
234 | validate_sha256(); | |
235 | ||
236 | if (mode == RANDOM_SEED_OFF) { | |
237 | /* Print(L"Random seed handling turned off.\n"); */ | |
238 | return EFI_NOT_FOUND; | |
239 | } | |
240 | ||
241 | /* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we | |
242 | * don't credit a random seed that is not authenticated. */ | |
243 | if (secure_boot_enabled()) { | |
244 | /* Print(L"Not loading random seed, because we are in SecureBoot mode.\n"); */ | |
245 | return EFI_NOT_FOUND; | |
246 | } | |
247 | ||
248 | /* Get some system specific seed that the installer might have placed in an EFI variable. We include | |
249 | * it in our hash. This is protection against golden master image sloppiness, and it remains on the | |
250 | * system, even when disk images are duplicated or swapped out. */ | |
251 | err = acquire_system_token(&system_token, &system_token_size); | |
252 | if (mode != RANDOM_SEED_ALWAYS) { | |
253 | /* if (err == EFI_NOT_FOUND) */ | |
254 | /* Print(L"Not loading random seed, because no system token is set.\n"); */ | |
255 | if (EFI_ERROR(err)) | |
256 | return err; /* in all other error cases we already logged */ | |
257 | } | |
258 | ||
259 | err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, L"\\loader\\random-seed", EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL); | |
260 | if (EFI_ERROR(err)) { | |
261 | if (err != EFI_NOT_FOUND) | |
262 | Print(L"Failed to open random seed file: %r\n", err); | |
263 | /* else */ | |
264 | /* Print(L"Not loading random seed, because there is none.\n"); */ | |
265 | ||
266 | return err; | |
267 | } | |
268 | ||
269 | info = LibFileInfo(handle); | |
270 | if (!info) | |
271 | return log_oom(); | |
272 | ||
273 | size = info->FileSize; | |
274 | if (size < RANDOM_MAX_SIZE_MIN) { | |
275 | Print(L"Random seed file is too short?\n"); | |
276 | return EFI_INVALID_PARAMETER; | |
277 | } | |
278 | ||
279 | if (size > RANDOM_MAX_SIZE_MAX) { | |
280 | Print(L"Random seed file is too large?\n"); | |
281 | return EFI_INVALID_PARAMETER; | |
282 | } | |
283 | ||
284 | seed = AllocatePool(size); | |
285 | if (!seed) | |
286 | return log_oom(); | |
287 | ||
288 | rsize = size; | |
289 | err = uefi_call_wrapper(handle->Read, 3, handle, &rsize, seed); | |
290 | if (EFI_ERROR(err)) { | |
291 | Print(L"Failed to read random seed file: %r\n", err); | |
292 | return err; | |
293 | } | |
294 | if (rsize != size) { | |
295 | Print(L"Short read on random seed file\n"); | |
296 | return EFI_PROTOCOL_ERROR; | |
297 | } | |
298 | ||
299 | err = uefi_call_wrapper(handle->SetPosition, 2, handle, 0); | |
300 | if (EFI_ERROR(err)) { | |
301 | Print(L"Failed to seek to beginning of random seed file: %r\n", err); | |
302 | return err; | |
303 | } | |
304 | ||
305 | /* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good | |
306 | * idea to use it because it helps us for cases where users mistakenly include a random seed in | |
307 | * golden master images that are replicated many times. */ | |
308 | (VOID) acquire_rng(size, &rng); /* It's fine if this fails */ | |
309 | ||
310 | /* Calculate new random seed for the disk and what to pass to the kernel */ | |
311 | err = mangle_random_seed(seed, rng, size, system_token, system_token_size, &new_seed, &for_kernel); | |
312 | if (EFI_ERROR(err)) | |
313 | return err; | |
314 | ||
315 | /* Update the random seed on disk before we use it */ | |
316 | wsize = size; | |
317 | err = uefi_call_wrapper(handle->Write, 3, handle, &wsize, new_seed); | |
318 | if (EFI_ERROR(err)) { | |
319 | Print(L"Failed to write random seed file: %r\n", err); | |
320 | return err; | |
321 | } | |
322 | if (wsize != size) { | |
323 | Print(L"Short write on random seed file\n"); | |
324 | return EFI_PROTOCOL_ERROR; | |
325 | } | |
326 | ||
327 | err = uefi_call_wrapper(handle->Flush, 1, handle); | |
328 | if (EFI_ERROR(err)) { | |
329 | Print(L"Failed to flush random seed file: %r\n"); | |
330 | return err; | |
331 | } | |
332 | ||
333 | /* We are good to go */ | |
334 | err = efivar_set_raw(&loader_guid, L"LoaderRandomSeed", for_kernel, size, FALSE); | |
335 | if (EFI_ERROR(err)) { | |
336 | Print(L"Failed to write random seed to EFI variable: %r\n", err); | |
337 | return err; | |
338 | } | |
339 | ||
340 | return EFI_SUCCESS; | |
341 | } |