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