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