]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0fa2cac4 | 2 | |
dc467928 MR |
3 | /* |
4 | * Generic Linux boot protocol using the EFI/PE entry point of the kernel. Passes | |
5 | * initrd with the LINUX_INITRD_MEDIA_GUID DevicePath and cmdline with | |
6 | * EFI LoadedImageProtocol. | |
7 | * | |
8 | * This method works for Linux 5.8 and newer on ARM/Aarch64, x86/x68_64 and RISC-V. | |
9 | */ | |
10 | ||
0fa2cac4 KS |
11 | #include <efi.h> |
12 | #include <efilib.h> | |
13 | ||
a6089431 | 14 | #include "initrd.h" |
dc467928 MR |
15 | #include "linux.h" |
16 | #include "pe.h" | |
6731a102 | 17 | #include "secure-boot.h" |
cf0fbc49 | 18 | #include "util.h" |
0fa2cac4 | 19 | |
a529d818 JJ |
20 | #define STUB_PAYLOAD_GUID \ |
21 | { 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } } | |
dc467928 | 22 | |
5489c13b JJ |
23 | typedef struct { |
24 | const void *addr; | |
25 | size_t len; | |
26 | const EFI_DEVICE_PATH *device_path; | |
27 | } ValidationContext; | |
6731a102 | 28 | |
5489c13b JJ |
29 | static bool validate_payload( |
30 | const void *ctx, const EFI_DEVICE_PATH *device_path, const void *file_buffer, size_t file_size) { | |
6731a102 | 31 | |
5489c13b | 32 | const ValidationContext *payload = ASSERT_PTR(ctx); |
6731a102 | 33 | |
5489c13b JJ |
34 | if (device_path != payload->device_path) |
35 | return false; | |
6731a102 | 36 | |
5489c13b JJ |
37 | /* Security arch (1) protocol does not provide a file buffer. Instead we are supposed to fetch the payload |
38 | * ourselves, which is not needed as we already have everything in memory and the device paths match. */ | |
39 | if (file_buffer && (file_buffer != payload->addr || file_size != payload->len)) | |
40 | return false; | |
6731a102 | 41 | |
5489c13b | 42 | return true; |
6731a102 JJ |
43 | } |
44 | ||
dcebf1d8 | 45 | static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_HANDLE *ret_image) { |
a529d818 JJ |
46 | assert(parent); |
47 | assert(source); | |
dc467928 | 48 | assert(ret_image); |
4287d083 | 49 | |
6731a102 JJ |
50 | /* We could pass a NULL device path, but it's nicer to provide something and it allows us to identify |
51 | * the loaded image from within the security hooks. */ | |
a529d818 JJ |
52 | struct { |
53 | VENDOR_DEVICE_PATH payload; | |
54 | EFI_DEVICE_PATH end; | |
55 | } _packed_ payload_device_path = { | |
56 | .payload = { | |
57 | .Header = { | |
58 | .Type = MEDIA_DEVICE_PATH, | |
59 | .SubType = MEDIA_VENDOR_DP, | |
60 | .Length = { sizeof(payload_device_path.payload), 0 }, | |
61 | }, | |
62 | .Guid = STUB_PAYLOAD_GUID, | |
63 | }, | |
64 | .end = { | |
65 | .Type = END_DEVICE_PATH_TYPE, | |
66 | .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
67 | .Length = { sizeof(payload_device_path.end), 0 }, | |
68 | }, | |
dc467928 | 69 | }; |
0fa2cac4 | 70 | |
6731a102 JJ |
71 | /* We want to support unsigned kernel images as payload, which is safe to do under secure boot |
72 | * because it is embedded in this stub loader (and since it is already running it must be trusted). */ | |
5489c13b JJ |
73 | install_security_override( |
74 | validate_payload, | |
75 | &(ValidationContext) { | |
76 | .addr = source, | |
77 | .len = len, | |
78 | .device_path = &payload_device_path.payload.Header, | |
79 | }); | |
6731a102 JJ |
80 | |
81 | EFI_STATUS ret = BS->LoadImage( | |
a529d818 JJ |
82 | /*BootPolicy=*/false, |
83 | parent, | |
84 | &payload_device_path.payload.Header, | |
85 | (void *) source, | |
86 | len, | |
87 | ret_image); | |
6731a102 | 88 | |
5489c13b | 89 | uninstall_security_override(); |
6731a102 JJ |
90 | |
91 | return ret; | |
dc467928 MR |
92 | } |
93 | ||
a6089431 | 94 | EFI_STATUS linux_exec( |
bfc075fa | 95 | EFI_HANDLE parent, |
927ebebe JJ |
96 | const char16_t *cmdline, |
97 | const void *linux_buffer, | |
98 | size_t linux_length, | |
99 | const void *initrd_buffer, | |
100 | size_t initrd_length) { | |
0d43ce52 | 101 | |
dcde6ae1 | 102 | uint32_t compat_address; |
0fa2cac4 KS |
103 | EFI_STATUS err; |
104 | ||
bfc075fa | 105 | assert(parent); |
dc467928 | 106 | assert(linux_buffer && linux_length > 0); |
a6089431 | 107 | assert(initrd_buffer || initrd_length == 0); |
508df915 | 108 | |
dcde6ae1 | 109 | err = pe_kernel_info(linux_buffer, &compat_address); |
ba2a105c JJ |
110 | #if defined(__i386__) || defined(__x86_64__) |
111 | if (err == EFI_UNSUPPORTED) | |
112 | /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover | |
113 | * protocol. */ | |
114 | return linux_exec_efi_handover( | |
bfc075fa | 115 | parent, |
ba2a105c | 116 | cmdline, |
ba2a105c JJ |
117 | linux_buffer, |
118 | linux_length, | |
119 | initrd_buffer, | |
120 | initrd_length); | |
121 | #endif | |
2a5e4fe4 | 122 | if (err != EFI_SUCCESS) |
a529d818 JJ |
123 | return log_error_status_stall(err, u"Bad kernel image: %r", err); |
124 | ||
125 | _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; | |
126 | err = load_image(parent, linux_buffer, linux_length, &kernel_image); | |
127 | if (err != EFI_SUCCESS) | |
128 | return log_error_status_stall(err, u"Error loading kernel image: %r", err); | |
129 | ||
130 | EFI_LOADED_IMAGE_PROTOCOL *loaded_image; | |
131 | err = BS->HandleProtocol(kernel_image, &LoadedImageProtocol, (void **) &loaded_image); | |
2a5e4fe4 | 132 | if (err != EFI_SUCCESS) |
a529d818 JJ |
133 | return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err); |
134 | ||
135 | if (cmdline) { | |
927ebebe | 136 | loaded_image->LoadOptions = (void *) cmdline; |
a529d818 JJ |
137 | loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions); |
138 | } | |
a6089431 | 139 | |
a529d818 | 140 | _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; |
a6089431 | 141 | err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); |
2a5e4fe4 | 142 | if (err != EFI_SUCCESS) |
a529d818 JJ |
143 | return log_error_status_stall(err, u"Error registering initrd: %r", err); |
144 | ||
145 | err = BS->StartImage(kernel_image, NULL, NULL); | |
146 | ||
147 | /* Try calling the kernel compat entry point if one exists. */ | |
dcde6ae1 | 148 | if (err == EFI_UNSUPPORTED && compat_address > 0) { |
a529d818 | 149 | EFI_IMAGE_ENTRY_POINT compat_entry = |
dcde6ae1 | 150 | (EFI_IMAGE_ENTRY_POINT) ((uint8_t *) loaded_image->ImageBase + compat_address); |
a529d818 JJ |
151 | err = compat_entry(kernel_image, ST); |
152 | } | |
dc467928 | 153 | |
a529d818 | 154 | return log_error_status_stall(err, u"Error starting kernel image: %r", err); |
0fa2cac4 | 155 | } |