]> code.delx.au - refind/blob - refind/mok.c
Refinements, mostly to shim/MOK support.
[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 /*
134 * Currently, shim/MOK only works on x86-64 (X64) systems, and some of this code
135 * generates warnings on x86 (IA32) builds, so don't bother compiling it at all
136 * on such systems.
137 *
138 */
139
140 #if defined(EFIX64)
141
142 /*
143 * Perform basic bounds checking of the intra-image pointers
144 */
145 static void *ImageAddress (void *image, int size, unsigned int address)
146 {
147 if (address > size)
148 return NULL;
149
150 return image + address;
151 }
152
153 /*
154 * Perform the actual relocation
155 */
156 static EFI_STATUS relocate_coff (GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT *context, void *data)
157 {
158 EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd;
159 UINT64 Adjust;
160 UINT16 *Reloc, *RelocEnd;
161 char *Fixup, *FixupBase, *FixupData = NULL;
162 UINT16 *Fixup16;
163 UINT32 *Fixup32;
164 UINT64 *Fixup64;
165 int size = context->ImageSize;
166 void *ImageEnd = (char *)data + size;
167
168 context->PEHdr->Pe32Plus.OptionalHeader.ImageBase = (UINT64)data;
169
170 // Linux kernels with EFI stub support don't have relocation information, so
171 // we can skip all this stuff....
172 if (((context->RelocDir->VirtualAddress == 0) && (context->RelocDir->Size == 0)) ||
173 ((context->PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) != 0)) {
174 return EFI_SUCCESS;
175 }
176
177 if (context->NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
178 Print(L"Image has no relocation entry\n");
179 return EFI_UNSUPPORTED;
180 }
181
182 RelocBase = ImageAddress(data, size, context->RelocDir->VirtualAddress);
183 RelocBaseEnd = ImageAddress(data, size, context->RelocDir->VirtualAddress + context->RelocDir->Size - 1);
184
185 if (!RelocBase || !RelocBaseEnd) {
186 Print(L"Reloc table overflows binary\n");
187 return EFI_UNSUPPORTED;
188 }
189
190 Adjust = (UINT64)data - context->ImageAddress;
191
192 while (RelocBase < RelocBaseEnd) {
193 Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION));
194 RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock);
195
196 if ((void *)RelocEnd < data || (void *)RelocEnd > ImageEnd) {
197 Print(L"Reloc entry overflows binary\n");
198 return EFI_UNSUPPORTED;
199 }
200
201 FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress);
202 if (!FixupBase) {
203 Print(L"Invalid fixupbase\n");
204 return EFI_UNSUPPORTED;
205 }
206
207 while (Reloc < RelocEnd) {
208 Fixup = FixupBase + (*Reloc & 0xFFF);
209 switch ((*Reloc) >> 12) {
210 case EFI_IMAGE_REL_BASED_ABSOLUTE:
211 break;
212
213 case EFI_IMAGE_REL_BASED_HIGH:
214 Fixup16 = (UINT16 *) Fixup;
215 *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16)));
216 if (FixupData != NULL) {
217 *(UINT16 *) FixupData = *Fixup16;
218 FixupData = FixupData + sizeof (UINT16);
219 }
220 break;
221
222 case EFI_IMAGE_REL_BASED_LOW:
223 Fixup16 = (UINT16 *) Fixup;
224 *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust);
225 if (FixupData != NULL) {
226 *(UINT16 *) FixupData = *Fixup16;
227 FixupData = FixupData + sizeof (UINT16);
228 }
229 break;
230
231 case EFI_IMAGE_REL_BASED_HIGHLOW:
232 Fixup32 = (UINT32 *) Fixup;
233 *Fixup32 = *Fixup32 + (UINT32) Adjust;
234 if (FixupData != NULL) {
235 FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32));
236 *(UINT32 *)FixupData = *Fixup32;
237 FixupData = FixupData + sizeof (UINT32);
238 }
239 break;
240
241 case EFI_IMAGE_REL_BASED_DIR64:
242 Fixup64 = (UINT64 *) Fixup;
243 *Fixup64 = *Fixup64 + (UINT64) Adjust;
244 if (FixupData != NULL) {
245 FixupData = ALIGN_POINTER (FixupData, sizeof(UINT64));
246 *(UINT64 *)(FixupData) = *Fixup64;
247 FixupData = FixupData + sizeof(UINT64);
248 }
249 break;
250
251 default:
252 Print(L"Unknown relocation\n");
253 return EFI_UNSUPPORTED;
254 }
255 Reloc += 1;
256 }
257 RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
258 }
259
260 return EFI_SUCCESS;
261 } /* relocate_coff() */
262
263 /*
264 * Read the binary header and grab appropriate information from it
265 */
266 static EFI_STATUS read_header(void *data, unsigned int datasize,
267 GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT *context)
268 {
269 EFI_IMAGE_DOS_HEADER *DosHdr = data;
270 EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data;
271
272 if (datasize < sizeof(EFI_IMAGE_DOS_HEADER)) {
273 Print(L"Invalid image\n");
274 return EFI_UNSUPPORTED;
275 }
276
277 if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE)
278 PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew);
279
280 if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) {
281 Print(L"Invalid image\n");
282 return EFI_UNSUPPORTED;
283 }
284
285 if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) {
286 Print(L"Unsupported image type\n");
287 return EFI_UNSUPPORTED;
288 }
289
290 if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) {
291 Print(L"Unsupported image - Relocations have been stripped\n");
292 return EFI_UNSUPPORTED;
293 }
294
295 if (PEHdr->Pe32.OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
296 Print(L"Only 64-bit images supported\n");
297 return EFI_UNSUPPORTED;
298 }
299
300 context->PEHdr = PEHdr;
301 context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
302 context->ImageSize = (UINT64)PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
303 context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
304 context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
305 context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
306 context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
307 context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
308 context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER));
309 context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
310
311 if (context->ImageSize < context->SizeOfHeaders) {
312 Print(L"Invalid image\n");
313 return EFI_UNSUPPORTED;
314 }
315
316 if (((UINT8 *)context->SecDir - (UINT8 *)data) > (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) {
317 Print(L"Invalid image\n");
318 return EFI_UNSUPPORTED;
319 }
320
321 if (context->SecDir->VirtualAddress >= datasize) {
322 Print(L"Malformed security header\n");
323 return EFI_INVALID_PARAMETER;
324 }
325 return EFI_SUCCESS;
326 }
327
328 // The following is based on the grub_linuxefi_secure_validate() function in Fedora's
329 // version of GRUB 2.
330 // Returns TRUE if the specified data is validated by Shim's MOK, FALSE otherwise
331 static BOOLEAN ShimValidate (VOID *data, UINT32 size)
332 {
333 EFI_GUID ShimLockGuid = SHIM_LOCK_GUID;
334 SHIM_LOCK *shim_lock;
335
336 if (BS->LocateProtocol(&ShimLockGuid, NULL, (VOID**) &shim_lock) == EFI_SUCCESS) {
337 if (!shim_lock)
338 return FALSE;
339
340 if (shim_lock->shim_verify(data, size) == EFI_SUCCESS)
341 return TRUE;
342 } // if
343
344 return FALSE;
345 } // BOOLEAN ShimValidate()
346
347 /*
348 * Once the image has been loaded it needs to be validated and relocated
349 */
350 static EFI_STATUS handle_image (void *data, unsigned int datasize, EFI_LOADED_IMAGE *li,
351 CHAR16 *Options, REFIT_VOLUME *DeviceVolume, IN EFI_DEVICE_PATH *DevicePath)
352 {
353 EFI_STATUS efi_status;
354 char *buffer;
355 int i, size;
356 EFI_IMAGE_SECTION_HEADER *Section;
357 char *base, *end;
358 GNUEFI_PE_COFF_LOADER_IMAGE_CONTEXT context;
359
360 /*
361 * The binary header contains relevant context and section pointers
362 */
363 efi_status = read_header(data, datasize, &context);
364 if (efi_status != EFI_SUCCESS) {
365 Print(L"Failed to read header\n");
366 return efi_status;
367 }
368
369 /*
370 * Validate the image; if this fails, return EFI_ACCESS_DENIED
371 */
372 if (!ShimValidate(data, datasize)) {
373 return EFI_ACCESS_DENIED;
374 }
375
376 buffer = AllocatePool(context.ImageSize);
377
378 if (!buffer) {
379 Print(L"Failed to allocate image buffer\n");
380 return EFI_OUT_OF_RESOURCES;
381 }
382
383 CopyMem(buffer, data, context.SizeOfHeaders);
384
385 /*
386 * Copy the executable's sections to their desired offsets
387 */
388 Section = context.FirstSection;
389 for (i = 0; i < context.NumberOfSections; i++) {
390 size = Section->Misc.VirtualSize;
391
392 if (size > Section->SizeOfRawData)
393 size = Section->SizeOfRawData;
394
395 base = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress);
396 end = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress + size - 1);
397
398 if (!base || !end) {
399 Print(L"Invalid section size\n");
400 return EFI_UNSUPPORTED;
401 }
402
403 if (Section->SizeOfRawData > 0)
404 CopyMem(base, data + Section->PointerToRawData, size);
405
406 if (size < Section->Misc.VirtualSize)
407 ZeroMem (base + size, Section->Misc.VirtualSize - size);
408
409 Section += 1;
410 }
411
412 /*
413 * Run the relocation fixups
414 */
415 efi_status = relocate_coff(&context, buffer);
416
417 if (efi_status != EFI_SUCCESS) {
418 Print(L"Relocation failed\n");
419 FreePool(buffer);
420 return efi_status;
421 }
422
423 entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint);
424 /*
425 * grub needs to know its location and size in memory, its location on
426 * disk, and its load options, so fix up the loaded image protocol values
427 */
428 li->DeviceHandle = DeviceVolume->DeviceHandle;
429 li->FilePath = DevicePath;
430 li->LoadOptionsSize = ((UINT32)StrLen(Options) + 1) * sizeof(CHAR16);
431 li->LoadOptions = (VOID *)Options;
432 li->ImageBase = buffer;
433 li->ImageSize = context.ImageSize;
434
435 if (!entry_point) {
436 Print(L"Invalid entry point\n");
437 FreePool(buffer);
438 return EFI_UNSUPPORTED;
439 }
440
441 return EFI_SUCCESS;
442 }
443
444 #endif /* defined(EFIX64) */
445
446 /*
447 * Load and run an EFI executable.
448 * Note that most of this function compiles only on x86-64 (X64) systems, since
449 * shim/MOK works only on those systems. I'm leaving just enough to get the
450 * function to return EFI_ACCESS_DENIED on x86 (IA32) systems, which should
451 * let the calling function work in case it somehow ends up calling this
452 * function inappropriately.
453 */
454 EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, VOID *data, UINTN datasize,
455 CHAR16 *Options, REFIT_VOLUME *DeviceVolume, IN EFI_DEVICE_PATH *DevicePath)
456 {
457 EFI_STATUS efi_status = EFI_ACCESS_DENIED;
458 #if defined(EFIX64)
459 EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL;
460 EFI_LOADED_IMAGE *li, li_bak;
461 CHAR16 *PathName = NULL;
462
463 if (data == NULL)
464 return EFI_LOAD_ERROR;
465
466 /*
467 * We need to refer to the loaded image protocol on the running
468 * binary in order to find our path
469 */
470 efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image_handle, &loaded_image_protocol, (void **)&li);
471
472 if (efi_status != EFI_SUCCESS) {
473 Print(L"Unable to init protocol\n");
474 return efi_status;
475 }
476
477 /*
478 * We need to modify the loaded image protocol entry before running
479 * the new binary, so back it up
480 */
481 CopyMem(&li_bak, li, sizeof(li_bak));
482
483 /*
484 * Verify and, if appropriate, relocate and execute the executable
485 */
486 efi_status = handle_image(data, datasize, li, Options, DeviceVolume, DevicePath);
487
488 if (efi_status != EFI_SUCCESS) {
489 Print(L"Failed to load image\n");
490 CopyMem(li, &li_bak, sizeof(li_bak));
491 goto done;
492 }
493
494 /*
495 * The binary is trusted and relocated. Run it
496 */
497 efi_status = refit_call2_wrapper(entry_point, image_handle, ST);
498
499 // efi_status = refit_call1_wrapper(BS->UnloadImage, li);
500
501 /*
502 * Restore our original loaded image values
503 */
504 CopyMem(li, &li_bak, sizeof(li_bak));
505 done:
506 if (PathName)
507 FreePool(PathName);
508
509 if (data)
510 FreePool(data);
511
512 #endif
513 return efi_status;
514 } // EFI_STATUS start_image()