]> code.delx.au - refind/blob - refind/mok.c
Fixed bug that prevented launching Windows in Secure Boot mode;
[refind] / refind / mok.c
1 /* refind/mok.c
2 *
3 * Based mostly on shim.c by Matthew J. Garrett/Red Hat (see below
4 * copyright notice).
5 *
6 * Code to perform Secure Boot verification of boot loader programs
7 * using the Shim program and its Machine Owner Keys (MOKs), to
8 * supplement standard Secure Boot checks performed by the firmware.
9 *
10 */
11
12 /*
13 * shim - trivial UEFI first-stage bootloader
14 *
15 * Copyright 2012 Red Hat, Inc <mjg@redhat.com>
16 *
17 * Redistribution and use in source and binary forms, with or without
18 * modification, are permitted provided that the following conditions
19 * are met:
20 *
21 * Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 *
24 * Redistributions in binary form must reproduce the above copyright
25 * notice, this list of conditions and the following disclaimer in the
26 * documentation and/or other materials provided with the
27 * distribution.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
32 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
33 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
35 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
38 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
39 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
40 * OF THE POSSIBILITY OF SUCH DAMAGE.
41 *
42 * Significant portions of this code are derived from Tianocore
43 * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
44 * Corporation.
45 */
46
47 #include "global.h"
48 #include "mok.h"
49 #include "lib.h"
50 #include "config.h"
51 #include "screen.h"
52 #include "../include/refit_call_wrapper.h"
53 #include "../include/PeImage.h"
54
55 #define SECOND_STAGE L"\\refind.efi"
56 #define MOK_MANAGER L"\\MokManager.efi"
57
58 static EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table);
59
60 // /*
61 // * The vendor certificate used for validating the second stage loader
62 // */
63 // extern UINT8 vendor_cert[];
64 // extern UINT32 vendor_cert_size;
65 // extern UINT32 vendor_dbx_size;
66
67 #define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }}
68
69 //static UINT8 insecure_mode;
70
71 typedef enum {
72 DATA_FOUND,
73 DATA_NOT_FOUND,
74 VAR_NOT_FOUND
75 } CHECK_STATUS;
76
77 typedef struct {
78 UINT32 MokSize;
79 UINT8 *Mok;
80 } MokListNode;
81
82
83 static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, UINT32 *attributes, UINTN *size, VOID **buffer)
84 {
85 EFI_STATUS efi_status;
86 char allocate = !(*size);
87
88 efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, attributes, size, buffer);
89
90 if (efi_status != EFI_BUFFER_TOO_SMALL || !allocate) {
91 return efi_status;
92 }
93
94 *buffer = AllocatePool(*size);
95
96 if (!*buffer) {
97 Print(L"Unable to allocate variable buffer\n");
98 return EFI_OUT_OF_RESOURCES;
99 }
100
101 efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, attributes, size, *buffer);
102
103 return efi_status;
104 } // get_variable()
105
106 /*
107 * Check whether we're in Secure Boot and user mode
108 */
109 BOOLEAN secure_mode (VOID)
110 {
111 EFI_STATUS status;
112 EFI_GUID global_var = EFI_GLOBAL_VARIABLE;
113 UINTN charsize = sizeof(char);
114 UINT8 sb, setupmode;
115 UINT32 attributes;
116
117 status = get_variable(L"SecureBoot", global_var, &attributes, &charsize, (VOID *)&sb);
118
119 /* FIXME - more paranoia here? */
120 if (status != EFI_SUCCESS || sb != 1) {
121 return FALSE;
122 }
123
124 status = get_variable(L"SetupMode", global_var, &attributes, &charsize, (VOID *)&setupmode);
125
126 if (status == EFI_SUCCESS && setupmode == 1) {
127 return FALSE;
128 }
129
130 return TRUE;
131 } // secure_mode()
132
133 // Returns TRUE if the shim program is available to verify binaries,
134 // FALSE if not
135 BOOLEAN ShimLoaded(void) {
136 SHIM_LOCK *shim_lock;
137 EFI_GUID ShimLockGuid = SHIM_LOCK_GUID;
138
139 return (BS->LocateProtocol(&ShimLockGuid, NULL, (VOID**) &shim_lock) == EFI_SUCCESS);
140 } // ShimLoaded()
141
142 /*
143 * Currently, shim/MOK only works on x86-64 (X64) systems, and some of this code
144 * generates warnings on x86 (IA32) builds, so don't bother compiling it at all
145 * on such systems.
146 *
147 */
148
149 #if defined(EFIX64)
150
151 /*
152 * Perform basic bounds checking of the intra-image pointers
153 */
154 static void *ImageAddress (void *image, int size, unsigned int address)
155 {
156 if (address > size)
157 return NULL;
158
159 return image + address;
160 }
161
162 /*
163 * Perform the actual relocation
164 */
165 static EFI_STATUS relocate_coff (GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT *context, void *data)
166 {
167 EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd;
168 UINT64 Adjust;
169 UINT16 *Reloc, *RelocEnd;
170 char *Fixup, *FixupBase, *FixupData = NULL;
171 UINT16 *Fixup16;
172 UINT32 *Fixup32;
173 UINT64 *Fixup64;
174 int size = context->ImageSize;
175 void *ImageEnd = (char *)data + size;
176
177 context->PEHdr->Pe32Plus.OptionalHeader.ImageBase = (UINT64)data;
178
179 // Linux kernels with EFI stub support don't have relocation information, so
180 // we can skip all this stuff....
181 if (((context->RelocDir->VirtualAddress == 0) && (context->RelocDir->Size == 0)) ||
182 ((context->PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) != 0)) {
183 return EFI_SUCCESS;
184 }
185
186 if (context->NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
187 Print(L"Image has no relocation entry\n");
188 return EFI_UNSUPPORTED;
189 }
190
191 RelocBase = ImageAddress(data, size, context->RelocDir->VirtualAddress);
192 RelocBaseEnd = ImageAddress(data, size, context->RelocDir->VirtualAddress + context->RelocDir->Size - 1);
193
194 if (!RelocBase || !RelocBaseEnd) {
195 Print(L"Reloc table overflows binary\n");
196 return EFI_UNSUPPORTED;
197 }
198
199 Adjust = (UINT64)data - context->ImageAddress;
200
201 while (RelocBase < RelocBaseEnd) {
202 Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION));
203 RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock);
204
205 if ((void *)RelocEnd < data || (void *)RelocEnd > ImageEnd) {
206 Print(L"Reloc entry overflows binary\n");
207 return EFI_UNSUPPORTED;
208 }
209
210 FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress);
211 if (!FixupBase) {
212 Print(L"Invalid fixupbase\n");
213 return EFI_UNSUPPORTED;
214 }
215
216 while (Reloc < RelocEnd) {
217 Fixup = FixupBase + (*Reloc & 0xFFF);
218 switch ((*Reloc) >> 12) {
219 case EFI_IMAGE_REL_BASED_ABSOLUTE:
220 break;
221
222 case EFI_IMAGE_REL_BASED_HIGH:
223 Fixup16 = (UINT16 *) Fixup;
224 *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16)));
225 if (FixupData != NULL) {
226 *(UINT16 *) FixupData = *Fixup16;
227 FixupData = FixupData + sizeof (UINT16);
228 }
229 break;
230
231 case EFI_IMAGE_REL_BASED_LOW:
232 Fixup16 = (UINT16 *) Fixup;
233 *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust);
234 if (FixupData != NULL) {
235 *(UINT16 *) FixupData = *Fixup16;
236 FixupData = FixupData + sizeof (UINT16);
237 }
238 break;
239
240 case EFI_IMAGE_REL_BASED_HIGHLOW:
241 Fixup32 = (UINT32 *) Fixup;
242 *Fixup32 = *Fixup32 + (UINT32) Adjust;
243 if (FixupData != NULL) {
244 FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32));
245 *(UINT32 *)FixupData = *Fixup32;
246 FixupData = FixupData + sizeof (UINT32);
247 }
248 break;
249
250 case EFI_IMAGE_REL_BASED_DIR64:
251 Fixup64 = (UINT64 *) Fixup;
252 *Fixup64 = *Fixup64 + (UINT64) Adjust;
253 if (FixupData != NULL) {
254 FixupData = ALIGN_POINTER (FixupData, sizeof(UINT64));
255 *(UINT64 *)(FixupData) = *Fixup64;
256 FixupData = FixupData + sizeof(UINT64);
257 }
258 break;
259
260 default:
261 Print(L"Unknown relocation\n");
262 return EFI_UNSUPPORTED;
263 }
264 Reloc += 1;
265 }
266 RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
267 }
268
269 return EFI_SUCCESS;
270 } /* relocate_coff() */
271
272 /*
273 * Read the binary header and grab appropriate information from it
274 */
275 static EFI_STATUS read_header(void *data, unsigned int datasize,
276 GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT *context)
277 {
278 EFI_IMAGE_DOS_HEADER *DosHdr = data;
279 EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data;
280
281 if (datasize < sizeof(EFI_IMAGE_DOS_HEADER)) {
282 Print(L"Invalid image\n");
283 return EFI_UNSUPPORTED;
284 }
285
286 if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE)
287 PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew);
288
289 if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) {
290 Print(L"Invalid image\n");
291 return EFI_UNSUPPORTED;
292 }
293
294 if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) {
295 Print(L"Unsupported image type\n");
296 return EFI_UNSUPPORTED;
297 }
298
299 if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) {
300 Print(L"Unsupported image - Relocations have been stripped\n");
301 return EFI_UNSUPPORTED;
302 }
303
304 if (PEHdr->Pe32.OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
305 Print(L"Only 64-bit images supported\n");
306 return EFI_UNSUPPORTED;
307 }
308
309 context->PEHdr = PEHdr;
310 context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
311 context->ImageSize = (UINT64)PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
312 context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
313 context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
314 context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
315 context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
316 context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
317 context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER));
318 context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
319
320 if (context->ImageSize < context->SizeOfHeaders) {
321 Print(L"Invalid image\n");
322 return EFI_UNSUPPORTED;
323 }
324
325 if (((UINT8 *)context->SecDir - (UINT8 *)data) > (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) {
326 Print(L"Invalid image\n");
327 return EFI_UNSUPPORTED;
328 }
329
330 if (context->SecDir->VirtualAddress >= datasize) {
331 Print(L"Malformed security header\n");
332 return EFI_INVALID_PARAMETER;
333 }
334 return EFI_SUCCESS;
335 }
336
337 // The following is based on the grub_linuxefi_secure_validate() function in Fedora's
338 // version of GRUB 2.
339 // Returns TRUE if the specified data is validated by Shim's MOK, FALSE otherwise
340 static BOOLEAN ShimValidate (VOID *data, UINT32 size)
341 {
342 SHIM_LOCK *shim_lock;
343 EFI_GUID ShimLockGuid = SHIM_LOCK_GUID;
344
345 if (BS->LocateProtocol(&ShimLockGuid, NULL, (VOID**) &shim_lock) == EFI_SUCCESS) {
346 if (!shim_lock)
347 return FALSE;
348
349 if (shim_lock->shim_verify(data, size) == EFI_SUCCESS)
350 return TRUE;
351 } // if
352
353 return FALSE;
354 } // BOOLEAN ShimValidate()
355
356 /*
357 * Once the image has been loaded it needs to be validated and relocated
358 */
359 static EFI_STATUS handle_image (void *data, unsigned int datasize, EFI_LOADED_IMAGE *li,
360 CHAR16 *Options, REFIT_VOLUME *DeviceVolume, IN EFI_DEVICE_PATH *DevicePath)
361 {
362 EFI_STATUS efi_status;
363 char *buffer;
364 int i, size;
365 EFI_IMAGE_SECTION_HEADER *Section;
366 char *base, *end;
367 GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT context;
368
369 /*
370 * The binary header contains relevant context and section pointers
371 */
372 efi_status = read_header(data, datasize, &context);
373 if (efi_status != EFI_SUCCESS) {
374 Print(L"Failed to read header\n");
375 return efi_status;
376 }
377
378 /*
379 * Validate the image; if this fails, return EFI_ACCESS_DENIED
380 */
381 if (!ShimValidate(data, datasize)) {
382 return EFI_ACCESS_DENIED;
383 }
384
385 buffer = AllocatePool(context.ImageSize);
386
387 if (!buffer) {
388 Print(L"Failed to allocate image buffer\n");
389 return EFI_OUT_OF_RESOURCES;
390 }
391
392 CopyMem(buffer, data, context.SizeOfHeaders);
393
394 /*
395 * Copy the executable's sections to their desired offsets
396 */
397 Section = context.FirstSection;
398 for (i = 0; i < context.NumberOfSections; i++) {
399 size = Section->Misc.VirtualSize;
400
401 if (size > Section->SizeOfRawData)
402 size = Section->SizeOfRawData;
403
404 base = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress);
405 end = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress + size - 1);
406
407 if (!base || !end) {
408 Print(L"Invalid section size\n");
409 return EFI_UNSUPPORTED;
410 }
411
412 if (Section->SizeOfRawData > 0)
413 CopyMem(base, data + Section->PointerToRawData, size);
414
415 if (size < Section->Misc.VirtualSize)
416 ZeroMem (base + size, Section->Misc.VirtualSize - size);
417
418 Section += 1;
419 }
420
421 /*
422 * Run the relocation fixups
423 */
424 efi_status = relocate_coff(&context, buffer);
425
426 if (efi_status != EFI_SUCCESS) {
427 Print(L"Relocation failed\n");
428 FreePool(buffer);
429 return efi_status;
430 }
431
432 entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint);
433 /*
434 * grub needs to know its location and size in memory, its location on
435 * disk, and its load options, so fix up the loaded image protocol values
436 */
437 li->DeviceHandle = DeviceVolume->DeviceHandle;
438 li->FilePath = DevicePath;
439 li->LoadOptionsSize = ((UINT32)StrLen(Options) + 1) * sizeof(CHAR16);
440 li->LoadOptions = (VOID *)Options;
441 li->ImageBase = buffer;
442 li->ImageSize = context.ImageSize;
443
444 if (!entry_point) {
445 Print(L"Invalid entry point\n");
446 FreePool(buffer);
447 return EFI_UNSUPPORTED;
448 }
449
450 return EFI_SUCCESS;
451 }
452
453 #endif /* defined(EFIX64) */
454
455 /*
456 * Load and run an EFI executable.
457 * Note that most of this function compiles only on x86-64 (X64) systems, since
458 * shim/MOK works only on those systems. I'm leaving just enough to get the
459 * function to return EFI_ACCESS_DENIED on x86 (IA32) systems, which should
460 * let the calling function work in case it somehow ends up calling this
461 * function inappropriately.
462 */
463 EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, VOID *data, UINTN datasize,
464 CHAR16 *Options, REFIT_VOLUME *DeviceVolume, IN EFI_DEVICE_PATH *DevicePath)
465 {
466 EFI_STATUS efi_status = EFI_ACCESS_DENIED;
467 #if defined(EFIX64)
468 EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL;
469 EFI_LOADED_IMAGE *li, li_bak;
470 CHAR16 *PathName = NULL;
471
472 if (data == NULL)
473 return EFI_LOAD_ERROR;
474
475 /*
476 * We need to refer to the loaded image protocol on the running
477 * binary in order to find our path
478 */
479 efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image_handle, &loaded_image_protocol, (void **)&li);
480
481 if (efi_status != EFI_SUCCESS) {
482 Print(L"Unable to init protocol\n");
483 return efi_status;
484 }
485
486 /*
487 * We need to modify the loaded image protocol entry before running
488 * the new binary, so back it up
489 */
490 CopyMem(&li_bak, li, sizeof(li_bak));
491
492 /*
493 * Verify and, if appropriate, relocate and execute the executable
494 */
495 efi_status = handle_image(data, datasize, li, Options, DeviceVolume, DevicePath);
496
497 if (efi_status != EFI_SUCCESS) {
498 Print(L"Failed to load image\n");
499 CopyMem(li, &li_bak, sizeof(li_bak));
500 goto done;
501 }
502
503 /*
504 * The binary is trusted and relocated. Run it
505 */
506 efi_status = refit_call2_wrapper(entry_point, image_handle, ST);
507
508 // efi_status = refit_call1_wrapper(BS->UnloadImage, li);
509
510 /*
511 * Restore our original loaded image values
512 */
513 CopyMem(li, &li_bak, sizeof(li_bak));
514 done:
515 if (PathName)
516 FreePool(PathName);
517
518 if (data)
519 FreePool(data);
520
521 #endif
522 return efi_status;
523 } // EFI_STATUS start_image()