blob: 92229d8dee01e9d8982ac0f9b372d9d99efc8e5c [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <efi.h>
#include <efilib.h>
#include "cpio.h"
#include "devicetree.h"
#include "disk.h"
#include "graphics.h"
#include "linux.h"
#include "measure.h"
#include "part-discovery.h"
#include "pe.h"
#include "random-seed.h"
#include "secure-boot.h"
#include "splash.h"
#include "tpm-pcr.h"
#include "util.h"
/* magic string to find in the binary image */
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
static EFI_STATUS combine_initrd(
EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size,
const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds,
Pages *ret_initr_pages, size_t *ret_initrd_size) {
size_t n;
assert(ret_initr_pages);
assert(ret_initrd_size);
/* Combines four initrds into one, by simple concatenation in memory */
n = ALIGN4(initrd_size); /* main initrd might not be padded yet */
for (size_t i = 0; i < n_extra_initrds; i++) {
if (!extra_initrds[i])
continue;
if (n > SIZE_MAX - extra_initrd_sizes[i])
return EFI_OUT_OF_RESOURCES;
n += extra_initrd_sizes[i];
}
_cleanup_pages_ Pages pages = xmalloc_pages(
AllocateMaxAddress,
EfiLoaderData,
EFI_SIZE_TO_PAGES(n),
UINT32_MAX /* Below 4G boundary. */);
uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr);
if (initrd_base != 0) {
size_t pad;
/* Order matters, the real initrd must come first, since it might include microcode updates
* which the kernel only looks for in the first cpio archive */
p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
pad = ALIGN4(initrd_size) - initrd_size;
if (pad > 0) {
memset(p, 0, pad);
p += pad;
}
}
for (size_t i = 0; i < n_extra_initrds; i++) {
if (!extra_initrds[i])
continue;
p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]);
}
assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p);
*ret_initr_pages = pages;
*ret_initrd_size = n;
pages.n_pages = 0;
return EFI_SUCCESS;
}
static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
static const uint64_t stub_features =
EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */
EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */
EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */
EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */
EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */
0;
char16_t uuid[37];
assert(loaded_image);
/* Export the device path this image is started from, if it's not set yet */
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS)
if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS)
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0);
/* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the
* UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note
* that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us,
* in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong
* here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath
* is non-NULL explicitly.) */
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS &&
loaded_image->FilePath) {
_cleanup_free_ char16_t *s = NULL;
if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS)
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0);
}
/* if LoaderFirmwareInfo is not set, let's set it */
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *s = NULL;
s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0);
}
/* ditto for LoaderFirmwareType */
if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) {
_cleanup_free_ char16_t *s = NULL;
s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0);
}
/* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our
* own data) */
(void) efivar_set(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0);
(void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"StubFeatures", stub_features, 0);
}
static bool use_load_options(
EFI_HANDLE stub_image,
EFI_LOADED_IMAGE_PROTOCOL *loaded_image,
bool have_cmdline,
char16_t **ret) {
assert(stub_image);
assert(loaded_image);
assert(ret);
/* We only allow custom command lines if we aren't in secure boot or if no cmdline was baked into
* the stub image. */
if (secure_boot_enabled() && have_cmdline)
return false;
/* We also do a superficial check whether first character of passed command line
* is printable character (for compat with some Dell systems which fill in garbage?). */
if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((char16_t *) loaded_image->LoadOptions)[0] <= 0x1F)
return false;
/* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that
* LoadOptions starts with the stub binary path which we want to strip off. */
EFI_SHELL_PARAMETERS_PROTOCOL *shell;
if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell)
!= EFI_SUCCESS) {
/* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so
* it could be anything! */
*ret = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
mangle_stub_cmdline(*ret);
return true;
}
if (shell->Argc < 2)
/* No arguments were provided? Then we fall back to built-in cmdline. */
return false;
/* Assemble the command line ourselves without our stub path. */
*ret = xstrdup16(shell->Argv[1]);
for (size_t i = 2; i < shell->Argc; i++) {
_cleanup_free_ char16_t *old = *ret;
*ret = xasprintf("%ls %ls", old, shell->Argv[i]);
}
mangle_stub_cmdline(*ret);
return true;
}
static EFI_STATUS run(EFI_HANDLE image) {
_cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
size_t linux_size, initrd_size, dt_size;
EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
_cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
_cleanup_free_ char16_t *cmdline = NULL;
int sections_measured = -1, parameters_measured = -1;
bool sysext_measured = false, m;
uint64_t loader_features = 0;
EFI_STATUS err;
err = BS->OpenProtocol(
image,
MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL),
(void **) &loaded_image,
image,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (err != EFI_SUCCESS)
return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m");
if (efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features) != EFI_SUCCESS ||
!FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) {
_cleanup_(file_closep) EFI_FILE *esp_dir = NULL;
err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir);
if (err == EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */
(void) process_random_seed(esp_dir);
}
err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs);
if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) {
if (err == EFI_SUCCESS)
err = EFI_NOT_FOUND;
return log_error_status(err, "Unable to locate embedded .linux section: %m");
}
/* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written
* into so far), so that we have one PCR that we can nicely write policies against because it
* contains all static data of this image, and thus can be easily be pre-calculated. */
for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
if (!unified_section_measure(section)) /* shall not measure? */
continue;
if (szs[section] == 0) /* not found */
continue;
m = false;
/* First measure the name of the section */
(void) tpm_log_event_ascii(
TPM_PCR_INDEX_KERNEL_IMAGE,
POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]),
strsize8(unified_sections[section]), /* including NUL byte */
unified_sections[section],
&m);
sections_measured = sections_measured < 0 ? m : (sections_measured && m);
/* Then measure the data of the section */
(void) tpm_log_event_ascii(
TPM_PCR_INDEX_KERNEL_IMAGE,
POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section],
szs[section],
unified_sections[section],
&m);
sections_measured = sections_measured < 0 ? m : (sections_measured && m);
}
/* After we are done, set an EFI variable that tells userspace this was done successfully, and encode
* in it which PCR was used. */
if (sections_measured > 0)
(void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0);
/* Show splash screen as early as possible */
graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) {
/* Let's measure the passed kernel command line into the TPM. Note that this possibly
* duplicates what we already did in the boot menu, if that was already used. However, since
* we want the boot menu to support an EFI binary, and want to this stub to be usable from
* any boot menu, let's measure things anyway. */
m = false;
(void) tpm_log_load_options(cmdline, &m);
parameters_measured = m;
} else if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
cmdline = xstrn8_to_16(
(char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
szs[UNIFIED_SECTION_CMDLINE]);
mangle_stub_cmdline(cmdline);
}
export_variables(loaded_image);
if (pack_cpio(loaded_image,
NULL,
u".cred",
".extra/credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ TPM_PCR_INDEX_KERNEL_PARAMETERS,
u"Credentials initrd",
&credential_initrd,
&credential_initrd_size,
&m) == EFI_SUCCESS)
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
if (pack_cpio(loaded_image,
u"\\loader\\credentials",
u".cred",
".extra/global_credentials",
/* dir_mode= */ 0500,
/* access_mode= */ 0400,
/* tpm_pcr= */ TPM_PCR_INDEX_KERNEL_PARAMETERS,
u"Global credentials initrd",
&global_credential_initrd,
&global_credential_initrd_size,
&m) == EFI_SUCCESS)
parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m);
if (pack_cpio(loaded_image,
NULL,
u".raw",
".extra/sysext",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ TPM_PCR_INDEX_INITRD_SYSEXTS,
u"System extension initrd",
&sysext_initrd,
&sysext_initrd_size,
&m) == EFI_SUCCESS)
sysext_measured = m;
if (parameters_measured > 0)
(void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0);
if (sysext_measured)
(void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0);
/* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it
* to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section
* is not measured, neither as raw section (see above), nor as cpio (here), because it is the
* signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't
* itself be input for PCR measurements. */
if (szs[UNIFIED_SECTION_PCRSIG] > 0)
(void) pack_cpio_literal(
(uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG],
szs[UNIFIED_SECTION_PCRSIG],
".extra",
u"tpm2-pcr-signature.json",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ UINT32_MAX,
/* tpm_description= */ NULL,
&pcrsig_initrd,
&pcrsig_initrd_size,
/* ret_measured= */ NULL);
/* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in
* a cpio and also pass it to the kernel, so that it can be read from
* /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the
* cpio. */
if (szs[UNIFIED_SECTION_PCRPKEY] > 0)
(void) pack_cpio_literal(
(uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY],
szs[UNIFIED_SECTION_PCRPKEY],
".extra",
u"tpm2-pcr-public-key.pem",
/* dir_mode= */ 0555,
/* access_mode= */ 0444,
/* tpm_pcr= */ UINT32_MAX,
/* tpm_description= */ NULL,
&pcrpkey_initrd,
&pcrpkey_initrd_size,
/* ret_measured= */ NULL);
linux_size = szs[UNIFIED_SECTION_LINUX];
linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX];
initrd_size = szs[UNIFIED_SECTION_INITRD];
initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0;
dt_size = szs[UNIFIED_SECTION_DTB];
dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0;
_cleanup_pages_ Pages initrd_pages = {};
if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) {
/* If we have generated initrds dynamically, let's combine them with the built-in initrd. */
err = combine_initrd(
initrd_base, initrd_size,
(const void*const[]) {
credential_initrd,
global_credential_initrd,
sysext_initrd,
pcrsig_initrd,
pcrpkey_initrd,
},
(const size_t[]) {
credential_initrd_size,
global_credential_initrd_size,
sysext_initrd_size,
pcrsig_initrd_size,
pcrpkey_initrd_size,
},
5,
&initrd_pages, &initrd_size);
if (err != EFI_SUCCESS)
return err;
initrd_base = initrd_pages.addr;
/* Given these might be large let's free them explicitly, quickly. */
credential_initrd = mfree(credential_initrd);
global_credential_initrd = mfree(global_credential_initrd);
sysext_initrd = mfree(sysext_initrd);
pcrsig_initrd = mfree(pcrsig_initrd);
pcrpkey_initrd = mfree(pcrpkey_initrd);
}
if (dt_size > 0) {
err = devicetree_install_from_memory(
&dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading embedded devicetree: %m");
}
err = linux_exec(image, cmdline,
PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
graphics_mode(false);
return err;
}
DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false);
/* See comment in boot.c. */
EFI_STATUS _entry(EFI_HANDLE image, EFI_SYSTEM_TABLE *system_table) {
return efi_main(image, system_table);
}