]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
0fa2cac4 KS |
2 | |
3 | #include <efi.h> | |
4 | #include <efilib.h> | |
5 | ||
845707aa | 6 | #include "cpio.h" |
8110e144 | 7 | #include "disk.h" |
37fa3690 | 8 | #include "graphics.h" |
0fa2cac4 | 9 | #include "linux.h" |
d4cbada2 MG |
10 | #include "measure.h" |
11 | #include "pe.h" | |
ce0f078f | 12 | #include "secure-boot.h" |
cf0fbc49 TA |
13 | #include "splash.h" |
14 | #include "util.h" | |
0fa2cac4 KS |
15 | |
16 | /* magic string to find in the binary image */ | |
681bd2c5 | 17 | static const char __attribute__((used)) magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"; |
0fa2cac4 | 18 | |
845707aa LP |
19 | static EFI_STATUS combine_initrd( |
20 | EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size, | |
21 | const VOID *credential_initrd, UINTN credential_initrd_size, | |
22 | const VOID *sysext_initrd, UINTN sysext_initrd_size, | |
23 | EFI_PHYSICAL_ADDRESS *ret_initrd_base, UINTN *ret_initrd_size) { | |
24 | ||
25 | EFI_PHYSICAL_ADDRESS base = UINT32_MAX; /* allocate an area below the 32bit boundary for this */ | |
26 | EFI_STATUS err; | |
27 | UINT8 *p; | |
28 | UINTN n; | |
29 | ||
30 | assert(ret_initrd_base); | |
31 | assert(ret_initrd_size); | |
32 | ||
33 | /* Combines three initrds into one, by simple concatenation in memory */ | |
34 | ||
35 | n = ALIGN_TO(initrd_size, 4); /* main initrd might not be padded yet */ | |
36 | if (credential_initrd) { | |
37 | if (n > UINTN_MAX - credential_initrd_size) | |
38 | return EFI_OUT_OF_RESOURCES; | |
39 | ||
40 | n += credential_initrd_size; | |
41 | } | |
42 | if (sysext_initrd) { | |
43 | if (n > UINTN_MAX - sysext_initrd_size) | |
44 | return EFI_OUT_OF_RESOURCES; | |
45 | ||
46 | n += sysext_initrd_size; | |
47 | } | |
48 | ||
49 | err = uefi_call_wrapper( | |
50 | BS->AllocatePages, 4, | |
51 | AllocateMaxAddress, | |
52 | EfiLoaderData, | |
53 | EFI_SIZE_TO_PAGES(n), | |
54 | &base); | |
55 | if (EFI_ERROR(err)) | |
56 | return log_error_status_stall(err, L"Failed to allocate space for combined initrd: %r", err); | |
57 | ||
a0a644be | 58 | p = PHYSICAL_ADDRESS_TO_POINTER(base); |
845707aa LP |
59 | if (initrd_base != 0) { |
60 | UINTN pad; | |
61 | ||
62 | /* Order matters, the real initrd must come first, since it might include microcode updates | |
63 | * which the kernel only looks for in the first cpio archive */ | |
a0a644be | 64 | CopyMem(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); |
845707aa LP |
65 | p += initrd_size; |
66 | ||
67 | pad = ALIGN_TO(initrd_size, 4) - initrd_size; | |
68 | if (pad > 0) { | |
69 | ZeroMem(p, pad); | |
70 | p += pad; | |
71 | } | |
72 | } | |
73 | ||
74 | if (credential_initrd) { | |
75 | CopyMem(p, credential_initrd, credential_initrd_size); | |
76 | p += credential_initrd_size; | |
77 | } | |
78 | ||
79 | if (sysext_initrd) { | |
80 | CopyMem(p, sysext_initrd, sysext_initrd_size); | |
81 | p += sysext_initrd_size; | |
82 | } | |
83 | ||
a0a644be | 84 | assert((UINT8*) PHYSICAL_ADDRESS_TO_POINTER(base) + n == p); |
845707aa LP |
85 | |
86 | *ret_initrd_base = base; | |
87 | *ret_initrd_size = n; | |
88 | ||
89 | return EFI_SUCCESS; | |
90 | } | |
91 | ||
5a186322 LP |
92 | static VOID export_variables(EFI_LOADED_IMAGE *loaded_image) { |
93 | CHAR16 uuid[37]; | |
94 | ||
95 | assert(loaded_image); | |
96 | ||
97 | /* Export the device path this image is started from, if it's not set yet */ | |
98 | if (efivar_get_raw(LOADER_GUID, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) | |
99 | if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) | |
100 | efivar_set(LOADER_GUID, L"LoaderDevicePartUUID", uuid, 0); | |
101 | ||
102 | /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the | |
103 | * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note | |
104 | * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, | |
105 | * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong | |
106 | * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath | |
107 | * is non-NULL explicitly.) */ | |
108 | if (efivar_get_raw(LOADER_GUID, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && | |
109 | loaded_image->FilePath) { | |
110 | _cleanup_freepool_ CHAR16 *s = NULL; | |
111 | ||
112 | s = DevicePathToStr(loaded_image->FilePath); | |
113 | efivar_set(LOADER_GUID, L"LoaderImageIdentifier", s, 0); | |
114 | } | |
115 | ||
116 | /* if LoaderFirmwareInfo is not set, let's set it */ | |
117 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { | |
118 | _cleanup_freepool_ CHAR16 *s = NULL; | |
119 | ||
120 | s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); | |
121 | efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", s, 0); | |
122 | } | |
123 | ||
124 | /* ditto for LoaderFirmwareType */ | |
125 | if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { | |
126 | _cleanup_freepool_ CHAR16 *s = NULL; | |
127 | ||
128 | s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); | |
129 | efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0); | |
130 | } | |
131 | ||
132 | /* add StubInfo */ | |
133 | if (efivar_get_raw(LOADER_GUID, L"StubInfo", NULL, NULL) != EFI_SUCCESS) | |
134 | efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0); | |
135 | } | |
136 | ||
0fa2cac4 | 137 | EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { |
e41d3d89 LP |
138 | |
139 | enum { | |
140 | SECTION_CMDLINE, | |
141 | SECTION_LINUX, | |
142 | SECTION_INITRD, | |
143 | SECTION_SPLASH, | |
144 | _SECTION_MAX, | |
145 | }; | |
146 | ||
147 | const CHAR8* const sections[] = { | |
148 | [SECTION_CMDLINE] = (const CHAR8*) ".cmdline", | |
149 | [SECTION_LINUX] = (const CHAR8*) ".linux", | |
150 | [SECTION_INITRD] = (const CHAR8*) ".initrd", | |
151 | [SECTION_SPLASH] = (const CHAR8*) ".splash", | |
152 | NULL, | |
0fa2cac4 | 153 | }; |
5b5d365d | 154 | |
845707aa LP |
155 | UINTN cmdline_len = 0, initrd_size, credential_initrd_size = 0, sysext_initrd_size = 0; |
156 | _cleanup_freepool_ VOID *credential_initrd = NULL, *sysext_initrd = NULL; | |
5b5d365d | 157 | EFI_PHYSICAL_ADDRESS linux_base, initrd_base; |
845707aa | 158 | EFI_LOADED_IMAGE *loaded_image; |
e41d3d89 LP |
159 | UINTN addrs[_SECTION_MAX] = {}; |
160 | UINTN szs[_SECTION_MAX] = {}; | |
0fa2cac4 | 161 | CHAR8 *cmdline = NULL; |
0fa2cac4 KS |
162 | EFI_STATUS err; |
163 | ||
164 | InitializeLib(image, sys_table); | |
165 | ||
166 | err = uefi_call_wrapper(BS->OpenProtocol, 6, image, &LoadedImageProtocol, (VOID **)&loaded_image, | |
167 | image, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); | |
8aba0eec JJ |
168 | if (EFI_ERROR(err)) |
169 | return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err); | |
0fa2cac4 | 170 | |
e41d3d89 | 171 | err = pe_memory_locate_sections(loaded_image->ImageBase, (const CHAR8**) sections, addrs, szs); |
8aba0eec JJ |
172 | if (EFI_ERROR(err)) |
173 | return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); | |
0fa2cac4 | 174 | |
94b81afb | 175 | /* Show splash screen as early as possible */ |
6e161dc8 | 176 | graphics_splash((const UINT8*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL); |
94b81afb | 177 | |
e41d3d89 | 178 | if (szs[SECTION_CMDLINE] > 0) { |
5b5d365d | 179 | cmdline = (CHAR8*) loaded_image->ImageBase + addrs[SECTION_CMDLINE]; |
e41d3d89 LP |
180 | cmdline_len = szs[SECTION_CMDLINE]; |
181 | } | |
0fa2cac4 | 182 | |
53a20455 | 183 | /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */ |
ce0f078f DDM |
184 | if ((!secure_boot_enabled() || cmdline_len == 0) && loaded_image->LoadOptionsSize > 0 && |
185 | *(CHAR16 *) loaded_image->LoadOptions > 0x1F) { | |
0fa2cac4 KS |
186 | CHAR16 *options; |
187 | CHAR8 *line; | |
0fa2cac4 KS |
188 | |
189 | options = (CHAR16 *)loaded_image->LoadOptions; | |
190 | cmdline_len = (loaded_image->LoadOptionsSize / sizeof(CHAR16)) * sizeof(CHAR8); | |
191 | line = AllocatePool(cmdline_len); | |
258f0970 | 192 | for (UINTN i = 0; i < cmdline_len; i++) |
0fa2cac4 KS |
193 | line[i] = options[i]; |
194 | cmdline = line; | |
92ed3bb4 | 195 | |
e6e24af5 LP |
196 | /* Let's measure the passed kernel command line into the TPM. Note that this possibly |
197 | * duplicates what we already did in the boot menu, if that was already used. However, since | |
198 | * we want the boot menu to support an EFI binary, and want to this stub to be usable from | |
199 | * any boot menu, let's measure things anyway. */ | |
200 | (VOID) tpm_log_load_options(loaded_image->LoadOptions); | |
0fa2cac4 KS |
201 | } |
202 | ||
5a186322 | 203 | export_variables(loaded_image); |
1aa15def | 204 | |
845707aa LP |
205 | (VOID) pack_cpio(loaded_image, |
206 | L".cred", | |
207 | (const CHAR8*) ".extra/credentials", | |
208 | /* dir_mode= */ 0500, | |
209 | /* access_mode= */ 0400, | |
210 | /* tpm_pcr= */ TPM_PCR_INDEX_KERNEL_PARAMETERS, | |
211 | L"Credentials initrd", | |
212 | &credential_initrd, | |
213 | &credential_initrd_size); | |
214 | ||
215 | (VOID) pack_cpio(loaded_image, | |
216 | L".raw", | |
217 | (const CHAR8*) ".extra/sysext", | |
218 | /* dir_mode= */ 0555, | |
219 | /* access_mode= */ 0444, | |
220 | /* tpm_pcr= */ TPM_PCR_INDEX_INITRD, | |
221 | L"System extension initrd", | |
222 | &sysext_initrd, | |
223 | &sysext_initrd_size); | |
224 | ||
a0a644be | 225 | linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; |
5b5d365d LP |
226 | |
227 | initrd_size = szs[SECTION_INITRD]; | |
a0a644be | 228 | initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_INITRD] : 0; |
37fa3690 | 229 | |
845707aa LP |
230 | if (credential_initrd || sysext_initrd) { |
231 | /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ | |
232 | err = combine_initrd( | |
233 | initrd_base, initrd_size, | |
234 | credential_initrd, credential_initrd_size, | |
235 | sysext_initrd, sysext_initrd_size, | |
236 | &initrd_base, &initrd_size); | |
237 | if (EFI_ERROR(err)) | |
238 | return err; | |
239 | ||
240 | /* Given these might be large let's free them explicitly, quickly. */ | |
241 | if (credential_initrd) { | |
242 | FreePool(credential_initrd); | |
243 | credential_initrd = NULL; | |
244 | } | |
245 | ||
246 | if (sysext_initrd) { | |
247 | FreePool(sysext_initrd); | |
248 | sysext_initrd = NULL; | |
249 | } | |
250 | } | |
0fa2cac4 | 251 | |
845707aa | 252 | err = linux_exec(image, cmdline, cmdline_len, linux_base, initrd_base, initrd_size); |
37fa3690 | 253 | graphics_mode(FALSE); |
8aba0eec | 254 | return log_error_status_stall(err, L"Execution of embedded linux image failed: %r", err); |
0fa2cac4 | 255 | } |