]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/boot/efi/stub.c
boot: Use xstr8_to_16 for path conversion
[thirdparty/systemd.git] / src / boot / efi / stub.c
CommitLineData
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"
33bc9b75 7#include "devicetree.h"
8110e144 8#include "disk.h"
37fa3690 9#include "graphics.h"
0fa2cac4 10#include "linux.h"
d4cbada2
MG
11#include "measure.h"
12#include "pe.h"
ce0f078f 13#include "secure-boot.h"
cf0fbc49 14#include "splash.h"
aa323c09 15#include "tpm-pcr.h"
cf0fbc49 16#include "util.h"
0fa2cac4
KS
17
18/* magic string to find in the binary image */
2ccd5986 19_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
0fa2cac4 20
845707aa
LP
21static EFI_STATUS combine_initrd(
22 EFI_PHYSICAL_ADDRESS initrd_base, UINTN initrd_size,
23002b45 23 const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
09173c91 24 Pages *ret_initr_pages, UINTN *ret_initrd_size) {
845707aa 25
845707aa
LP
26 UINTN n;
27
09173c91 28 assert(ret_initr_pages);
845707aa
LP
29 assert(ret_initrd_size);
30
f3b6f333 31 /* Combines four initrds into one, by simple concatenation in memory */
845707aa 32
b4e7df4a 33 n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
845707aa 34
23002b45
LP
35 for (size_t i = 0; i < n_extra_initrds; i++) {
36 if (!extra_initrds[i])
37 continue;
f3b6f333 38
23002b45 39 if (n > UINTN_MAX - extra_initrd_sizes[i])
845707aa
LP
40 return EFI_OUT_OF_RESOURCES;
41
23002b45 42 n += extra_initrd_sizes[i];
845707aa
LP
43 }
44
09173c91 45 _cleanup_pages_ Pages pages = xmalloc_pages(
845707aa
LP
46 AllocateMaxAddress,
47 EfiLoaderData,
48 EFI_SIZE_TO_PAGES(n),
09173c91
JJ
49 UINT32_MAX /* Below 4G boundary. */);
50 uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
845707aa
LP
51 if (initrd_base != 0) {
52 UINTN pad;
53
54 /* Order matters, the real initrd must come first, since it might include microcode updates
55 * which the kernel only looks for in the first cpio archive */
515581d6 56 p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
845707aa 57
b4e7df4a 58 pad = ALIGN4(initrd_size) - initrd_size;
845707aa 59 if (pad > 0) {
bbc1f2ea 60 memset(p, 0, pad);
845707aa
LP
61 p += pad;
62 }
63 }
64
23002b45
LP
65 for (size_t i = 0; i < n_extra_initrds; i++) {
66 if (!extra_initrds[i])
67 continue;
68
69 p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
70 }
845707aa 71
09173c91 72 assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p);
845707aa 73
09173c91 74 *ret_initr_pages = pages;
845707aa 75 *ret_initrd_size = n;
09173c91 76 pages.n_pages = 0;
845707aa
LP
77
78 return EFI_SUCCESS;
79}
80
b30a43df 81static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
46d33672
LP
82 static const uint64_t stub_features =
83 EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
84 EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
85 EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */
86 EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */
87 0;
88
3639d1b0 89 char16_t uuid[37];
5a186322
LP
90
91 assert(loaded_image);
92
93 /* Export the device path this image is started from, if it's not set yet */
94 if (efivar_get_raw(LOADER_GUID, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS)
95 if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS)
96 efivar_set(LOADER_GUID, L"LoaderDevicePartUUID", uuid, 0);
97
98 /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the
99 * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note
100 * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us,
101 * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong
102 * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath
103 * is non-NULL explicitly.) */
104 if (efivar_get_raw(LOADER_GUID, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS &&
105 loaded_image->FilePath) {
3639d1b0 106 _cleanup_free_ char16_t *s = NULL;
616a80fe 107 if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS)
9f048123 108 efivar_set(LOADER_GUID, L"LoaderImageIdentifier", s, 0);
5a186322
LP
109 }
110
111 /* if LoaderFirmwareInfo is not set, let's set it */
112 if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
3639d1b0 113 _cleanup_free_ char16_t *s = NULL;
ec4106af 114 s = xpool_print(L"%s %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
0a15a824 115 efivar_set(LOADER_GUID, L"LoaderFirmwareInfo", s, 0);
5a186322
LP
116 }
117
118 /* ditto for LoaderFirmwareType */
119 if (efivar_get_raw(LOADER_GUID, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
3639d1b0 120 _cleanup_free_ char16_t *s = NULL;
ec4106af 121 s = xpool_print(L"UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
0a15a824 122 efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0);
5a186322
LP
123 }
124
46d33672 125
24120e40
LP
126 /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our
127 * own data) */
128 (void) efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0);
46d33672
LP
129
130 (void) efivar_set_uint64_le(LOADER_GUID, L"StubFeatures", stub_features, 0);
5a186322
LP
131}
132
0fa2cac4 133EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
23002b45
LP
134 _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
135 UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
33bc9b75 136 UINTN cmdline_len = 0, linux_size, initrd_size, dt_size;
33bc9b75
MR
137 EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
138 _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
b30a43df 139 EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
6017eee9 140 UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
07d0fde4
JJ
141 char *cmdline = NULL;
142 _cleanup_free_ char *cmdline_owned = NULL;
72c97c19 143 int sections_measured = -1, parameters_measured = -1;
de7ad6d4 144 bool sysext_measured = false, m;
0fa2cac4
KS
145 EFI_STATUS err;
146
147 InitializeLib(image, sys_table);
948d085e
JJ
148 debug_hook(L"systemd-stub");
149 /* Uncomment the next line if you need to wait for debugger. */
150 // debug_break();
0fa2cac4 151
12f32748
JJ
152 err = BS->OpenProtocol(
153 image,
154 &LoadedImageProtocol,
155 (void **)&loaded_image,
156 image,
157 NULL,
158 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2a5e4fe4 159 if (err != EFI_SUCCESS)
8aba0eec 160 return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err);
0fa2cac4 161
6017eee9
LP
162 err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs);
163 if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
2a5e4fe4 164 if (err == EFI_SUCCESS)
65ff3d26 165 err = EFI_NOT_FOUND;
8aba0eec 166 return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err);
65ff3d26 167 }
0fa2cac4 168
72c97c19
LP
169 /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
170 * into so far), so that we have one PCR that we can nicely write policies against because it
171 * contains all static data of this image, and thus can be easily be pre-calculated. */
6017eee9 172 for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
df7ee6f8
LP
173
174 if (!unified_section_measure(section)) /* shall not measure? */
175 continue;
72c97c19
LP
176
177 if (szs[section] == 0) /* not found */
178 continue;
179
df7ee6f8
LP
180 m = false;
181
72c97c19
LP
182 /* First measure the name of the section */
183 (void) tpm_log_event_ascii(
184 TPM_PCR_INDEX_KERNEL_IMAGE,
6017eee9
LP
185 POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]),
186 strsize8(unified_sections[section]), /* including NUL byte */
187 unified_sections[section],
72c97c19
LP
188 &m);
189
190 sections_measured = sections_measured < 0 ? m : (sections_measured && m);
191
192 /* Then measure the data of the section */
193 (void) tpm_log_event_ascii(
194 TPM_PCR_INDEX_KERNEL_IMAGE,
195 POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section],
196 szs[section],
6017eee9 197 unified_sections[section],
72c97c19
LP
198 &m);
199
200 sections_measured = sections_measured < 0 ? m : (sections_measured && m);
201 }
202
203 /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode
204 * in it which PCR was used. */
205 if (sections_measured > 0)
206 (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0);
207
94b81afb 208 /* Show splash screen as early as possible */
957dfcc9 209 graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
94b81afb 210
6017eee9
LP
211 if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
212 cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE];
213 cmdline_len = szs[UNIFIED_SECTION_CMDLINE];
e41d3d89 214 }
0fa2cac4 215
03d5449f 216 /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace
5c19169f 217 * the built-in one. We also do a superficial check whether first character of passed command line
03d5449f
LP
218 * is printable character (for compat with some Dell systems which fill in garbage?). */
219 if ((!secure_boot_enabled() || cmdline_len == 0) &&
220 loaded_image->LoadOptionsSize > 0 &&
221 ((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) {
07d0fde4 222 cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char);
03d5449f 223 cmdline = cmdline_owned = xnew(char, cmdline_len);
9f048123 224
03d5449f
LP
225 for (UINTN i = 0; i < cmdline_len; i++) {
226 char16_t c = ((char16_t *) loaded_image->LoadOptions)[i];
227 cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */
228 }
92ed3bb4 229
e6e24af5
LP
230 /* Let's measure the passed kernel command line into the TPM. Note that this possibly
231 * duplicates what we already did in the boot menu, if that was already used. However, since
232 * we want the boot menu to support an EFI binary, and want to this stub to be usable from
233 * any boot menu, let's measure things anyway. */
599fe002
LP
234 m = false;
235 (void) tpm_log_load_options(loaded_image->LoadOptions, &m);
236 parameters_measured = m;
0fa2cac4
KS
237 }
238
5a186322 239 export_variables(loaded_image);
1aa15def 240
599fe002
LP
241 if (pack_cpio(loaded_image,
242 NULL,
243 L".cred",
244 ".extra/credentials",
245 /* dir_mode= */ 0500,
246 /* access_mode= */ 0400,
247 /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
248 /* n_tpm_pcr= */ 2,
249 L"Credentials initrd",
250 &credential_initrd,
251 &credential_initrd_size,
252 &m) == EFI_SUCCESS)
253 parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
254
255 if (pack_cpio(loaded_image,
256 L"\\loader\\credentials",
257 L".cred",
258 ".extra/global_credentials",
259 /* dir_mode= */ 0500,
260 /* access_mode= */ 0400,
261 /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT },
262 /* n_tpm_pcr= */ 2,
263 L"Global credentials initrd",
264 &global_credential_initrd,
265 &global_credential_initrd_size,
266 &m) == EFI_SUCCESS)
267 parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
f3b6f333 268
de7ad6d4
LP
269 if (pack_cpio(loaded_image,
270 NULL,
271 L".raw",
272 ".extra/sysext",
273 /* dir_mode= */ 0555,
274 /* access_mode= */ 0444,
275 /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD_SYSEXTS },
276 /* n_tpm_pcr= */ 1,
277 L"System extension initrd",
278 &sysext_initrd,
279 &sysext_initrd_size,
280 &m) == EFI_SUCCESS)
281 sysext_measured = m;
599fe002
LP
282
283 if (parameters_measured > 0)
284 (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0);
de7ad6d4
LP
285 if (sysext_measured)
286 (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0);
845707aa 287
23002b45
LP
288 /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
289 * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
290 * is not measured, neither as raw section (see above), nor as cpio (here), because it is the
5dcb9c3c 291 * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
23002b45
LP
292 * itself be input for PCR measurements. */
293 if (szs[UNIFIED_SECTION_PCRSIG] > 0)
294 (void) pack_cpio_literal(
295 (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG],
296 szs[UNIFIED_SECTION_PCRSIG],
297 ".extra",
298 L"tpm2-pcr-signature.json",
299 /* dir_mode= */ 0555,
300 /* access_mode= */ 0444,
301 /* tpm_pcr= */ NULL,
302 /* n_tpm_pcr= */ 0,
303 /* tpm_description= */ NULL,
304 &pcrsig_initrd,
305 &pcrsig_initrd_size,
306 /* ret_measured= */ NULL);
307
308 /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
309 * a cpio and also pass it to the kernel, so that it can be read from
310 * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the
311 * cpio. */
312 if (szs[UNIFIED_SECTION_PCRPKEY] > 0)
313 (void) pack_cpio_literal(
314 (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY],
315 szs[UNIFIED_SECTION_PCRPKEY],
316 ".extra",
317 L"tpm2-pcr-public-key.pem",
318 /* dir_mode= */ 0555,
319 /* access_mode= */ 0444,
320 /* tpm_pcr= */ NULL,
321 /* n_tpm_pcr= */ 0,
322 /* tpm_description= */ NULL,
323 &pcrpkey_initrd,
324 &pcrpkey_initrd_size,
325 /* ret_measured= */ NULL);
326
6017eee9
LP
327 linux_size = szs[UNIFIED_SECTION_LINUX];
328 linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
5b5d365d 329
6017eee9
LP
330 initrd_size = szs[UNIFIED_SECTION_INITRD];
331 initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
37fa3690 332
6017eee9
LP
333 dt_size = szs[UNIFIED_SECTION_DTB];
334 dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
33bc9b75 335
09173c91 336 _cleanup_pages_ Pages initrd_pages = {};
64650de7 337 if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) {
845707aa
LP
338 /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
339 err = combine_initrd(
340 initrd_base, initrd_size,
23002b45
LP
341 (const void*const[]) {
342 credential_initrd,
343 global_credential_initrd,
344 sysext_initrd,
345 pcrsig_initrd,
346 pcrpkey_initrd,
347 },
348 (const size_t[]) {
349 credential_initrd_size,
350 global_credential_initrd_size,
351 sysext_initrd_size,
352 pcrsig_initrd_size,
353 pcrpkey_initrd_size,
354 },
355 5,
09173c91 356 &initrd_pages, &initrd_size);
2a5e4fe4 357 if (err != EFI_SUCCESS)
845707aa
LP
358 return err;
359
09173c91
JJ
360 initrd_base = initrd_pages.addr;
361
845707aa 362 /* Given these might be large let's free them explicitly, quickly. */
f3b6f333
AV
363 credential_initrd = mfree(credential_initrd);
364 global_credential_initrd = mfree(global_credential_initrd);
365 sysext_initrd = mfree(sysext_initrd);
23002b45
LP
366 pcrsig_initrd = mfree(pcrsig_initrd);
367 pcrpkey_initrd = mfree(pcrpkey_initrd);
845707aa 368 }
0fa2cac4 369
33bc9b75
MR
370 if (dt_size > 0) {
371 err = devicetree_install_from_memory(
372 &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
2a5e4fe4 373 if (err != EFI_SUCCESS)
33bc9b75
MR
374 log_error_stall(L"Error loading embedded devicetree: %r", err);
375 }
376
a6089431 377 err = linux_exec(image, cmdline, cmdline_len,
dc467928 378 PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
a6089431 379 PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
e5a1b8f9 380 graphics_mode(false);
a529d818 381 return err;
0fa2cac4 382}