]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/boot/efi/stub.c
Merge pull request #26258 from DaanDeMeyer/boot-smbios
[thirdparty/systemd.git] / src / boot / efi / stub.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "cpio.h"
4 #include "device-path-util.h"
5 #include "devicetree.h"
6 #include "graphics.h"
7 #include "linux.h"
8 #include "measure.h"
9 #include "part-discovery.h"
10 #include "pe.h"
11 #include "proto/shell-parameters.h"
12 #include "random-seed.h"
13 #include "secure-boot.h"
14 #include "splash.h"
15 #include "tpm-pcr.h"
16 #include "util.h"
17 #include "vmm.h"
18
19 /* magic string to find in the binary image */
20 _used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
21
22 static EFI_STATUS combine_initrd(
23 EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size,
24 const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
25 Pages *ret_initr_pages, size_t *ret_initrd_size) {
26
27 size_t n;
28
29 assert(ret_initr_pages);
30 assert(ret_initrd_size);
31
32 /* Combines four initrds into one, by simple concatenation in memory */
33
34 n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
35
36 for (size_t i = 0; i < n_extra_initrds; i++) {
37 if (!extra_initrds[i])
38 continue;
39
40 if (n > SIZE_MAX - extra_initrd_sizes[i])
41 return EFI_OUT_OF_RESOURCES;
42
43 n += extra_initrd_sizes[i];
44 }
45
46 _cleanup_pages_ Pages pages = xmalloc_pages(
47 AllocateMaxAddress,
48 EfiLoaderData,
49 EFI_SIZE_TO_PAGES(n),
50 UINT32_MAX /* Below 4G boundary. */);
51 uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
52 if (initrd_base != 0) {
53 size_t pad;
54
55 /* Order matters, the real initrd must come first, since it might include microcode updates
56 * which the kernel only looks for in the first cpio archive */
57 p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
58
59 pad = ALIGN4(initrd_size) - initrd_size;
60 if (pad > 0) {
61 memset(p, 0, pad);
62 p += pad;
63 }
64 }
65
66 for (size_t i = 0; i < n_extra_initrds; i++) {
67 if (!extra_initrds[i])
68 continue;
69
70 p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
71 }
72
73 assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p);
74
75 *ret_initr_pages = pages;
76 *ret_initrd_size = n;
77 pages.n_pages = 0;
78
79 return EFI_SUCCESS;
80 }
81
82 static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
83 static const uint64_t stub_features =
84 EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
85 EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
86 EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */
87 EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */
88 EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */
89 0;
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(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) {
95 _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle);
96 if (uuid)
97 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
98 }
99
100 /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the
101 * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note
102 * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us,
103 * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong
104 * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath
105 * is non-NULL explicitly.) */
106 if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS &&
107 loaded_image->FilePath) {
108 _cleanup_free_ char16_t *s = NULL;
109 if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS)
110 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0);
111 }
112
113 /* if LoaderFirmwareInfo is not set, let's set it */
114 if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
115 _cleanup_free_ char16_t *s = NULL;
116 s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
117 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0);
118 }
119
120 /* ditto for LoaderFirmwareType */
121 if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
122 _cleanup_free_ char16_t *s = NULL;
123 s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
124 efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0);
125 }
126
127
128 /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our
129 * own data) */
130 (void) efivar_set(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0);
131
132 (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"StubFeatures", stub_features, 0);
133 }
134
135 static bool use_load_options(
136 EFI_HANDLE stub_image,
137 EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
138 bool have_cmdline,
139 char16_t **ret) {
140
141 assert(stub_image);
142 assert(loaded_image);
143 assert(ret);
144
145 /* We only allow custom command lines if we aren't in secure boot or if no cmdline was baked into
146 * the stub image. */
147 if (secure_boot_enabled() && have_cmdline)
148 return false;
149
150 /* We also do a superficial check whether first character of passed command line
151 * is printable character (for compat with some Dell systems which fill in garbage?). */
152 if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((char16_t *) loaded_image->LoadOptions)[0] <= 0x1F)
153 return false;
154
155 /* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that
156 * LoadOptions starts with the stub binary path which we want to strip off. */
157 EFI_SHELL_PARAMETERS_PROTOCOL *shell;
158 if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell)
159 != EFI_SUCCESS) {
160 /* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so
161 * it could be anything! */
162 *ret = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
163 mangle_stub_cmdline(*ret);
164 return true;
165 }
166
167 if (shell->Argc < 2)
168 /* No arguments were provided? Then we fall back to built-in cmdline. */
169 return false;
170
171 /* Assemble the command line ourselves without our stub path. */
172 *ret = xstrdup16(shell->Argv[1]);
173 for (size_t i = 2; i < shell->Argc; i++) {
174 _cleanup_free_ char16_t *old = *ret;
175 *ret = xasprintf("%ls %ls", old, shell->Argv[i]);
176 }
177
178 mangle_stub_cmdline(*ret);
179 return true;
180 }
181
182 static EFI_STATUS run(EFI_HANDLE image) {
183 _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
184 size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
185 size_t linux_size, initrd_size, dt_size;
186 EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
187 _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
188 EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
189 size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
190 _cleanup_free_ char16_t *cmdline = NULL;
191 int sections_measured = -1, parameters_measured = -1;
192 bool sysext_measured = false, m;
193 uint64_t loader_features = 0;
194 EFI_STATUS err;
195
196 err = BS->OpenProtocol(
197 image,
198 MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL),
199 (void **) &loaded_image,
200 image,
201 NULL,
202 EFI_OPEN_PROTOCOL_GET_PROTOCOL);
203 if (err != EFI_SUCCESS)
204 return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
205
206 if (efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features) != EFI_SUCCESS ||
207 !FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) {
208 _cleanup_(file_closep) EFI_FILE *esp_dir = NULL;
209
210 err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir);
211 if (err == EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */
212 (void) process_random_seed(esp_dir);
213 }
214
215 err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs);
216 if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
217 if (err == EFI_SUCCESS)
218 err = EFI_NOT_FOUND;
219 return log_error_status(err, "Unable to locate embedded .linux section: %m");
220 }
221
222 /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
223 * into so far), so that we have one PCR that we can nicely write policies against because it
224 * contains all static data of this image, and thus can be easily be pre-calculated. */
225 for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
226
227 if (!unified_section_measure(section)) /* shall not measure? */
228 continue;
229
230 if (szs[section] == 0) /* not found */
231 continue;
232
233 m = false;
234
235 /* First measure the name of the section */
236 (void) tpm_log_event_ascii(
237 TPM_PCR_INDEX_KERNEL_IMAGE,
238 POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]),
239 strsize8(unified_sections[section]), /* including NUL byte */
240 unified_sections[section],
241 &m);
242
243 sections_measured = sections_measured < 0 ? m : (sections_measured && m);
244
245 /* Then measure the data of the section */
246 (void) tpm_log_event_ascii(
247 TPM_PCR_INDEX_KERNEL_IMAGE,
248 POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section],
249 szs[section],
250 unified_sections[section],
251 &m);
252
253 sections_measured = sections_measured < 0 ? m : (sections_measured && m);
254 }
255
256 /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode
257 * in it which PCR was used. */
258 if (sections_measured > 0)
259 (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0);
260
261 /* Show splash screen as early as possible */
262 graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
263
264 if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) {
265 /* Let's measure the passed kernel command line into the TPM. Note that this possibly
266 * duplicates what we already did in the boot menu, if that was already used. However, since
267 * we want the boot menu to support an EFI binary, and want to this stub to be usable from
268 * any boot menu, let's measure things anyway. */
269 m = false;
270 (void) tpm_log_load_options(cmdline, &m);
271 parameters_measured = m;
272 } else if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
273 cmdline = xstrn8_to_16(
274 (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
275 szs[UNIFIED_SECTION_CMDLINE]);
276 mangle_stub_cmdline(cmdline);
277 }
278
279 const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
280 if (extra) {
281 _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);
282 cmdline = xasprintf("%ls %ls", tmp, extra16);
283 }
284
285 export_variables(loaded_image);
286
287 if (pack_cpio(loaded_image,
288 NULL,
289 u".cred",
290 ".extra/credentials",
291 /* dir_mode= */ 0500,
292 /* access_mode= */ 0400,
293 /* tpm_pcr= */ TPM_PCR_INDEX_KERNEL_PARAMETERS,
294 u"Credentials initrd",
295 &credential_initrd,
296 &credential_initrd_size,
297 &m) == EFI_SUCCESS)
298 parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
299
300 if (pack_cpio(loaded_image,
301 u"\\loader\\credentials",
302 u".cred",
303 ".extra/global_credentials",
304 /* dir_mode= */ 0500,
305 /* access_mode= */ 0400,
306 /* tpm_pcr= */ TPM_PCR_INDEX_KERNEL_PARAMETERS,
307 u"Global credentials initrd",
308 &global_credential_initrd,
309 &global_credential_initrd_size,
310 &m) == EFI_SUCCESS)
311 parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
312
313 if (pack_cpio(loaded_image,
314 NULL,
315 u".raw",
316 ".extra/sysext",
317 /* dir_mode= */ 0555,
318 /* access_mode= */ 0444,
319 /* tpm_pcr= */ TPM_PCR_INDEX_INITRD_SYSEXTS,
320 u"System extension initrd",
321 &sysext_initrd,
322 &sysext_initrd_size,
323 &m) == EFI_SUCCESS)
324 sysext_measured = m;
325
326 if (parameters_measured > 0)
327 (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0);
328 if (sysext_measured)
329 (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0);
330
331 /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
332 * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
333 * is not measured, neither as raw section (see above), nor as cpio (here), because it is the
334 * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
335 * itself be input for PCR measurements. */
336 if (szs[UNIFIED_SECTION_PCRSIG] > 0)
337 (void) pack_cpio_literal(
338 (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG],
339 szs[UNIFIED_SECTION_PCRSIG],
340 ".extra",
341 u"tpm2-pcr-signature.json",
342 /* dir_mode= */ 0555,
343 /* access_mode= */ 0444,
344 /* tpm_pcr= */ UINT32_MAX,
345 /* tpm_description= */ NULL,
346 &pcrsig_initrd,
347 &pcrsig_initrd_size,
348 /* ret_measured= */ NULL);
349
350 /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
351 * a cpio and also pass it to the kernel, so that it can be read from
352 * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the
353 * cpio. */
354 if (szs[UNIFIED_SECTION_PCRPKEY] > 0)
355 (void) pack_cpio_literal(
356 (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY],
357 szs[UNIFIED_SECTION_PCRPKEY],
358 ".extra",
359 u"tpm2-pcr-public-key.pem",
360 /* dir_mode= */ 0555,
361 /* access_mode= */ 0444,
362 /* tpm_pcr= */ UINT32_MAX,
363 /* tpm_description= */ NULL,
364 &pcrpkey_initrd,
365 &pcrpkey_initrd_size,
366 /* ret_measured= */ NULL);
367
368 linux_size = szs[UNIFIED_SECTION_LINUX];
369 linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
370
371 initrd_size = szs[UNIFIED_SECTION_INITRD];
372 initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
373
374 dt_size = szs[UNIFIED_SECTION_DTB];
375 dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
376
377 _cleanup_pages_ Pages initrd_pages = {};
378 if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) {
379 /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
380 err = combine_initrd(
381 initrd_base, initrd_size,
382 (const void*const[]) {
383 credential_initrd,
384 global_credential_initrd,
385 sysext_initrd,
386 pcrsig_initrd,
387 pcrpkey_initrd,
388 },
389 (const size_t[]) {
390 credential_initrd_size,
391 global_credential_initrd_size,
392 sysext_initrd_size,
393 pcrsig_initrd_size,
394 pcrpkey_initrd_size,
395 },
396 5,
397 &initrd_pages, &initrd_size);
398 if (err != EFI_SUCCESS)
399 return err;
400
401 initrd_base = initrd_pages.addr;
402
403 /* Given these might be large let's free them explicitly, quickly. */
404 credential_initrd = mfree(credential_initrd);
405 global_credential_initrd = mfree(global_credential_initrd);
406 sysext_initrd = mfree(sysext_initrd);
407 pcrsig_initrd = mfree(pcrsig_initrd);
408 pcrpkey_initrd = mfree(pcrpkey_initrd);
409 }
410
411 if (dt_size > 0) {
412 err = devicetree_install_from_memory(
413 &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
414 if (err != EFI_SUCCESS)
415 log_error_status(err, "Error loading embedded devicetree: %m");
416 }
417
418 err = linux_exec(image, cmdline,
419 PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
420 PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
421 graphics_mode(false);
422 return err;
423 }
424
425 DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false);
426
427 /* See comment in boot.c. */
428 EFI_STATUS _entry(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
429 return efi_main(image, system_table);
430 }