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