diff --git a/meson.build b/meson.build index f1339e065d1..6b62dfa052d 100644 --- a/meson.build +++ b/meson.build @@ -2344,8 +2344,9 @@ subdir('src/ask-password') subdir('src/backlight') subdir('src/battery-check') subdir('src/binfmt') +subdir('src/bless-boot') subdir('src/boot') -subdir('src/boot/efi') +subdir('src/bootctl') subdir('src/busctl') subdir('src/cgls') subdir('src/cgroups-agent') @@ -2380,6 +2381,7 @@ subdir('src/locale') subdir('src/login') subdir('src/machine') subdir('src/machine-id-setup') +subdir('src/measure') subdir('src/mountfsd') subdir('src/modules-load') subdir('src/mount') @@ -2408,6 +2410,7 @@ subdir('src/rfkill') subdir('src/rpm') subdir('src/run') subdir('src/run-generator') +subdir('src/sbsign') subdir('src/shutdown') subdir('src/sleep') subdir('src/socket-activate') diff --git a/src/boot/bless-boot-generator.c b/src/bless-boot/bless-boot-generator.c similarity index 100% rename from src/boot/bless-boot-generator.c rename to src/bless-boot/bless-boot-generator.c diff --git a/src/boot/bless-boot.c b/src/bless-boot/bless-boot.c similarity index 100% rename from src/boot/bless-boot.c rename to src/bless-boot/bless-boot.c diff --git a/src/boot/boot-check-no-failures.c b/src/bless-boot/boot-check-no-failures.c similarity index 100% rename from src/boot/boot-check-no-failures.c rename to src/bless-boot/boot-check-no-failures.c diff --git a/src/bless-boot/meson.build b/src/bless-boot/meson.build new file mode 100644 index 00000000000..003f110664d --- /dev/null +++ b/src/bless-boot/meson.build @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if get_option('link-boot-shared') + boot_link_with = [libshared] +else + boot_link_with = [ + libshared_static, + libsystemd_static, + ] +endif + +executables += [ + libexec_template + { + 'name' : 'systemd-bless-boot', + 'public' : true, + 'conditions' : [ + 'HAVE_BLKID', + 'ENABLE_BOOTLOADER', + ], + 'sources' : files('bless-boot.c'), + 'link_with' : boot_link_with, + 'dependencies' : libblkid, + }, + generator_template + { + 'name' : 'systemd-bless-boot-generator', + 'conditions' : [ + 'HAVE_BLKID', + 'ENABLE_BOOTLOADER', + ], + 'sources' : files('bless-boot-generator.c'), + 'link_with' : boot_link_with, + }, + libexec_template + { + 'name' : 'systemd-boot-check-no-failures', + 'sources' : files('boot-check-no-failures.c'), + }, +] diff --git a/src/boot/efi/UEFI_SECURITY.md b/src/boot/UEFI_SECURITY.md similarity index 100% rename from src/boot/efi/UEFI_SECURITY.md rename to src/boot/UEFI_SECURITY.md diff --git a/src/boot/efi/addon.c b/src/boot/addon.c similarity index 100% rename from src/boot/efi/addon.c rename to src/boot/addon.c diff --git a/src/boot/efi/bcd.c b/src/boot/bcd.c similarity index 100% rename from src/boot/efi/bcd.c rename to src/boot/bcd.c diff --git a/src/boot/efi/bcd.h b/src/boot/bcd.h similarity index 100% rename from src/boot/efi/bcd.h rename to src/boot/bcd.h diff --git a/src/boot/efi/boot.c b/src/boot/boot.c similarity index 100% rename from src/boot/efi/boot.c rename to src/boot/boot.c diff --git a/src/boot/efi/chid.c b/src/boot/chid.c similarity index 100% rename from src/boot/efi/chid.c rename to src/boot/chid.c diff --git a/src/boot/efi/chid.h b/src/boot/chid.h similarity index 100% rename from src/boot/efi/chid.h rename to src/boot/chid.h diff --git a/src/boot/efi/console.c b/src/boot/console.c similarity index 100% rename from src/boot/efi/console.c rename to src/boot/console.c diff --git a/src/boot/efi/console.h b/src/boot/console.h similarity index 100% rename from src/boot/efi/console.h rename to src/boot/console.h diff --git a/src/boot/efi/cpio.c b/src/boot/cpio.c similarity index 100% rename from src/boot/efi/cpio.c rename to src/boot/cpio.c diff --git a/src/boot/efi/cpio.h b/src/boot/cpio.h similarity index 100% rename from src/boot/efi/cpio.h rename to src/boot/cpio.h diff --git a/src/boot/efi/device-path-util.c b/src/boot/device-path-util.c similarity index 100% rename from src/boot/efi/device-path-util.c rename to src/boot/device-path-util.c diff --git a/src/boot/efi/device-path-util.h b/src/boot/device-path-util.h similarity index 100% rename from src/boot/efi/device-path-util.h rename to src/boot/device-path-util.h diff --git a/src/boot/efi/devicetree.c b/src/boot/devicetree.c similarity index 100% rename from src/boot/efi/devicetree.c rename to src/boot/devicetree.c diff --git a/src/boot/efi/devicetree.h b/src/boot/devicetree.h similarity index 100% rename from src/boot/efi/devicetree.h rename to src/boot/devicetree.h diff --git a/src/boot/efi/drivers.c b/src/boot/drivers.c similarity index 100% rename from src/boot/efi/drivers.c rename to src/boot/drivers.c diff --git a/src/boot/efi/drivers.h b/src/boot/drivers.h similarity index 100% rename from src/boot/efi/drivers.h rename to src/boot/drivers.h diff --git a/src/boot/efi/efi-string.c b/src/boot/efi-string.c similarity index 100% rename from src/boot/efi/efi-string.c rename to src/boot/efi-string.c diff --git a/src/boot/efi/efi-string.h b/src/boot/efi-string.h similarity index 100% rename from src/boot/efi/efi-string.h rename to src/boot/efi-string.h diff --git a/src/boot/efi/efi.h b/src/boot/efi.h similarity index 100% rename from src/boot/efi/efi.h rename to src/boot/efi.h diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c deleted file mode 100644 index 15f1ba9afff..00000000000 --- a/src/boot/efi/measure.c +++ /dev/null @@ -1,324 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#if ENABLE_TPM - -#include "macro-fundamental.h" -#include "measure.h" -#include "memory-util-fundamental.h" -#include "proto/cc-measurement.h" -#include "proto/tcg.h" -#include "tpm2-pcr.h" -#include "util.h" - -static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( - EFI_TCG2_PROTOCOL *tcg, - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - uint64_t buffer_size, - uint32_t event_id, - const char16_t *description) { - - _cleanup_free_ struct event { - EFI_TCG2_EVENT tcg_event; - EFI_TCG2_TAGGED_EVENT tcg_tagged_event; - } _packed_ *event = NULL; - size_t desc_len, event_size; - - assert(tcg); - assert(description); - - /* New style stuff we log as EV_EVENT_TAG with a recognizable event tag. */ - - desc_len = strsize16(description); - event_size = offsetof(EFI_TCG2_EVENT, Event) + offsetof(EFI_TCG2_TAGGED_EVENT, Event) + desc_len; - - event = xmalloc(event_size); - *event = (struct event) { - .tcg_event = (EFI_TCG2_EVENT) { - .Size = event_size, - .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER), - .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION, - .Header.PCRIndex = pcrindex, - .Header.EventType = EV_EVENT_TAG, - }, - .tcg_tagged_event = { - .EventId = event_id, - .EventSize = desc_len, - }, - }; - memcpy(event->tcg_tagged_event.Event, description, desc_len); - - return tcg->HashLogExtendEvent( - tcg, - 0, - buffer, buffer_size, - &event->tcg_event); -} - -static EFI_STATUS tpm2_measure_to_pcr_and_ipl_event_log( - EFI_TCG2_PROTOCOL *tcg, - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - uint64_t buffer_size, - const char16_t *description) { - - _cleanup_free_ EFI_TCG2_EVENT *tcg_event = NULL; - size_t desc_len; - - assert(tcg); - assert(description); - - /* We record older stuff as EV_IPL. Which sucks, because it makes it hard to recognize from the event - * log which of the events are ours. Measurement logs are kinda API hence this is hard to change for - * existing, established events. But for future additions, let's use EV_EVENT_TAG instead, with a tag - * of our choosing that makes clear what precisely we are measuring here. See above. */ - - desc_len = strsize16(description); - tcg_event = xmalloc(offsetof(EFI_TCG2_EVENT, Event) + desc_len); - *tcg_event = (EFI_TCG2_EVENT) { - .Size = offsetof(EFI_TCG2_EVENT, Event) + desc_len, - .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER), - .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION, - .Header.PCRIndex = pcrindex, - .Header.EventType = EV_IPL, - }; - - memcpy(tcg_event->Event, description, desc_len); - - return tcg->HashLogExtendEvent( - tcg, - 0, - buffer, buffer_size, - tcg_event); -} - -static EFI_STATUS cc_measure_to_mr_and_ipl_event_log( - EFI_CC_MEASUREMENT_PROTOCOL *cc, - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - uint64_t buffer_size, - const char16_t *description) { - - _cleanup_free_ EFI_CC_EVENT *event = NULL; - uint32_t mr; - EFI_STATUS err; - size_t desc_len; - - assert(cc); - assert(description); - - /* MapPcrToMrIndex service provides callers information on - * how the TPM PCR registers are mapped to the CC measurement - * registers (MR) in the vendor implementation. */ - err = cc->MapPcrToMrIndex(cc, pcrindex, &mr); - if (err != EFI_SUCCESS) - return EFI_NOT_FOUND; - - desc_len = strsize16(description); - event = xmalloc(offsetof(EFI_CC_EVENT, Event) + desc_len); - *event = (EFI_CC_EVENT) { - .Size = offsetof(EFI_CC_EVENT, Event) + desc_len, - .Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER), - .Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION, - .Header.MrIndex = mr, - .Header.EventType = EV_IPL, - }; - - memcpy(event->Event, description, desc_len); - - return cc->HashLogExtendEvent( - cc, - 0, - buffer, - buffer_size, - event); -} - -static EFI_CC_MEASUREMENT_PROTOCOL *cc_interface_check(void) { - EFI_CC_BOOT_SERVICE_CAPABILITY capability = { - .Size = sizeof(capability), - }; - EFI_STATUS err; - EFI_CC_MEASUREMENT_PROTOCOL *cc; - - err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_CC_MEASUREMENT_PROTOCOL), NULL, (void **) &cc); - if (err != EFI_SUCCESS) - return NULL; - - err = cc->GetCapability(cc, &capability); - if (err != EFI_SUCCESS) - return NULL; - - if (!(capability.SupportedEventLogs & EFI_CC_EVENT_LOG_FORMAT_TCG_2)) - return NULL; - - return cc; -} - -static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { - EFI_TCG2_BOOT_SERVICE_CAPABILITY capability = { - .Size = sizeof(capability), - }; - EFI_STATUS err; - EFI_TCG2_PROTOCOL *tcg; - - err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG2_PROTOCOL), NULL, (void **) &tcg); - if (err != EFI_SUCCESS) - return NULL; - - err = tcg->GetCapability(tcg, &capability); - if (err != EFI_SUCCESS) - return NULL; - - if (capability.StructureVersion.Major == 1 && - capability.StructureVersion.Minor == 0) { - EFI_TCG_BOOT_SERVICE_CAPABILITY *caps_1_0 = - (EFI_TCG_BOOT_SERVICE_CAPABILITY*) &capability; - if (caps_1_0->TPMPresentFlag) - return tcg; - } - - if (!capability.TPMPresentFlag) - return NULL; - - return tcg; -} - -bool tpm_present(void) { - return tcg2_interface_check(); -} - -static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { - EFI_TCG2_PROTOCOL *tpm2; - EFI_STATUS err = EFI_SUCCESS; - - assert(ret_measured); - - tpm2 = tcg2_interface_check(); - if (!tpm2) { - *ret_measured = false; - return EFI_SUCCESS; - } - - err = tpm2_measure_to_pcr_and_ipl_event_log(tpm2, pcrindex, buffer, buffer_size, description); - if (err != EFI_SUCCESS) - return err; - - *ret_measured = true; - return EFI_SUCCESS; -} - -static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { - EFI_CC_MEASUREMENT_PROTOCOL *cc; - EFI_STATUS err = EFI_SUCCESS; - - assert(ret_measured); - - cc = cc_interface_check(); - if (!cc) { - *ret_measured = false; - return EFI_SUCCESS; - } - - err = cc_measure_to_mr_and_ipl_event_log(cc, pcrindex, buffer, buffer_size, description); - if (err != EFI_SUCCESS) - return err; - - *ret_measured = true; - return EFI_SUCCESS; -} - -EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { - EFI_STATUS err; - bool tpm_ret_measured, cc_ret_measured; - - assert(description || pcrindex == UINT32_MAX); - - /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured - * something, or false if measurement was turned off. */ - - if (pcrindex == UINT32_MAX) { /* PCR disabled? */ - if (ret_measured) - *ret_measured = false; - - return EFI_SUCCESS; - } - - /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299 */ - err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); - if (err != EFI_SUCCESS) - return err; - - err = tcg2_log_ipl_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); - if (err != EFI_SUCCESS) - return err; - - if (ret_measured) - *ret_measured = tpm_ret_measured || cc_ret_measured; - - return EFI_SUCCESS; -} - -EFI_STATUS tpm_log_tagged_event( - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - size_t buffer_size, - uint32_t event_id, - const char16_t *description, - bool *ret_measured) { - - EFI_TCG2_PROTOCOL *tpm2; - EFI_STATUS err; - - assert(description || pcrindex == UINT32_MAX); - assert(event_id > 0); - - /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured - * something, or false if measurement was turned off. */ - - tpm2 = tcg2_interface_check(); - if (!tpm2 || pcrindex == UINT32_MAX) { /* PCR disabled? */ - if (ret_measured) - *ret_measured = false; - - return EFI_SUCCESS; - } - - err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); - if (!err) - return err; - - *ret_measured = true; - return EFI_SUCCESS; -} - -EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { - _cleanup_free_ char16_t *c = NULL; - - if (description) - c = xstr8_to_16(description); - - return tpm_log_ipl_event(pcrindex, buffer, buffer_size, c, ret_measured); -} - -EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) { - EFI_STATUS err; - - /* Measures a load options string into the TPM2, i.e. the kernel command line */ - - err = tpm_log_ipl_event( - TPM2_PCR_KERNEL_CONFIG, - POINTER_TO_PHYSICAL_ADDRESS(load_options), - strsize16(load_options), - load_options, - ret_measured); - if (err != EFI_SUCCESS) - return log_error_status( - err, - "Unable to add load options (i.e. kernel command) line measurement to PCR %i: %m", - TPM2_PCR_KERNEL_CONFIG); - - return EFI_SUCCESS; -} - -#endif diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build deleted file mode 100644 index 29c5455dbd1..00000000000 --- a/src/boot/efi/meson.build +++ /dev/null @@ -1,430 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -efi_config_h_dir = meson.current_build_dir() -efi_addon = '' - -libefitest = static_library( - 'efitest', - files( - 'bcd.c', - 'efi-string.c', - ), - build_by_default : false, - include_directories : [ - basic_includes, - include_directories('.'), - ], - dependencies : userspace) - -efitest_base = { - 'link_with' : [ - libefitest, - libshared, - ], -} -efi_test_template = test_template + efitest_base -efi_fuzz_template = fuzz_template + efitest_base - -executables += [ - efi_test_template + { - 'sources' : files('test-bcd.c'), - 'dependencies' : libzstd_cflags, - 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_ZSTD'], - }, - efi_test_template + { - 'sources' : files('test-efi-string.c'), - 'conditions' : ['ENABLE_BOOTLOADER'], - }, - efi_fuzz_template + { - 'sources' : files('fuzz-bcd.c'), - }, - efi_fuzz_template + { - 'sources' : files('fuzz-efi-string.c'), - }, - efi_fuzz_template + { - 'sources' : files('fuzz-efi-osrel.c'), - }, - efi_fuzz_template + { - 'sources' : files('fuzz-efi-printf.c'), - }, -] - -if conf.get('ENABLE_BOOTLOADER') != 1 - subdir_done() -endif - -efi_conf = configuration_data() -efi_conf.set10('ENABLE_TPM', get_option('tpm')) - -foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit'] - c = get_option('efi-' + ctype).split(',') - efi_conf.set(ctype.underscorify().to_upper(), 'EFI_TEXT_ATTR(@0@, @1@)'.format( - 'EFI_' + c[0].strip().underscorify().to_upper(), - 'EFI_' + c[1].strip().underscorify().to_upper())) -endforeach - -efi_conf.set_quoted('PROJECT_VERSION', project_major_version) -efi_conf.set_quoted('VERSION_TAG', version_tag) -efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL')) - -if meson.is_cross_build() and get_option('sbat-distro') == 'auto' - warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.') -elif get_option('sbat-distro') != '' - efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) - if get_option('sbat-distro-generation') < 1 - error('SBAT Distro Generation must be a positive integer') - endif - efi_conf.set('SBAT_DISTRO_GENERATION', get_option('sbat-distro-generation')) - foreach sbatvar : [['sbat-distro', 'ID'], - ['sbat-distro-summary', 'NAME'], - ['sbat-distro-url', 'BUG_REPORT_URL']] - value = get_option(sbatvar[0]) - if (value == '' or value == 'auto') and not meson.is_cross_build() - cmd = 'if [ -e /etc/os-release ]; then . /etc/os-release; else . /usr/lib/os-release; fi; echo $@0@'.format(sbatvar[1]) - value = run_command(sh, '-c', cmd, check: true).stdout().strip() - endif - if value == '' - error('Required @0@ option not set and autodetection failed'.format(sbatvar[0])) - endif - efi_conf.set_quoted(sbatvar[0].underscorify().to_upper(), value) - endforeach - - pkgname = get_option('sbat-distro-pkgname') - if pkgname == '' - pkgname = meson.project_name() - endif - efi_conf.set_quoted('SBAT_DISTRO_PKGNAME', pkgname) - - pkgver = get_option('sbat-distro-version') - if pkgver == '' - # This is determined during build, not configuration, so we can't display it yet. - efi_conf.set('SBAT_DISTRO_VERSION', 'GIT_VERSION') - else - efi_conf.set_quoted('SBAT_DISTRO_VERSION', pkgver) - endif -endif - -summary({'UEFI architectures' : efi_arch + (efi_arch_alt == '' ? '' : ', ' + efi_arch_alt)}, - section : 'UEFI') - -if efi_conf.get('SBAT_DISTRO', '') != '' - summary({ - 'SBAT distro': efi_conf.get('SBAT_DISTRO'), - 'SBAT distro generation': efi_conf.get('SBAT_DISTRO_GENERATION'), - 'SBAT distro version': efi_conf.get('SBAT_DISTRO_VERSION'), - 'SBAT distro summary': efi_conf.get('SBAT_DISTRO_SUMMARY'), - 'SBAT distro URL': efi_conf.get('SBAT_DISTRO_URL')}, - section : 'UEFI') -endif - -configure_file( - output : 'efi_config.h', - configuration : efi_conf) - -############################################################ - -efi_includes = [ - build_dir_include, - fundamental_include, - include_directories('.'), -] - -efi_c_args = [ - '-DSD_BOOT=1', - '-ffreestanding', - '-fno-strict-aliasing', - '-fshort-wchar', - '-include', 'efi_config.h', -] - -efi_c_args += cc.get_supported_arguments( - '-fwide-exec-charset=UCS2', - # gcc docs says this is required for ms_abi to work correctly. - '-maccumulate-outgoing-args', - '-mstack-protector-guard=global', -) - -# Debug information has little value in release builds as no normal human being knows -# how to attach a debugger to EFI binaries running on real hardware. Anyone who does -# certainly has the means to do their own dev build. -if get_option('mode') == 'developer' and get_option('debug') - efi_c_args += '-DEFI_DEBUG' -endif - -efi_c_ld_args = [ - '-lgcc', - '-nostdlib', - '-static-pie', - '-Wl,--entry=efi_main', - '-Wl,--fatal-warnings', - - # These flags should be passed by -static-pie, but for whatever reason the flag translation - # is not enabled on all architectures. Not passing `-static` would just allow the linker to - # use dynamic libraries, (which we can't/don't use anyway). But if `-pie` is missing and the - # gcc build does not default to `-pie` we get a regular (no-pie) binary that will be - # rightfully rejected by elf2efi. Note that meson also passes `-pie` to the linker driver, - # but it is overridden by our `-static-pie`. We also need to pass these directly to the - # linker as `-static`+`-pie` seem to get translated differently. - '-Wl,-static,-pie,--no-dynamic-linker,-z,text', - - # EFI has 4KiB pages. - '-z', 'common-page-size=4096', - '-z', 'max-page-size=4096', - - '-z', 'noexecstack', - '-z', 'relro', - '-z', 'separate-code', -] - -efi_c_ld_args += cc.get_supported_link_arguments( - # binutils >= 2.38 - '-Wl,-z,nopack-relative-relocs', -) - -# efi_c_args is explicitly passed to targets so that they can override distro-provided flags -# that should not be used for EFI binaries. -efi_disabled_c_args = cc.get_supported_arguments( - '-fcf-protection=none', - '-fno-asynchronous-unwind-tables', - '-fno-exceptions', - '-fno-unwind-tables', -) -efi_override_options = [ - 'b_coverage=false', - 'b_pgo=off', -] - -if get_option('b_sanitize') == 'undefined' - efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize-link-runtime') -else - efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize=all') - efi_override_options += 'b_sanitize=none' -endif - -efi_c_args += efi_disabled_c_args -efi_c_ld_args += efi_disabled_c_args - -if cc.get_id() == 'clang' - # clang is too picky sometimes. - efi_c_args += '-Wno-unused-command-line-argument' - efi_c_ld_args += '-Wno-unused-command-line-argument' -endif - -efi_arch_c_args = { - 'aarch64' : ['-mgeneral-regs-only'], - 'arm' : ['-mgeneral-regs-only'], - # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead: - 'loongarch64' : ['-mno-lsx', '-mno-lasx'], - # Pass -m64/32 explicitly to make building on x32 work. - 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], - 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], -} -efi_arch_c_ld_args = { - # libgcc is not compiled with -fshort-wchar, but it does not use it anyways, - # so it's fine to link against it. - 'arm' : cc.get_supported_link_arguments('-Wl,--no-wchar-size-warning'), - 'x86_64' : ['-m64'], - 'x86' : ['-m32'], -} - -linker_sanity_code = 'void a(void) {}; void _start(void) { a(); }' -linker_sanity_args = ['-nostdlib', '-Wl,--fatal-warnings'] -if not cc.links(linker_sanity_code, - name : 'linker supports -static-pie', - args : [linker_sanity_args, '-static-pie']) - error('Linker does not support -static-pie.') -endif - -# https://github.com/llvm/llvm-project/issues/67152 -if not cc.links(linker_sanity_code, - name : 'linker supports LTO with -nostdlib', - args : [linker_sanity_args, '-flto']) - efi_c_args += '-fno-lto' - efi_c_ld_args += '-fno-lto' -endif - -# https://github.com/llvm/llvm-project/issues/61101 -if efi_cpu_family_alt == 'x86' and not cc.links(linker_sanity_code, - name : 'linker supports LTO with -nostdlib (x86)', - args : [linker_sanity_args, '-flto', '-m32']) - efi_arch_c_args += { 'x86' : efi_arch_c_args['x86'] + '-fno-lto' } - efi_arch_c_ld_args += { 'x86' : efi_arch_c_ld_args['x86'] + '-fno-lto' } -endif - -############################################################ - -libefi_sources = files( - 'chid.c', - 'console.c', - 'device-path-util.c', - 'devicetree.c', - 'drivers.c', - 'efi-string.c', - 'efivars.c', - 'export-vars.c', - 'graphics.c', - 'initrd.c', - 'log.c', - 'measure.c', - 'part-discovery.c', - 'pe.c', - 'random-seed.c', - 'secure-boot.c', - 'shim.c', - 'smbios.c', - 'ticks.c', - 'util.c', - 'vmm.c', -) - -systemd_boot_sources = files( - 'boot.c', -) - -stub_sources = files( - 'cpio.c', - 'linux.c', - 'splash.c', - 'stub.c', -) - -addon_sources = files( - 'addon.c', -) - -if get_option('b_sanitize') == 'undefined' - libefi_sources += files('ubsan.c') -endif - -if host_machine.cpu_family() in ['x86', 'x86_64'] - stub_sources += files('linux_x86.c') -endif - -# BCD parser only makes sense on arches that Windows supports. -if host_machine.cpu_family() in ['aarch64', 'arm', 'x86_64', 'x86'] - systemd_boot_sources += files('bcd.c') -endif - -boot_targets = [] -efi_elf_binaries = [] -efi_archspecs = [ - { - 'arch' : efi_arch, - 'c_args' : [ - efi_c_args, - '-DEFI_MACHINE_TYPE_NAME="' + efi_arch + '"', - efi_arch_c_args.get(host_machine.cpu_family(), []), - ], - 'link_args' : [ - efi_c_ld_args, - efi_arch_c_ld_args.get(host_machine.cpu_family(), []), - ], - }, -] -if efi_arch_alt != '' - efi_archspecs += { - 'arch' : efi_arch_alt, - 'c_args' : [ - efi_c_args, - '-DEFI_MACHINE_TYPE_NAME="' + efi_arch_alt + '"', - efi_arch_c_args.get(efi_cpu_family_alt, []), - ], - 'link_args' : [ - efi_c_ld_args, - efi_arch_c_ld_args.get(efi_cpu_family_alt, []), - ], - } -endif - -foreach archspec : efi_archspecs - libefi = static_library( - 'efi' + archspec['arch'], - fundamental_sources, - libefi_sources, - version_h, - include_directories : efi_includes, - c_args : archspec['c_args'], - gnu_symbol_visibility : 'hidden', - override_options : efi_override_options, - pic : true) - - kwargs = { - 'include_directories' : efi_includes, - 'c_args' : archspec['c_args'], - 'link_args' : archspec['link_args'], - 'gnu_symbol_visibility' : 'hidden', - 'override_options' : efi_override_options, - 'pie' : true, - } - - efi_elf_binaries += executable( - 'systemd-boot' + archspec['arch'], - sources : [systemd_boot_sources, version_h], - link_with : libefi, - name_suffix : 'elf', - kwargs : kwargs) - - efi_elf_binaries += executable( - 'linux' + archspec['arch'], - sources : [stub_sources, version_h], - link_with : libefi, - name_suffix : 'elf.stub', - kwargs : kwargs) - - efi_elf_binaries += executable( - 'addon' + archspec['arch'], - sources : [addon_sources, version_h], - name_suffix : 'elf.stub', - kwargs : kwargs) -endforeach - -foreach efi_elf_binary : efi_elf_binaries - name = efi_elf_binary.name() - name += name.startswith('systemd-boot') ? '.efi' : '.efi.stub' - - # For the addon, given it's empty, we need to explicitly reserve space in the header to account for - # the sections that ukify will add. - if name.startswith('linux') - minimum_sections = get_option('efi-stub-extra-sections') - elif name.startswith('addon') - minimum_sections = get_option('efi-addon-extra-sections') - else - minimum_sections = 0 - endif - - exe = custom_target( - name, - output : name, - input : efi_elf_binary, - install : true, - install_dir : bootlibdir, - install_tag : 'systemd-boot', - command : [ - elf2efi_py, - '--version-major=' + project_major_version, - '--version-minor=' + project_minor_version, - '--efi-major=1', - '--efi-minor=1', - '--subsystem=10', - '--minimum-sections=@0@'.format(minimum_sections), - '--copy-sections=.sbat,.sdmagic,.osrel', - '@INPUT@', - '@OUTPUT@', - ]) - boot_targets += exe - if name.startswith('linux') - boot_stubs += exe - endif - - # This is supposed to match exactly one time - if name == 'addon@0@.efi.stub'.format(efi_arch) - efi_addon = exe.full_path() - endif - - test('check-alignment-@0@'.format(name), - check_efi_alignment_py, - args : exe.full_path(), - suite : 'efi') -endforeach - -alias_target('systemd-boot', boot_targets) diff --git a/src/boot/efi/efivars.c b/src/boot/efivars.c similarity index 100% rename from src/boot/efi/efivars.c rename to src/boot/efivars.c diff --git a/src/boot/efi/efivars.h b/src/boot/efivars.h similarity index 100% rename from src/boot/efi/efivars.h rename to src/boot/efivars.h diff --git a/src/boot/efi/export-vars.c b/src/boot/export-vars.c similarity index 100% rename from src/boot/efi/export-vars.c rename to src/boot/export-vars.c diff --git a/src/boot/efi/export-vars.h b/src/boot/export-vars.h similarity index 100% rename from src/boot/efi/export-vars.h rename to src/boot/export-vars.h diff --git a/src/boot/efi/fuzz-bcd.c b/src/boot/fuzz-bcd.c similarity index 100% rename from src/boot/efi/fuzz-bcd.c rename to src/boot/fuzz-bcd.c diff --git a/src/boot/efi/fuzz-efi-osrel.c b/src/boot/fuzz-efi-osrel.c similarity index 100% rename from src/boot/efi/fuzz-efi-osrel.c rename to src/boot/fuzz-efi-osrel.c diff --git a/src/boot/efi/fuzz-efi-printf.c b/src/boot/fuzz-efi-printf.c similarity index 100% rename from src/boot/efi/fuzz-efi-printf.c rename to src/boot/fuzz-efi-printf.c diff --git a/src/boot/efi/fuzz-efi-string.c b/src/boot/fuzz-efi-string.c similarity index 100% rename from src/boot/efi/fuzz-efi-string.c rename to src/boot/fuzz-efi-string.c diff --git a/src/boot/efi/graphics.c b/src/boot/graphics.c similarity index 100% rename from src/boot/efi/graphics.c rename to src/boot/graphics.c diff --git a/src/boot/efi/graphics.h b/src/boot/graphics.h similarity index 100% rename from src/boot/efi/graphics.h rename to src/boot/graphics.h diff --git a/src/boot/efi/initrd.c b/src/boot/initrd.c similarity index 100% rename from src/boot/efi/initrd.c rename to src/boot/initrd.c diff --git a/src/boot/efi/initrd.h b/src/boot/initrd.h similarity index 100% rename from src/boot/efi/initrd.h rename to src/boot/initrd.h diff --git a/src/boot/efi/linux.c b/src/boot/linux.c similarity index 100% rename from src/boot/efi/linux.c rename to src/boot/linux.c diff --git a/src/boot/efi/linux.h b/src/boot/linux.h similarity index 100% rename from src/boot/efi/linux.h rename to src/boot/linux.h diff --git a/src/boot/efi/linux_x86.c b/src/boot/linux_x86.c similarity index 100% rename from src/boot/efi/linux_x86.c rename to src/boot/linux_x86.c diff --git a/src/boot/efi/log.c b/src/boot/log.c similarity index 100% rename from src/boot/efi/log.c rename to src/boot/log.c diff --git a/src/boot/efi/log.h b/src/boot/log.h similarity index 100% rename from src/boot/efi/log.h rename to src/boot/log.h diff --git a/src/boot/measure.c b/src/boot/measure.c index eacf90f08c0..15f1ba9afff 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -1,1171 +1,324 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include +#if ENABLE_TPM -#include "sd-json.h" - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "build.h" -#include "efi-loader.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "main-func.h" -#include "memstream-util.h" -#include "openssl-util.h" -#include "parse-argument.h" -#include "parse-util.h" -#include "pe-binary.h" -#include "pretty-print.h" -#include "sha256.h" -#include "strv.h" -#include "terminal-util.h" +#include "macro-fundamental.h" +#include "measure.h" +#include "memory-util-fundamental.h" +#include "proto/cc-measurement.h" +#include "proto/tcg.h" #include "tpm2-pcr.h" -#include "tpm2-util.h" -#include "uki.h" -#include "verbs.h" +#include "util.h" -/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used - * to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */ +static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( + EFI_TCG2_PROTOCOL *tcg, + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + uint64_t buffer_size, + uint32_t event_id, + const char16_t *description) { -static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; -static char **arg_banks = NULL; -static char *arg_tpm2_device = NULL; -static char *arg_private_key = NULL; -static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; -static char *arg_private_key_source = NULL; -static char *arg_public_key = NULL; -static char *arg_certificate = NULL; -static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_OFF; -static PagerFlags arg_pager_flags = 0; -static bool arg_current = false; -static char **arg_phase = NULL; -static char *arg_append = NULL; + _cleanup_free_ struct event { + EFI_TCG2_EVENT tcg_event; + EFI_TCG2_TAGGED_EVENT tcg_tagged_event; + } _packed_ *event = NULL; + size_t desc_len, event_size; -STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); -STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); -STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); -STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); -STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); -STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); -STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); -STATIC_DESTRUCTOR_REGISTER(arg_append, freep); + assert(tcg); + assert(description); -static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { - for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) - free((*sections)[c]); + /* New style stuff we log as EV_EVENT_TAG with a recognizable event tag. */ + + desc_len = strsize16(description); + event_size = offsetof(EFI_TCG2_EVENT, Event) + offsetof(EFI_TCG2_TAGGED_EVENT, Event) + desc_len; + + event = xmalloc(event_size); + *event = (struct event) { + .tcg_event = (EFI_TCG2_EVENT) { + .Size = event_size, + .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER), + .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION, + .Header.PCRIndex = pcrindex, + .Header.EventType = EV_EVENT_TAG, + }, + .tcg_tagged_event = { + .EventId = event_id, + .EventSize = desc_len, + }, + }; + memcpy(event->tcg_tagged_event.Event, description, desc_len); + + return tcg->HashLogExtendEvent( + tcg, + 0, + buffer, buffer_size, + &event->tcg_event); } -STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); +static EFI_STATUS tpm2_measure_to_pcr_and_ipl_event_log( + EFI_TCG2_PROTOCOL *tcg, + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + uint64_t buffer_size, + const char16_t *description) { -static int help(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *link = NULL; - int r; + _cleanup_free_ EFI_TCG2_EVENT *tcg_event = NULL; + size_t desc_len; - r = terminal_urlify_man("systemd-measure", "1", &link); - if (r < 0) - return log_oom(); + assert(tcg); + assert(description); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current PCR values\n" - " calculate Calculate expected PCR values\n" - " sign Calculate and sign expected PCR values\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " -c --current Use current PCR values\n" - " --phase=PHASE Specify a boot phase to sign for\n" - " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH PEM certificate to use when signing with a URI\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --append=PATH Load specified JSON signature, and append new signature to it\n" - "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" - " --linux=PATH Path to Linux kernel image file %7$s .linux\n" - " --osrel=PATH Path to os-release file %7$s .osrel\n" - " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" - " --initrd=PATH Path to initrd image file %7$s .initrd\n" - " --ucode=PATH Path to microcode image file %7$s .ucode\n" - " --splash=PATH Path to splash bitmap file %7$s .splash\n" - " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" - " --uname=PATH Path to 'uname -r' file %7$s .uname\n" - " --sbat=PATH Path to SBAT file %7$s .sbat\n" - " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" - " --profile=PATH Path to profile file %7$s .profile\n" - " --hwids=PATH Path to HWIDs file %7$s .hwids\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT)); + /* We record older stuff as EV_IPL. Which sucks, because it makes it hard to recognize from the event + * log which of the events are ours. Measurement logs are kinda API hence this is hard to change for + * existing, established events. But for future additions, let's use EV_EVENT_TAG instead, with a tag + * of our choosing that makes clear what precisely we are measuring here. See above. */ - return 0; + desc_len = strsize16(description); + tcg_event = xmalloc(offsetof(EFI_TCG2_EVENT, Event) + desc_len); + *tcg_event = (EFI_TCG2_EVENT) { + .Size = offsetof(EFI_TCG2_EVENT, Event) + desc_len, + .Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER), + .Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION, + .Header.PCRIndex = pcrindex, + .Header.EventType = EV_IPL, + }; + + memcpy(tcg_event->Event, description, desc_len); + + return tcg->HashLogExtendEvent( + tcg, + 0, + buffer, buffer_size, + tcg_event); } -static char *normalize_phase(const char *s) { - _cleanup_strv_free_ char **l = NULL; +static EFI_STATUS cc_measure_to_mr_and_ipl_event_log( + EFI_CC_MEASUREMENT_PROTOCOL *cc, + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + uint64_t buffer_size, + const char16_t *description) { - /* Let's normalize phase expressions. We split the series of colon-separated words up, then remove - * all empty ones, and glue them back together again. In other words we remove duplicate ":", as well - * as leading and trailing ones. */ + _cleanup_free_ EFI_CC_EVENT *event = NULL; + uint32_t mr; + EFI_STATUS err; + size_t desc_len; - l = strv_split(s, ":"); /* Split series of words */ - if (!l) + assert(cc); + assert(description); + + /* MapPcrToMrIndex service provides callers information on + * how the TPM PCR registers are mapped to the CC measurement + * registers (MR) in the vendor implementation. */ + err = cc->MapPcrToMrIndex(cc, pcrindex, &mr); + if (err != EFI_SUCCESS) + return EFI_NOT_FOUND; + + desc_len = strsize16(description); + event = xmalloc(offsetof(EFI_CC_EVENT, Event) + desc_len); + *event = (EFI_CC_EVENT) { + .Size = offsetof(EFI_CC_EVENT, Event) + desc_len, + .Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER), + .Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION, + .Header.MrIndex = mr, + .Header.EventType = EV_IPL, + }; + + memcpy(event->Event, description, desc_len); + + return cc->HashLogExtendEvent( + cc, + 0, + buffer, + buffer_size, + event); +} + +static EFI_CC_MEASUREMENT_PROTOCOL *cc_interface_check(void) { + EFI_CC_BOOT_SERVICE_CAPABILITY capability = { + .Size = sizeof(capability), + }; + EFI_STATUS err; + EFI_CC_MEASUREMENT_PROTOCOL *cc; + + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_CC_MEASUREMENT_PROTOCOL), NULL, (void **) &cc); + if (err != EFI_SUCCESS) return NULL; - /* Remove all empty words and glue things back together */ - return strv_join(strv_remove(l, ""), ":"); + err = cc->GetCapability(cc, &capability); + if (err != EFI_SUCCESS) + return NULL; + + if (!(capability.SupportedEventLogs & EFI_CC_EVENT_LOG_FORMAT_TCG_2)) + return NULL; + + return cc; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - _ARG_SECTION_FIRST, - ARG_LINUX = _ARG_SECTION_FIRST, - ARG_OSREL, - ARG_CMDLINE, - ARG_INITRD, - ARG_UCODE, - ARG_SPLASH, - ARG_DTB, - ARG_UNAME, - ARG_SBAT, - _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ - ARG_PCRPKEY, - ARG_PROFILE, - ARG_HWIDS, - _ARG_SECTION_LAST, - ARG_DTBAUTO = _ARG_SECTION_LAST, - ARG_BANK, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PUBLIC_KEY, - ARG_CERTIFICATE, - ARG_TPM2_DEVICE, - ARG_JSON, - ARG_PHASE, - ARG_APPEND, +static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { + EFI_TCG2_BOOT_SERVICE_CAPABILITY capability = { + .Size = sizeof(capability), }; + EFI_STATUS err; + EFI_TCG2_PROTOCOL *tcg; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "ucode", required_argument, NULL, ARG_UCODE }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "hwids", required_argument, NULL, ARG_HWIDS }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, - {} - }; + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG2_PROTOCOL), NULL, (void **) &tcg); + if (err != EFI_SUCCESS) + return NULL; - int c, r; + err = tcg->GetCapability(tcg, &capability); + if (err != EFI_SUCCESS) + return NULL; - assert(argc >= 0); - assert(argv); - - /* Make sure the arguments list and the section list, stays in sync */ - assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); - - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) - switch (c) { - - case 'h': - help(0, NULL, NULL); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; - - case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { - UnifiedSection section = c - _ARG_SECTION_FIRST; - - r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); - if (r < 0) - return r; - break; - } - - case 'c': - arg_current = true; - break; - - case ARG_BANK: { - const EVP_MD *implementation; - - implementation = EVP_get_digestbyname(optarg); - if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); - - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) - return log_oom(); - - break; - } - - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); - if (r < 0) - return r; - - break; - - case ARG_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument( - optarg, - &arg_private_key_source, - &arg_private_key_source_type); - if (r < 0) - return r; - - break; - - case ARG_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); - if (r < 0) - return r; - - break; - - case ARG_CERTIFICATE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); - if (r < 0) - return r; - - break; - - case ARG_TPM2_DEVICE: { - _cleanup_free_ char *device = NULL; - - if (streq(optarg, "list")) - return tpm2_list_devices(); - - if (!streq(optarg, "auto")) { - device = strdup(optarg); - if (!device) - return log_oom(); - } - - free_and_replace(arg_tpm2_device, device); - break; - } - - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - break; - - case ARG_PHASE: { - char *n; - - n = normalize_phase(optarg); - if (!n) - return log_oom(); - - r = strv_consume(&arg_phase, TAKE_PTR(n)); - if (r < 0) - return r; - - break; - } - - case ARG_APPEND: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); - if (r < 0) - return r; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); - } - - if (arg_public_key && arg_certificate) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --public-key= and --certificate= specified, refusing."); - - if (arg_private_key_source && !arg_certificate) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); - - if (strv_isempty(arg_banks)) { - /* If no banks are specifically selected, pick all known banks */ - arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); - if (!arg_banks) - return log_oom(); + if (capability.StructureVersion.Major == 1 && + capability.StructureVersion.Minor == 0) { + EFI_TCG_BOOT_SERVICE_CAPABILITY *caps_1_0 = + (EFI_TCG_BOOT_SERVICE_CAPABILITY*) &capability; + if (caps_1_0->TPMPresentFlag) + return tcg; } - strv_sort_uniq(arg_banks); + if (!capability.TPMPresentFlag) + return NULL; - if (arg_current) - for (UnifiedSection us = 0; us < _UNIFIED_SECTION_MAX; us++) - if (arg_sections[us]) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --current switch cannot be used in combination with --linux= and related switches."); - - if (strv_isempty(arg_phase)) { - /* If no phases are specifically selected, pick everything from the beginning of the initrd - * to the beginning of shutdown. */ - if (strv_extend_many(&arg_phase, - "enter-initrd", - "enter-initrd:leave-initrd", - "enter-initrd:leave-initrd:sysinit", - "enter-initrd:leave-initrd:sysinit:ready") < 0) - return log_oom(); - } else - strv_sort_uniq(arg_phase); - - _cleanup_free_ char *j = NULL; - j = strv_join(arg_phase, ", "); - if (!j) - return log_oom(); - - log_debug("Measuring boot phases: %s", j); - return 1; + return tcg; } -/* The PCR 11 state for one specific bank */ -typedef struct PcrState { - char *bank; - const EVP_MD *md; - void *value; - size_t value_size; - void *saved_value; /* A copy of the original value we calculated, used by pcr_states_save()/pcr_states_restore() to come later back to */ -} PcrState; - -static void pcr_state_free_all(PcrState **pcr_state) { - assert(pcr_state); - - if (!*pcr_state) - return; - - for (size_t i = 0; (*pcr_state)[i].value; i++) { - free((*pcr_state)[i].bank); - free((*pcr_state)[i].value); - free((*pcr_state)[i].saved_value); - } - - *pcr_state = mfree(*pcr_state); +bool tpm_present(void) { + return tcg2_interface_check(); } -static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) { - assert(md); +static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { + EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err = EFI_SUCCESS; - if (!*md) - return; + assert(ret_measured); - for (size_t i = 0; (*md)[i]; i++) - EVP_MD_CTX_free((*md)[i]); + tpm2 = tcg2_interface_check(); + if (!tpm2) { + *ret_measured = false; + return EFI_SUCCESS; + } - *md = mfree(*md); + err = tpm2_measure_to_pcr_and_ipl_event_log(tpm2, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; + + *ret_measured = true; + return EFI_SUCCESS; } -static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) { - _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL; - unsigned value_size; +static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { + EFI_CC_MEASUREMENT_PROTOCOL *cc; + EFI_STATUS err = EFI_SUCCESS; - assert(pcr_state); - assert(data || sz == 0); - assert(pcr_state->md); - assert(pcr_state->value); - assert(pcr_state->value_size > 0); + assert(ret_measured); - /* Extends a (virtual) PCR by the given data */ + cc = cc_interface_check(); + if (!cc) { + *ret_measured = false; + return EFI_SUCCESS; + } - mc = EVP_MD_CTX_new(); - if (!mc) - return log_oom(); + err = cc_measure_to_mr_and_ipl_event_log(cc, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; - if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", pcr_state->bank); - - /* First thing we do, is hash the old PCR value */ - if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - - /* Then, we hash the new data */ - if (EVP_DigestUpdate(mc, data, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - - if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); - - assert(value_size == pcr_state->value_size); - return 0; + *ret_measured = true; + return EFI_SUCCESS; } -#define BUFFER_SIZE (16U * 1024U) +EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { + EFI_STATUS err; + bool tpm_ret_measured, cc_ret_measured; -static int measure_kernel(PcrState *pcr_states, size_t n) { - _cleanup_free_ void *buffer = NULL; - int r; + assert(description || pcrindex == UINT32_MAX); - assert(n > 0); - assert(pcr_states); + /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured + * something, or false if measurement was turned off. */ - /* Virtually measures the components of a unified kernel image into PCR 11 */ + if (pcrindex == UINT32_MAX) { /* PCR disabled? */ + if (ret_measured) + *ret_measured = false; - if (arg_current) { - /* Shortcut things, if we should just use the current PCR value */ - - for (size_t i = 0; i < n; i++) { - _cleanup_free_ char *p = NULL, *s = NULL; - _cleanup_free_ void *v = NULL; - size_t sz; - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%i", pcr_states[i].bank, TPM2_PCR_KERNEL_BOOT) < 0) - return log_oom(); - - r = read_virtual_file(p, 4096, &s, NULL); - if (r == -ENOENT && access("/sys/class/tpm/tpm0/", F_OK) >= 0) - return log_error_errno(r, "TPM device exists, but cannot open '%s'; either the kernel is too old, or selected PCR bank is not supported: %m", p); - if (r < 0) - return log_error_errno(r, "Failed to read '%s': %m", p); - - r = unhexmem(strstrip(s), &v, &sz); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - - assert(pcr_states[i].value_size == sz); - memcpy(pcr_states[i].value, v, sz); - } - - return 0; + return EFI_SUCCESS; } - buffer = malloc(BUFFER_SIZE); - if (!buffer) - return log_oom(); + /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299 */ + err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); + if (err != EFI_SUCCESS) + return err; - for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) { - _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL; - _cleanup_close_ int fd = -EBADF; - uint64_t m = 0; + err = tcg2_log_ipl_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); + if (err != EFI_SUCCESS) + return err; - if (!arg_sections[c]) - continue; + if (ret_measured) + *ret_measured = tpm_ret_measured || cc_ret_measured; - fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]); - - /* Allocate one message digest context per bank (NULL terminated) */ - mdctx = new0(EVP_MD_CTX*, n + 1); - if (!mdctx) - return log_oom(); - - for (size_t i = 0; i < n; i++) { - mdctx[i] = EVP_MD_CTX_new(); - if (!mdctx[i]) - return log_oom(); - - if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to initialize data %s context.", pcr_states[i].bank); - } - - for (;;) { - ssize_t sz; - - sz = read(fd, buffer, BUFFER_SIZE); - if (sz < 0) - return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]); - if (sz == 0) /* EOF */ - break; - - for (size_t i = 0; i < n; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - - m += sz; - } - - if (c == UNIFIED_SECTION_LINUX) { - _cleanup_free_ PeHeader *pe_header = NULL; - - r = pe_load_headers(fd, /*ret_dos_header=*/ NULL, &pe_header); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel image file '%s', ignoring: %m", arg_sections[c]); - else if (m < pe_header->optional.SizeOfImage) { - memzero(buffer, BUFFER_SIZE); - - /* Our EFI stub measures VirtualSize bytes of the .linux section into PCR 11. - * Notably, VirtualSize can be larger than the section's size on disk. In - * that case the extra space is initialized with zeros, so the stub ends up - * measuring a bunch of zeros. To accommodate this, we have to measure the - * same number of zeros here. We opt to measure extra zeros here instead of - * modifying the stub to only measure the number of bytes on disk as we want - * newer ukify + systemd-measure to work with older versions of the stub and - * as of 6.12 the kernel image's VirtualSize won't be larger than its size on - * disk anymore (see https://github.com/systemd/systemd/issues/34578#issuecomment-2382459515). - */ - - while (m < pe_header->optional.SizeOfImage) { - uint64_t sz = MIN(BUFFER_SIZE, pe_header->optional.SizeOfImage - m); - - for (size_t i = 0; i < n; i++) - if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); - - m += sz; - } - } - } - - fd = safe_close(fd); - - if (m == 0) /* We skip over empty files, the stub does so too */ - continue; - - for (size_t i = 0; i < n; i++) { - _cleanup_free_ void *data_hash = NULL; - unsigned data_hash_size; - - data_hash = malloc(pcr_states[i].value_size); - if (!data_hash) - return log_oom(); - - /* Measure name of section */ - if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", pcr_states[i].bank); - - assert(data_hash_size == (unsigned) pcr_states[i].value_size); - - r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); - if (r < 0) - return r; - - /* Retrieve hash of data and measure it */ - if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); - - assert(data_hash_size == (unsigned) pcr_states[i].value_size); - - r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); - if (r < 0) - return r; - } - } - - return 0; + return EFI_SUCCESS; } -static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { - _cleanup_strv_free_ char **l = NULL; - int r; +EFI_STATUS tpm_log_tagged_event( + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + size_t buffer_size, + uint32_t event_id, + const char16_t *description, + bool *ret_measured) { - assert(pcr_states); - assert(n > 0); + EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err; - /* Measure a phase string into PCR 11. This splits up the "phase" expression at colons, and then - * virtually extends each specified word into PCR 11, to model how during boot we measure a series of - * words into PCR 11, one for each phase. */ + assert(description || pcrindex == UINT32_MAX); + assert(event_id > 0); - l = strv_split(phase, ":"); - if (!l) - return log_oom(); + /* If EFI_SUCCESS is returned, will initialize ret_measured to true if we actually measured + * something, or false if measurement was turned off. */ - STRV_FOREACH(word, l) { - size_t wl; + tpm2 = tcg2_interface_check(); + if (!tpm2 || pcrindex == UINT32_MAX) { /* PCR disabled? */ + if (ret_measured) + *ret_measured = false; - if (isempty(*word)) - continue; - - wl = strlen(*word); - - for (size_t i = 0; i < n; i++) { /* For each bank */ - _cleanup_free_ void *b = NULL; - int bsz; - - bsz = EVP_MD_size(pcr_states[i].md); - assert(bsz > 0); - - b = malloc(bsz); - if (!b) - return log_oom(); - - /* First hash the word itself */ - if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word); - - /* And then extend the PCR with the resulting hash */ - r = pcr_state_extend(pcr_states + i, b, bsz); - if (r < 0) - return r; - } + return EFI_SUCCESS; } - return 0; + err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); + if (!err) + return err; + + *ret_measured = true; + return EFI_SUCCESS; } -static int pcr_states_allocate(PcrState **ret) { - _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; - size_t n = 0; +EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { + _cleanup_free_ char16_t *c = NULL; - pcr_states = new0(PcrState, strv_length(arg_banks) + 1); - if (!pcr_states) - return log_oom(); + if (description) + c = xstr8_to_16(description); - /* Allocate a PCR state structure, one for each bank */ - STRV_FOREACH(d, arg_banks) { - const EVP_MD *implementation; - _cleanup_free_ void *v = NULL; - _cleanup_free_ char *b = NULL; - int sz; - - assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ - - b = strdup(EVP_MD_name(implementation)); - if (!b) - return log_oom(); - - sz = EVP_MD_size(implementation); - if (sz <= 0 || sz >= INT_MAX) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz); - - v = malloc0(sz); /* initial PCR state is all zeroes */ - if (!v) - return log_oom(); - - pcr_states[n++] = (struct PcrState) { - .bank = ascii_strlower(TAKE_PTR(b)), - .md = implementation, - .value = TAKE_PTR(v), - .value_size = sz, - }; - } - - *ret = TAKE_PTR(pcr_states); - return (int) n; + return tpm_log_ipl_event(pcrindex, buffer, buffer_size, c, ret_measured); } -static int pcr_states_save(PcrState *pcr_states, size_t n) { - assert(pcr_states); - assert(n > 0); +EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) { + EFI_STATUS err; - for (size_t i = 0; i < n; i++) { - _cleanup_free_ void *saved = NULL; + /* Measures a load options string into the TPM2, i.e. the kernel command line */ - if (!pcr_states[i].value) - continue; + err = tpm_log_ipl_event( + TPM2_PCR_KERNEL_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(load_options), + strsize16(load_options), + load_options, + ret_measured); + if (err != EFI_SUCCESS) + return log_error_status( + err, + "Unable to add load options (i.e. kernel command) line measurement to PCR %i: %m", + TPM2_PCR_KERNEL_CONFIG); - saved = memdup(pcr_states[i].value, pcr_states[i].value_size); - if (!saved) - return log_oom(); - - free_and_replace(pcr_states[i].saved_value, saved); - } - - return 0; + return EFI_SUCCESS; } -static void pcr_states_restore(PcrState *pcr_states, size_t n) { - assert(pcr_states); - assert(n > 0); - - for (size_t i = 0; i < n; i++) { - - assert(pcr_states[i].value); - assert(pcr_states[i].saved_value); - - memcpy(pcr_states[i].value, pcr_states[i].saved_value, pcr_states[i].value_size); - } -} - -static int verb_calculate(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; - _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; - int r; - - if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Either --linux= or --current must be specified, refusing."); - if (arg_append) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --append= switch is only supported for 'sign', not 'calculate'."); - - assert(!strv_isempty(arg_banks)); - assert(!strv_isempty(arg_phase)); - - r = pcr_states_allocate(&pcr_states); - if (r < 0) - return r; - - size_t n = r; - - r = measure_kernel(pcr_states, n); - if (r < 0) - return r; - - /* Save the current state, so that we later can restore to it. This way we can measure the PCR values - * for multiple different boot phases without heaving to start from zero each time */ - r = pcr_states_save(pcr_states, n); - if (r < 0) - return r; - - STRV_FOREACH(phase, arg_phase) { - - r = measure_phase(pcr_states, n, *phase); - if (r < 0) - return r; - - for (size_t i = 0; i < n; i++) { - if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *hd = NULL; - - if (i == 0) { - fflush(stdout); - fprintf(stderr, "%s# PCR[%i] Phase <%s>%s\n", - ansi_grey(), - TPM2_PCR_KERNEL_BOOT, - isempty(*phase) ? ":" : *phase, - ansi_normal()); - fflush(stderr); - } - - hd = hexmem(pcr_states[i].value, pcr_states[i].value_size); - if (!hd) - return log_oom(); - - printf("%i:%s=%s\n", TPM2_PCR_KERNEL_BOOT, pcr_states[i].bank, hd); - } else { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - - array = sd_json_variant_ref(sd_json_variant_by_key(w, pcr_states[i].bank)); - - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_CONDITION(!isempty(*phase), "phase", SD_JSON_BUILD_STRING(*phase)), - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_INTEGER(TPM2_PCR_KERNEL_BOOT)), - SD_JSON_BUILD_PAIR("hash", SD_JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))); - if (r < 0) - return log_error_errno(r, "Failed to append JSON object to array: %m"); - - r = sd_json_variant_set_field(&w, pcr_states[i].bank, array); - if (r < 0) - return log_error_errno(r, "Failed to add bank info to object: %m"); - } - } - - /* Return to the original kernel measurement for the next phase calculation */ - pcr_states_restore(pcr_states, n); - } - - if (sd_json_format_enabled(arg_json_format_flags)) { - - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(w, arg_json_format_flags, stdout, NULL); - } - - return 0; -} - -static int verb_sign(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; - _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; - _cleanup_(X509_freep) X509 *certificate = NULL; - size_t n; - int r; - - if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Either --linux= or --current must be specified, refusing."); - - if (!arg_private_key) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No private key specified, use --private-key=."); - - assert(!strv_isempty(arg_banks)); - assert(!strv_isempty(arg_phase)); - - if (arg_append) { - r = sd_json_parse_file(NULL, arg_append, 0, &v, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse '%s': %m", arg_append); - - if (!sd_json_variant_is_object(v)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "File '%s' is not a valid JSON object, refusing.", arg_append); - } - - /* When signing we only support JSON output */ - arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; - - /* This must be done before openssl_load_private_key() otherwise it will get stuck */ - if (arg_certificate) { - r = openssl_load_x509_certificate(arg_certificate, &certificate); - if (r < 0) - return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); - } - - if (arg_private_key) { - if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { - r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); - if (r < 0) - return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); - } - - r = openssl_load_private_key( - arg_private_key_source_type, - arg_private_key_source, - arg_private_key, - &(AskPasswordRequest) { - .id = "measure-private-key-pin", - .keyring = arg_private_key, - .credential = "measure.private-key-pin", - }, - &privkey, - &ui); - if (r < 0) - return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key); - } - - if (arg_public_key) { - _cleanup_fclose_ FILE *pubkeyf = NULL; - - pubkeyf = fopen(arg_public_key, "re"); - if (!pubkeyf) - return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key); - - pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); - if (!pubkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); - } else if (certificate) { - pubkey = X509_get_pubkey(certificate); - if (!pubkey) - return log_error_errno( - SYNTHETIC_ERRNO(EIO), - "Failed to extract public key from certificate %s.", - arg_certificate); - } else { - _cleanup_(memstream_done) MemStream m = {}; - FILE *tf; - - /* No public key was specified, let's derive it automatically, if we can */ - - tf = memstream_init(&m); - if (!tf) - return log_oom(); - - if (i2d_PUBKEY_fp(tf, privkey) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to extract public key from private key file '%s'.", arg_private_key); - - fflush(tf); - rewind(tf); - - if (!d2i_PUBKEY_fp(tf, &pubkey)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to parse extracted public key of private key file '%s'.", arg_private_key); - } - - r = pcr_states_allocate(&pcr_states); - if (r < 0) - return r; - - n = (size_t) r; - - r = measure_kernel(pcr_states, n); - if (r < 0) - return r; - - r = pcr_states_save(pcr_states, n); - if (r < 0) - return r; - - STRV_FOREACH(phase, arg_phase) { - - r = measure_phase(pcr_states, n, *phase); - if (r < 0) - return r; - - for (size_t i = 0; i < n; i++) { - PcrState *p = pcr_states + i; - - int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); - if (tpmalg < 0) - return log_error_errno(tpmalg, "Unsupported PCR bank"); - - Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM2_PCR_KERNEL_BOOT, - tpmalg, - TPM2B_DIGEST_MAKE(p->value, p->value_size)); - - TPM2B_DIGEST pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - - r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); - if (r < 0) - return log_error_errno(r, "Could not calculate PolicyPCR digest: %m"); - - _cleanup_free_ void *sig = NULL; - size_t ss; - - r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); - if (r < 0) - return log_error_errno(r, "Failed to sign PCR policy: %m"); - - _cleanup_free_ void *pubkey_fp = NULL; - size_t pubkey_fp_size = 0; - r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); - if (r < 0) - return r; - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *a = NULL; - r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM2_PCR_KERNEL_BOOT, &a); - if (r < 0) - return log_error_errno(r, "Failed to build JSON PCR mask array: %m"); - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL; - r = sd_json_buildo(&bv, - SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */ - SD_JSON_BUILD_PAIR("pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ - SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */ - SD_JSON_BUILD_PAIR("sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */ - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *av = NULL; - av = sd_json_variant_ref(sd_json_variant_by_key(v, p->bank)); - - r = sd_json_variant_append_array_nodup(&av, bv); - if (r < 0) - return log_error_errno(r, "Failed to append JSON object: %m"); - - r = sd_json_variant_set_field(&v, p->bank, av); - if (r < 0) - return log_error_errno(r, "Failed to add JSON field: %m"); - } - - /* Return to the original kernel measurement for the next phase calculation */ - pcr_states_restore(pcr_states, n); - } - - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - - return 0; -} - -static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { - _cleanup_free_ char *s = NULL; - uint32_t v; - int r; - - r = efi_get_variable_string(varname, &s); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); - - r = safe_atou32(s, &v); - if (r < 0) - return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); - - if (pcr != v) - log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" - "The measurements are likely inconsistent.", description, v, pcr); - - return 0; -} - -static int validate_stub(void) { - uint64_t features; - bool found = false; - int r; - - if (!tpm2_is_fully_supported()) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); - - r = efi_stub_get_features(&features); - if (r < 0) - return log_error_errno(r, "Unable to get stub features: %m"); - - if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) - log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" - "The PCR measurements seen are unlikely to be valid."); - - r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) - return log_oom(); - - if (access(p, F_OK) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); - } else - found = true; - } - - if (!found) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); - - return 0; -} - -static int verb_status(int argc, char *argv[], void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - r = validate_stub(); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; - _cleanup_free_ void *h = NULL; - size_t l; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) - return log_oom(); - - r = read_virtual_file(p, 4096, &s, NULL); - if (r == -ENOENT) - continue; - if (r < 0) - return log_error_errno(r, "Failed to read '%s': %m", p); - - r = unhexmem(strstrip(s), &h, &l); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *f = NULL; - - f = hexmem(h, l); - if (!h) - return log_oom(); - - if (bank == arg_banks) { - /* before the first line for each PCR, write a short descriptive text to - * stderr, and leave the primary content on stdout */ - fflush(stdout); - fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", - ansi_grey(), - (uint32_t) TPM2_PCR_KERNEL_BOOT, - tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), - memeqzero(h, l) ? " (NOT SET!)" : "", - ansi_normal()); - fflush(stderr); - } - - printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); - - } else { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; - - r = sd_json_buildo( - &bv, - SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_INTEGER(TPM2_PCR_KERNEL_BOOT)), - SD_JSON_BUILD_PAIR("hash", SD_JSON_BUILD_HEX(h, l))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); - - r = sd_json_variant_append_array(&a, bv); - if (r < 0) - return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); - - r = sd_json_variant_set_field(&v, b, a); - if (r < 0) - return log_error_errno(r, "Failed to add bank info to object: %m"); - } - } - - if (sd_json_format_enabled(arg_json_format_flags)) { - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } - - return 0; -} - -static int measure_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "sign", VERB_ANY, 1, 0, verb_sign }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -static int run(int argc, char *argv[]) { - int r; - - log_setup(); - - r = parse_argv(argc, argv); - if (r <= 0) - return r; - - return measure_main(argc, argv); -} - -DEFINE_MAIN_FUNCTION(run); +#endif diff --git a/src/boot/efi/measure.h b/src/boot/measure.h similarity index 100% rename from src/boot/efi/measure.h rename to src/boot/measure.h diff --git a/src/boot/meson.build b/src/boot/meson.build index a72388baff2..29c5455dbd1 100644 --- a/src/boot/meson.build +++ b/src/boot/meson.build @@ -1,77 +1,430 @@ # SPDX-License-Identifier: LGPL-2.1-or-later -bootctl_sources = files( - 'bootctl-install.c', - 'bootctl-random-seed.c', - 'bootctl-reboot-to-firmware.c', - 'bootctl-set-efivar.c', - 'bootctl-status.c', - 'bootctl-systemd-efi-options.c', - 'bootctl-uki.c', - 'bootctl-util.c', - 'bootctl.c', -) +efi_config_h_dir = meson.current_build_dir() +efi_addon = '' -if get_option('link-boot-shared') - boot_link_with = [libshared] -else - boot_link_with = [ - libshared_static, - libsystemd_static, - ] -endif +libefitest = static_library( + 'efitest', + files( + 'bcd.c', + 'efi-string.c', + ), + build_by_default : false, + include_directories : [ + basic_includes, + include_directories('.'), + ], + dependencies : userspace) + +efitest_base = { + 'link_with' : [ + libefitest, + libshared, + ], +} +efi_test_template = test_template + efitest_base +efi_fuzz_template = fuzz_template + efitest_base executables += [ - executable_template + { - 'name' : 'bootctl', - 'public' : true, - 'conditions' : [ - 'HAVE_BLKID', - ], - 'sources' : bootctl_sources, - 'link_with' : boot_link_with, - 'dependencies' : [libblkid, libopenssl], + efi_test_template + { + 'sources' : files('test-bcd.c'), + 'dependencies' : libzstd_cflags, + 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_ZSTD'], }, - libexec_template + { - 'name' : 'systemd-bless-boot', - 'public' : true, - 'conditions' : [ - 'HAVE_BLKID', - 'ENABLE_BOOTLOADER', - ], - 'sources' : files('bless-boot.c'), - 'link_with' : boot_link_with, - 'dependencies' : libblkid, + efi_test_template + { + 'sources' : files('test-efi-string.c'), + 'conditions' : ['ENABLE_BOOTLOADER'], }, - generator_template + { - 'name' : 'systemd-bless-boot-generator', - 'conditions' : [ - 'HAVE_BLKID', - 'ENABLE_BOOTLOADER', - ], - 'sources' : files('bless-boot-generator.c'), - 'link_with' : boot_link_with, + efi_fuzz_template + { + 'sources' : files('fuzz-bcd.c'), }, - libexec_template + { - 'name' : 'systemd-measure', - 'conditions' : [ - 'HAVE_BLKID', - 'HAVE_OPENSSL', - 'HAVE_TPM2', - ], - 'sources' : files('measure.c'), - 'dependencies' : libopenssl, + efi_fuzz_template + { + 'sources' : files('fuzz-efi-string.c'), }, - libexec_template + { - 'name' : 'systemd-sbsign', - 'conditions' : [ - 'HAVE_OPENSSL', - ], - 'sources' : files('sbsign.c'), - 'dependencies' : libopenssl, + efi_fuzz_template + { + 'sources' : files('fuzz-efi-osrel.c'), }, - libexec_template + { - 'name' : 'systemd-boot-check-no-failures', - 'sources' : files('boot-check-no-failures.c'), + efi_fuzz_template + { + 'sources' : files('fuzz-efi-printf.c'), }, ] + +if conf.get('ENABLE_BOOTLOADER') != 1 + subdir_done() +endif + +efi_conf = configuration_data() +efi_conf.set10('ENABLE_TPM', get_option('tpm')) + +foreach ctype : ['color-normal', 'color-entry', 'color-highlight', 'color-edit'] + c = get_option('efi-' + ctype).split(',') + efi_conf.set(ctype.underscorify().to_upper(), 'EFI_TEXT_ATTR(@0@, @1@)'.format( + 'EFI_' + c[0].strip().underscorify().to_upper(), + 'EFI_' + c[1].strip().underscorify().to_upper())) +endforeach + +efi_conf.set_quoted('PROJECT_VERSION', project_major_version) +efi_conf.set_quoted('VERSION_TAG', version_tag) +efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL')) + +if meson.is_cross_build() and get_option('sbat-distro') == 'auto' + warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.') +elif get_option('sbat-distro') != '' + efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) + if get_option('sbat-distro-generation') < 1 + error('SBAT Distro Generation must be a positive integer') + endif + efi_conf.set('SBAT_DISTRO_GENERATION', get_option('sbat-distro-generation')) + foreach sbatvar : [['sbat-distro', 'ID'], + ['sbat-distro-summary', 'NAME'], + ['sbat-distro-url', 'BUG_REPORT_URL']] + value = get_option(sbatvar[0]) + if (value == '' or value == 'auto') and not meson.is_cross_build() + cmd = 'if [ -e /etc/os-release ]; then . /etc/os-release; else . /usr/lib/os-release; fi; echo $@0@'.format(sbatvar[1]) + value = run_command(sh, '-c', cmd, check: true).stdout().strip() + endif + if value == '' + error('Required @0@ option not set and autodetection failed'.format(sbatvar[0])) + endif + efi_conf.set_quoted(sbatvar[0].underscorify().to_upper(), value) + endforeach + + pkgname = get_option('sbat-distro-pkgname') + if pkgname == '' + pkgname = meson.project_name() + endif + efi_conf.set_quoted('SBAT_DISTRO_PKGNAME', pkgname) + + pkgver = get_option('sbat-distro-version') + if pkgver == '' + # This is determined during build, not configuration, so we can't display it yet. + efi_conf.set('SBAT_DISTRO_VERSION', 'GIT_VERSION') + else + efi_conf.set_quoted('SBAT_DISTRO_VERSION', pkgver) + endif +endif + +summary({'UEFI architectures' : efi_arch + (efi_arch_alt == '' ? '' : ', ' + efi_arch_alt)}, + section : 'UEFI') + +if efi_conf.get('SBAT_DISTRO', '') != '' + summary({ + 'SBAT distro': efi_conf.get('SBAT_DISTRO'), + 'SBAT distro generation': efi_conf.get('SBAT_DISTRO_GENERATION'), + 'SBAT distro version': efi_conf.get('SBAT_DISTRO_VERSION'), + 'SBAT distro summary': efi_conf.get('SBAT_DISTRO_SUMMARY'), + 'SBAT distro URL': efi_conf.get('SBAT_DISTRO_URL')}, + section : 'UEFI') +endif + +configure_file( + output : 'efi_config.h', + configuration : efi_conf) + +############################################################ + +efi_includes = [ + build_dir_include, + fundamental_include, + include_directories('.'), +] + +efi_c_args = [ + '-DSD_BOOT=1', + '-ffreestanding', + '-fno-strict-aliasing', + '-fshort-wchar', + '-include', 'efi_config.h', +] + +efi_c_args += cc.get_supported_arguments( + '-fwide-exec-charset=UCS2', + # gcc docs says this is required for ms_abi to work correctly. + '-maccumulate-outgoing-args', + '-mstack-protector-guard=global', +) + +# Debug information has little value in release builds as no normal human being knows +# how to attach a debugger to EFI binaries running on real hardware. Anyone who does +# certainly has the means to do their own dev build. +if get_option('mode') == 'developer' and get_option('debug') + efi_c_args += '-DEFI_DEBUG' +endif + +efi_c_ld_args = [ + '-lgcc', + '-nostdlib', + '-static-pie', + '-Wl,--entry=efi_main', + '-Wl,--fatal-warnings', + + # These flags should be passed by -static-pie, but for whatever reason the flag translation + # is not enabled on all architectures. Not passing `-static` would just allow the linker to + # use dynamic libraries, (which we can't/don't use anyway). But if `-pie` is missing and the + # gcc build does not default to `-pie` we get a regular (no-pie) binary that will be + # rightfully rejected by elf2efi. Note that meson also passes `-pie` to the linker driver, + # but it is overridden by our `-static-pie`. We also need to pass these directly to the + # linker as `-static`+`-pie` seem to get translated differently. + '-Wl,-static,-pie,--no-dynamic-linker,-z,text', + + # EFI has 4KiB pages. + '-z', 'common-page-size=4096', + '-z', 'max-page-size=4096', + + '-z', 'noexecstack', + '-z', 'relro', + '-z', 'separate-code', +] + +efi_c_ld_args += cc.get_supported_link_arguments( + # binutils >= 2.38 + '-Wl,-z,nopack-relative-relocs', +) + +# efi_c_args is explicitly passed to targets so that they can override distro-provided flags +# that should not be used for EFI binaries. +efi_disabled_c_args = cc.get_supported_arguments( + '-fcf-protection=none', + '-fno-asynchronous-unwind-tables', + '-fno-exceptions', + '-fno-unwind-tables', +) +efi_override_options = [ + 'b_coverage=false', + 'b_pgo=off', +] + +if get_option('b_sanitize') == 'undefined' + efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize-link-runtime') +else + efi_disabled_c_args += cc.get_supported_arguments('-fno-sanitize=all') + efi_override_options += 'b_sanitize=none' +endif + +efi_c_args += efi_disabled_c_args +efi_c_ld_args += efi_disabled_c_args + +if cc.get_id() == 'clang' + # clang is too picky sometimes. + efi_c_args += '-Wno-unused-command-line-argument' + efi_c_ld_args += '-Wno-unused-command-line-argument' +endif + +efi_arch_c_args = { + 'aarch64' : ['-mgeneral-regs-only'], + 'arm' : ['-mgeneral-regs-only'], + # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead: + 'loongarch64' : ['-mno-lsx', '-mno-lasx'], + # Pass -m64/32 explicitly to make building on x32 work. + 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], + 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], +} +efi_arch_c_ld_args = { + # libgcc is not compiled with -fshort-wchar, but it does not use it anyways, + # so it's fine to link against it. + 'arm' : cc.get_supported_link_arguments('-Wl,--no-wchar-size-warning'), + 'x86_64' : ['-m64'], + 'x86' : ['-m32'], +} + +linker_sanity_code = 'void a(void) {}; void _start(void) { a(); }' +linker_sanity_args = ['-nostdlib', '-Wl,--fatal-warnings'] +if not cc.links(linker_sanity_code, + name : 'linker supports -static-pie', + args : [linker_sanity_args, '-static-pie']) + error('Linker does not support -static-pie.') +endif + +# https://github.com/llvm/llvm-project/issues/67152 +if not cc.links(linker_sanity_code, + name : 'linker supports LTO with -nostdlib', + args : [linker_sanity_args, '-flto']) + efi_c_args += '-fno-lto' + efi_c_ld_args += '-fno-lto' +endif + +# https://github.com/llvm/llvm-project/issues/61101 +if efi_cpu_family_alt == 'x86' and not cc.links(linker_sanity_code, + name : 'linker supports LTO with -nostdlib (x86)', + args : [linker_sanity_args, '-flto', '-m32']) + efi_arch_c_args += { 'x86' : efi_arch_c_args['x86'] + '-fno-lto' } + efi_arch_c_ld_args += { 'x86' : efi_arch_c_ld_args['x86'] + '-fno-lto' } +endif + +############################################################ + +libefi_sources = files( + 'chid.c', + 'console.c', + 'device-path-util.c', + 'devicetree.c', + 'drivers.c', + 'efi-string.c', + 'efivars.c', + 'export-vars.c', + 'graphics.c', + 'initrd.c', + 'log.c', + 'measure.c', + 'part-discovery.c', + 'pe.c', + 'random-seed.c', + 'secure-boot.c', + 'shim.c', + 'smbios.c', + 'ticks.c', + 'util.c', + 'vmm.c', +) + +systemd_boot_sources = files( + 'boot.c', +) + +stub_sources = files( + 'cpio.c', + 'linux.c', + 'splash.c', + 'stub.c', +) + +addon_sources = files( + 'addon.c', +) + +if get_option('b_sanitize') == 'undefined' + libefi_sources += files('ubsan.c') +endif + +if host_machine.cpu_family() in ['x86', 'x86_64'] + stub_sources += files('linux_x86.c') +endif + +# BCD parser only makes sense on arches that Windows supports. +if host_machine.cpu_family() in ['aarch64', 'arm', 'x86_64', 'x86'] + systemd_boot_sources += files('bcd.c') +endif + +boot_targets = [] +efi_elf_binaries = [] +efi_archspecs = [ + { + 'arch' : efi_arch, + 'c_args' : [ + efi_c_args, + '-DEFI_MACHINE_TYPE_NAME="' + efi_arch + '"', + efi_arch_c_args.get(host_machine.cpu_family(), []), + ], + 'link_args' : [ + efi_c_ld_args, + efi_arch_c_ld_args.get(host_machine.cpu_family(), []), + ], + }, +] +if efi_arch_alt != '' + efi_archspecs += { + 'arch' : efi_arch_alt, + 'c_args' : [ + efi_c_args, + '-DEFI_MACHINE_TYPE_NAME="' + efi_arch_alt + '"', + efi_arch_c_args.get(efi_cpu_family_alt, []), + ], + 'link_args' : [ + efi_c_ld_args, + efi_arch_c_ld_args.get(efi_cpu_family_alt, []), + ], + } +endif + +foreach archspec : efi_archspecs + libefi = static_library( + 'efi' + archspec['arch'], + fundamental_sources, + libefi_sources, + version_h, + include_directories : efi_includes, + c_args : archspec['c_args'], + gnu_symbol_visibility : 'hidden', + override_options : efi_override_options, + pic : true) + + kwargs = { + 'include_directories' : efi_includes, + 'c_args' : archspec['c_args'], + 'link_args' : archspec['link_args'], + 'gnu_symbol_visibility' : 'hidden', + 'override_options' : efi_override_options, + 'pie' : true, + } + + efi_elf_binaries += executable( + 'systemd-boot' + archspec['arch'], + sources : [systemd_boot_sources, version_h], + link_with : libefi, + name_suffix : 'elf', + kwargs : kwargs) + + efi_elf_binaries += executable( + 'linux' + archspec['arch'], + sources : [stub_sources, version_h], + link_with : libefi, + name_suffix : 'elf.stub', + kwargs : kwargs) + + efi_elf_binaries += executable( + 'addon' + archspec['arch'], + sources : [addon_sources, version_h], + name_suffix : 'elf.stub', + kwargs : kwargs) +endforeach + +foreach efi_elf_binary : efi_elf_binaries + name = efi_elf_binary.name() + name += name.startswith('systemd-boot') ? '.efi' : '.efi.stub' + + # For the addon, given it's empty, we need to explicitly reserve space in the header to account for + # the sections that ukify will add. + if name.startswith('linux') + minimum_sections = get_option('efi-stub-extra-sections') + elif name.startswith('addon') + minimum_sections = get_option('efi-addon-extra-sections') + else + minimum_sections = 0 + endif + + exe = custom_target( + name, + output : name, + input : efi_elf_binary, + install : true, + install_dir : bootlibdir, + install_tag : 'systemd-boot', + command : [ + elf2efi_py, + '--version-major=' + project_major_version, + '--version-minor=' + project_minor_version, + '--efi-major=1', + '--efi-minor=1', + '--subsystem=10', + '--minimum-sections=@0@'.format(minimum_sections), + '--copy-sections=.sbat,.sdmagic,.osrel', + '@INPUT@', + '@OUTPUT@', + ]) + boot_targets += exe + if name.startswith('linux') + boot_stubs += exe + endif + + # This is supposed to match exactly one time + if name == 'addon@0@.efi.stub'.format(efi_arch) + efi_addon = exe.full_path() + endif + + test('check-alignment-@0@'.format(name), + check_efi_alignment_py, + args : exe.full_path(), + suite : 'efi') +endforeach + +alias_target('systemd-boot', boot_targets) diff --git a/src/boot/efi/part-discovery.c b/src/boot/part-discovery.c similarity index 100% rename from src/boot/efi/part-discovery.c rename to src/boot/part-discovery.c diff --git a/src/boot/efi/part-discovery.h b/src/boot/part-discovery.h similarity index 100% rename from src/boot/efi/part-discovery.h rename to src/boot/part-discovery.h diff --git a/src/boot/efi/pe.c b/src/boot/pe.c similarity index 100% rename from src/boot/efi/pe.c rename to src/boot/pe.c diff --git a/src/boot/efi/pe.h b/src/boot/pe.h similarity index 100% rename from src/boot/efi/pe.h rename to src/boot/pe.h diff --git a/src/boot/efi/proto/block-io.h b/src/boot/proto/block-io.h similarity index 100% rename from src/boot/efi/proto/block-io.h rename to src/boot/proto/block-io.h diff --git a/src/boot/efi/proto/cc-measurement.h b/src/boot/proto/cc-measurement.h similarity index 100% rename from src/boot/efi/proto/cc-measurement.h rename to src/boot/proto/cc-measurement.h diff --git a/src/boot/efi/proto/console-control.h b/src/boot/proto/console-control.h similarity index 100% rename from src/boot/efi/proto/console-control.h rename to src/boot/proto/console-control.h diff --git a/src/boot/efi/proto/device-path.h b/src/boot/proto/device-path.h similarity index 100% rename from src/boot/efi/proto/device-path.h rename to src/boot/proto/device-path.h diff --git a/src/boot/efi/proto/dt-fixup.h b/src/boot/proto/dt-fixup.h similarity index 100% rename from src/boot/efi/proto/dt-fixup.h rename to src/boot/proto/dt-fixup.h diff --git a/src/boot/efi/proto/file-io.h b/src/boot/proto/file-io.h similarity index 100% rename from src/boot/efi/proto/file-io.h rename to src/boot/proto/file-io.h diff --git a/src/boot/efi/proto/graphics-output.h b/src/boot/proto/graphics-output.h similarity index 100% rename from src/boot/efi/proto/graphics-output.h rename to src/boot/proto/graphics-output.h diff --git a/src/boot/efi/proto/load-file.h b/src/boot/proto/load-file.h similarity index 100% rename from src/boot/efi/proto/load-file.h rename to src/boot/proto/load-file.h diff --git a/src/boot/efi/proto/loaded-image.h b/src/boot/proto/loaded-image.h similarity index 100% rename from src/boot/efi/proto/loaded-image.h rename to src/boot/proto/loaded-image.h diff --git a/src/boot/efi/proto/rng.h b/src/boot/proto/rng.h similarity index 100% rename from src/boot/efi/proto/rng.h rename to src/boot/proto/rng.h diff --git a/src/boot/efi/proto/security-arch.h b/src/boot/proto/security-arch.h similarity index 100% rename from src/boot/efi/proto/security-arch.h rename to src/boot/proto/security-arch.h diff --git a/src/boot/efi/proto/shell-parameters.h b/src/boot/proto/shell-parameters.h similarity index 100% rename from src/boot/efi/proto/shell-parameters.h rename to src/boot/proto/shell-parameters.h diff --git a/src/boot/efi/proto/simple-text-io.h b/src/boot/proto/simple-text-io.h similarity index 100% rename from src/boot/efi/proto/simple-text-io.h rename to src/boot/proto/simple-text-io.h diff --git a/src/boot/efi/proto/tcg.h b/src/boot/proto/tcg.h similarity index 100% rename from src/boot/efi/proto/tcg.h rename to src/boot/proto/tcg.h diff --git a/src/boot/efi/random-seed.c b/src/boot/random-seed.c similarity index 100% rename from src/boot/efi/random-seed.c rename to src/boot/random-seed.c diff --git a/src/boot/efi/random-seed.h b/src/boot/random-seed.h similarity index 100% rename from src/boot/efi/random-seed.h rename to src/boot/random-seed.h diff --git a/src/boot/efi/secure-boot.c b/src/boot/secure-boot.c similarity index 100% rename from src/boot/efi/secure-boot.c rename to src/boot/secure-boot.c diff --git a/src/boot/efi/secure-boot.h b/src/boot/secure-boot.h similarity index 100% rename from src/boot/efi/secure-boot.h rename to src/boot/secure-boot.h diff --git a/src/boot/efi/shim.c b/src/boot/shim.c similarity index 100% rename from src/boot/efi/shim.c rename to src/boot/shim.c diff --git a/src/boot/efi/shim.h b/src/boot/shim.h similarity index 100% rename from src/boot/efi/shim.h rename to src/boot/shim.h diff --git a/src/boot/efi/smbios.c b/src/boot/smbios.c similarity index 100% rename from src/boot/efi/smbios.c rename to src/boot/smbios.c diff --git a/src/boot/efi/smbios.h b/src/boot/smbios.h similarity index 100% rename from src/boot/efi/smbios.h rename to src/boot/smbios.h diff --git a/src/boot/efi/splash.c b/src/boot/splash.c similarity index 100% rename from src/boot/efi/splash.c rename to src/boot/splash.c diff --git a/src/boot/efi/splash.h b/src/boot/splash.h similarity index 100% rename from src/boot/efi/splash.h rename to src/boot/splash.h diff --git a/src/boot/efi/stub.c b/src/boot/stub.c similarity index 100% rename from src/boot/efi/stub.c rename to src/boot/stub.c diff --git a/src/boot/efi/test-bcd.c b/src/boot/test-bcd.c similarity index 100% rename from src/boot/efi/test-bcd.c rename to src/boot/test-bcd.c diff --git a/src/boot/efi/test-efi-string.c b/src/boot/test-efi-string.c similarity index 100% rename from src/boot/efi/test-efi-string.c rename to src/boot/test-efi-string.c diff --git a/src/boot/efi/ticks.c b/src/boot/ticks.c similarity index 100% rename from src/boot/efi/ticks.c rename to src/boot/ticks.c diff --git a/src/boot/efi/ticks.h b/src/boot/ticks.h similarity index 100% rename from src/boot/efi/ticks.h rename to src/boot/ticks.h diff --git a/src/boot/efi/ubsan.c b/src/boot/ubsan.c similarity index 100% rename from src/boot/efi/ubsan.c rename to src/boot/ubsan.c diff --git a/src/boot/efi/util.c b/src/boot/util.c similarity index 100% rename from src/boot/efi/util.c rename to src/boot/util.c diff --git a/src/boot/efi/util.h b/src/boot/util.h similarity index 100% rename from src/boot/efi/util.h rename to src/boot/util.h diff --git a/src/boot/efi/vmm.c b/src/boot/vmm.c similarity index 100% rename from src/boot/efi/vmm.c rename to src/boot/vmm.c diff --git a/src/boot/efi/vmm.h b/src/boot/vmm.h similarity index 100% rename from src/boot/efi/vmm.h rename to src/boot/vmm.h diff --git a/src/boot/bootctl-install.c b/src/bootctl/bootctl-install.c similarity index 100% rename from src/boot/bootctl-install.c rename to src/bootctl/bootctl-install.c diff --git a/src/boot/bootctl-install.h b/src/bootctl/bootctl-install.h similarity index 100% rename from src/boot/bootctl-install.h rename to src/bootctl/bootctl-install.h diff --git a/src/boot/bootctl-random-seed.c b/src/bootctl/bootctl-random-seed.c similarity index 100% rename from src/boot/bootctl-random-seed.c rename to src/bootctl/bootctl-random-seed.c diff --git a/src/boot/bootctl-random-seed.h b/src/bootctl/bootctl-random-seed.h similarity index 100% rename from src/boot/bootctl-random-seed.h rename to src/bootctl/bootctl-random-seed.h diff --git a/src/boot/bootctl-reboot-to-firmware.c b/src/bootctl/bootctl-reboot-to-firmware.c similarity index 100% rename from src/boot/bootctl-reboot-to-firmware.c rename to src/bootctl/bootctl-reboot-to-firmware.c diff --git a/src/boot/bootctl-reboot-to-firmware.h b/src/bootctl/bootctl-reboot-to-firmware.h similarity index 100% rename from src/boot/bootctl-reboot-to-firmware.h rename to src/bootctl/bootctl-reboot-to-firmware.h diff --git a/src/boot/bootctl-set-efivar.c b/src/bootctl/bootctl-set-efivar.c similarity index 100% rename from src/boot/bootctl-set-efivar.c rename to src/bootctl/bootctl-set-efivar.c diff --git a/src/boot/bootctl-set-efivar.h b/src/bootctl/bootctl-set-efivar.h similarity index 100% rename from src/boot/bootctl-set-efivar.h rename to src/bootctl/bootctl-set-efivar.h diff --git a/src/boot/bootctl-status.c b/src/bootctl/bootctl-status.c similarity index 100% rename from src/boot/bootctl-status.c rename to src/bootctl/bootctl-status.c diff --git a/src/boot/bootctl-status.h b/src/bootctl/bootctl-status.h similarity index 100% rename from src/boot/bootctl-status.h rename to src/bootctl/bootctl-status.h diff --git a/src/boot/bootctl-systemd-efi-options.c b/src/bootctl/bootctl-systemd-efi-options.c similarity index 100% rename from src/boot/bootctl-systemd-efi-options.c rename to src/bootctl/bootctl-systemd-efi-options.c diff --git a/src/boot/bootctl-systemd-efi-options.h b/src/bootctl/bootctl-systemd-efi-options.h similarity index 100% rename from src/boot/bootctl-systemd-efi-options.h rename to src/bootctl/bootctl-systemd-efi-options.h diff --git a/src/boot/bootctl-uki.c b/src/bootctl/bootctl-uki.c similarity index 100% rename from src/boot/bootctl-uki.c rename to src/bootctl/bootctl-uki.c diff --git a/src/boot/bootctl-uki.h b/src/bootctl/bootctl-uki.h similarity index 100% rename from src/boot/bootctl-uki.h rename to src/bootctl/bootctl-uki.h diff --git a/src/boot/bootctl-util.c b/src/bootctl/bootctl-util.c similarity index 100% rename from src/boot/bootctl-util.c rename to src/bootctl/bootctl-util.c diff --git a/src/boot/bootctl-util.h b/src/bootctl/bootctl-util.h similarity index 100% rename from src/boot/bootctl-util.h rename to src/bootctl/bootctl-util.h diff --git a/src/boot/bootctl.c b/src/bootctl/bootctl.c similarity index 100% rename from src/boot/bootctl.c rename to src/bootctl/bootctl.c diff --git a/src/boot/bootctl.h b/src/bootctl/bootctl.h similarity index 100% rename from src/boot/bootctl.h rename to src/bootctl/bootctl.h diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build new file mode 100644 index 00000000000..bcd12a2f612 --- /dev/null +++ b/src/bootctl/meson.build @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +bootctl_sources = files( + 'bootctl-install.c', + 'bootctl-random-seed.c', + 'bootctl-reboot-to-firmware.c', + 'bootctl-set-efivar.c', + 'bootctl-status.c', + 'bootctl-systemd-efi-options.c', + 'bootctl-uki.c', + 'bootctl-util.c', + 'bootctl.c', +) + +executables += [ + executable_template + { + 'name' : 'bootctl', + 'public' : true, + 'conditions' : [ + 'HAVE_BLKID', + ], + 'sources' : bootctl_sources, + 'link_with' : boot_link_with, + 'dependencies' : [libblkid, libopenssl], + }, +] diff --git a/src/measure/measure.c b/src/measure/measure.c new file mode 100644 index 00000000000..eacf90f08c0 --- /dev/null +++ b/src/measure/measure.c @@ -0,0 +1,1171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-json.h" + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "build.h" +#include "efi-loader.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "main-func.h" +#include "memstream-util.h" +#include "openssl-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "pe-binary.h" +#include "pretty-print.h" +#include "sha256.h" +#include "strv.h" +#include "terminal-util.h" +#include "tpm2-pcr.h" +#include "tpm2-util.h" +#include "uki.h" +#include "verbs.h" + +/* Tool for pre-calculating expected TPM PCR values based on measured resources. This is intended to be used + * to pre-calculate suitable values for PCR 11, the way sd-stub measures into it. */ + +static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; +static char **arg_banks = NULL; +static char *arg_tpm2_device = NULL; +static char *arg_private_key = NULL; +static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; +static char *arg_private_key_source = NULL; +static char *arg_public_key = NULL; +static char *arg_certificate = NULL; +static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_OFF; +static PagerFlags arg_pager_flags = 0; +static bool arg_current = false; +static char **arg_phase = NULL; +static char *arg_append = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); +STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); +STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_append, freep); + +static void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) { + for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) + free((*sections)[c]); +} + +STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); + +static int help(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-measure", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n" + "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" + "\n%3$sCommands:%4$s\n" + " status Show current PCR values\n" + " calculate Calculate expected PCR values\n" + " sign Calculate and sign expected PCR values\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" + " --no-pager Do not pipe output into a pager\n" + " -c --current Use current PCR values\n" + " --phase=PHASE Specify a boot phase to sign for\n" + " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" + " --tpm2-device=PATH Use specified TPM2 device\n" + " --private-key=KEY Private key (PEM) to sign with\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use KEY for --private-key=. Allows\n" + " an OpenSSL engine/provider to be used for signing\n" + " --public-key=KEY Public key (PEM) to validate against\n" + " --certificate=PATH PEM certificate to use when signing with a URI\n" + " --json=MODE Output as JSON\n" + " -j Same as --json=pretty on tty, --json=short otherwise\n" + " --append=PATH Load specified JSON signature, and append new signature to it\n" + "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" + " --linux=PATH Path to Linux kernel image file %7$s .linux\n" + " --osrel=PATH Path to os-release file %7$s .osrel\n" + " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" + " --initrd=PATH Path to initrd image file %7$s .initrd\n" + " --ucode=PATH Path to microcode image file %7$s .ucode\n" + " --splash=PATH Path to splash bitmap file %7$s .splash\n" + " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" + " --uname=PATH Path to 'uname -r' file %7$s .uname\n" + " --sbat=PATH Path to SBAT file %7$s .sbat\n" + " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" + " --profile=PATH Path to profile file %7$s .profile\n" + " --hwids=PATH Path to HWIDs file %7$s .hwids\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal(), + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT)); + + return 0; +} + +static char *normalize_phase(const char *s) { + _cleanup_strv_free_ char **l = NULL; + + /* Let's normalize phase expressions. We split the series of colon-separated words up, then remove + * all empty ones, and glue them back together again. In other words we remove duplicate ":", as well + * as leading and trailing ones. */ + + l = strv_split(s, ":"); /* Split series of words */ + if (!l) + return NULL; + + /* Remove all empty words and glue things back together */ + return strv_join(strv_remove(l, ""), ":"); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + _ARG_SECTION_FIRST, + ARG_LINUX = _ARG_SECTION_FIRST, + ARG_OSREL, + ARG_CMDLINE, + ARG_INITRD, + ARG_UCODE, + ARG_SPLASH, + ARG_DTB, + ARG_UNAME, + ARG_SBAT, + _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ + ARG_PCRPKEY, + ARG_PROFILE, + ARG_HWIDS, + _ARG_SECTION_LAST, + ARG_DTBAUTO = _ARG_SECTION_LAST, + ARG_BANK, + ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, + ARG_PUBLIC_KEY, + ARG_CERTIFICATE, + ARG_TPM2_DEVICE, + ARG_JSON, + ARG_PHASE, + ARG_APPEND, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "version", no_argument, NULL, ARG_VERSION }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "osrel", required_argument, NULL, ARG_OSREL }, + { "cmdline", required_argument, NULL, ARG_CMDLINE }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "ucode", required_argument, NULL, ARG_UCODE }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, + { "uname", required_argument, NULL, ARG_UNAME }, + { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "profile", required_argument, NULL, ARG_PROFILE }, + { "hwids", required_argument, NULL, ARG_HWIDS }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, + { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, + { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "json", required_argument, NULL, ARG_JSON }, + { "phase", required_argument, NULL, ARG_PHASE }, + { "append", required_argument, NULL, ARG_APPEND }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + /* Make sure the arguments list and the section list, stays in sync */ + assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + + while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(0, NULL, NULL); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { + UnifiedSection section = c - _ARG_SECTION_FIRST; + + r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); + if (r < 0) + return r; + break; + } + + case 'c': + arg_current = true; + break; + + case ARG_BANK: { + const EVP_MD *implementation; + + implementation = EVP_get_digestbyname(optarg); + if (!implementation) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); + + if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + return log_oom(); + + break; + } + + case ARG_PRIVATE_KEY: + r = free_and_strdup_warn(&arg_private_key, optarg); + if (r < 0) + return r; + + break; + + case ARG_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + optarg, + &arg_private_key_source, + &arg_private_key_source_type); + if (r < 0) + return r; + + break; + + case ARG_PUBLIC_KEY: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); + if (r < 0) + return r; + + break; + + case ARG_CERTIFICATE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + if (r < 0) + return r; + + break; + + case ARG_TPM2_DEVICE: { + _cleanup_free_ char *device = NULL; + + if (streq(optarg, "list")) + return tpm2_list_devices(); + + if (!streq(optarg, "auto")) { + device = strdup(optarg); + if (!device) + return log_oom(); + } + + free_and_replace(arg_tpm2_device, device); + break; + } + + case 'j': + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + case ARG_PHASE: { + char *n; + + n = normalize_phase(optarg); + if (!n) + return log_oom(); + + r = strv_consume(&arg_phase, TAKE_PTR(n)); + if (r < 0) + return r; + + break; + } + + case ARG_APPEND: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); + if (r < 0) + return r; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (arg_public_key && arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --public-key= and --certificate= specified, refusing."); + + if (arg_private_key_source && !arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + + if (strv_isempty(arg_banks)) { + /* If no banks are specifically selected, pick all known banks */ + arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); + if (!arg_banks) + return log_oom(); + } + + strv_sort_uniq(arg_banks); + + if (arg_current) + for (UnifiedSection us = 0; us < _UNIFIED_SECTION_MAX; us++) + if (arg_sections[us]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --current switch cannot be used in combination with --linux= and related switches."); + + if (strv_isempty(arg_phase)) { + /* If no phases are specifically selected, pick everything from the beginning of the initrd + * to the beginning of shutdown. */ + if (strv_extend_many(&arg_phase, + "enter-initrd", + "enter-initrd:leave-initrd", + "enter-initrd:leave-initrd:sysinit", + "enter-initrd:leave-initrd:sysinit:ready") < 0) + return log_oom(); + } else + strv_sort_uniq(arg_phase); + + _cleanup_free_ char *j = NULL; + j = strv_join(arg_phase, ", "); + if (!j) + return log_oom(); + + log_debug("Measuring boot phases: %s", j); + return 1; +} + +/* The PCR 11 state for one specific bank */ +typedef struct PcrState { + char *bank; + const EVP_MD *md; + void *value; + size_t value_size; + void *saved_value; /* A copy of the original value we calculated, used by pcr_states_save()/pcr_states_restore() to come later back to */ +} PcrState; + +static void pcr_state_free_all(PcrState **pcr_state) { + assert(pcr_state); + + if (!*pcr_state) + return; + + for (size_t i = 0; (*pcr_state)[i].value; i++) { + free((*pcr_state)[i].bank); + free((*pcr_state)[i].value); + free((*pcr_state)[i].saved_value); + } + + *pcr_state = mfree(*pcr_state); +} + +static void evp_md_ctx_free_all(EVP_MD_CTX **md[]) { + assert(md); + + if (!*md) + return; + + for (size_t i = 0; (*md)[i]; i++) + EVP_MD_CTX_free((*md)[i]); + + *md = mfree(*md); +} + +static int pcr_state_extend(PcrState *pcr_state, const void *data, size_t sz) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mc = NULL; + unsigned value_size; + + assert(pcr_state); + assert(data || sz == 0); + assert(pcr_state->md); + assert(pcr_state->value); + assert(pcr_state->value_size > 0); + + /* Extends a (virtual) PCR by the given data */ + + mc = EVP_MD_CTX_new(); + if (!mc) + return log_oom(); + + if (EVP_DigestInit_ex(mc, pcr_state->md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", pcr_state->bank); + + /* First thing we do, is hash the old PCR value */ + if (EVP_DigestUpdate(mc, pcr_state->value, pcr_state->value_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + /* Then, we hash the new data */ + if (EVP_DigestUpdate(mc, data, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + if (EVP_DigestFinal_ex(mc, pcr_state->value, &value_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); + + assert(value_size == pcr_state->value_size); + return 0; +} + +#define BUFFER_SIZE (16U * 1024U) + +static int measure_kernel(PcrState *pcr_states, size_t n) { + _cleanup_free_ void *buffer = NULL; + int r; + + assert(n > 0); + assert(pcr_states); + + /* Virtually measures the components of a unified kernel image into PCR 11 */ + + if (arg_current) { + /* Shortcut things, if we should just use the current PCR value */ + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_free_ void *v = NULL; + size_t sz; + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%i", pcr_states[i].bank, TPM2_PCR_KERNEL_BOOT) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT && access("/sys/class/tpm/tpm0/", F_OK) >= 0) + return log_error_errno(r, "TPM device exists, but cannot open '%s'; either the kernel is too old, or selected PCR bank is not supported: %m", p); + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), &v, &sz); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + assert(pcr_states[i].value_size == sz); + memcpy(pcr_states[i].value, v, sz); + } + + return 0; + } + + buffer = malloc(BUFFER_SIZE); + if (!buffer) + return log_oom(); + + for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) { + _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL; + _cleanup_close_ int fd = -EBADF; + uint64_t m = 0; + + if (!arg_sections[c]) + continue; + + fd = open(arg_sections[c], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", arg_sections[c]); + + /* Allocate one message digest context per bank (NULL terminated) */ + mdctx = new0(EVP_MD_CTX*, n + 1); + if (!mdctx) + return log_oom(); + + for (size_t i = 0; i < n; i++) { + mdctx[i] = EVP_MD_CTX_new(); + if (!mdctx[i]) + return log_oom(); + + if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to initialize data %s context.", pcr_states[i].bank); + } + + for (;;) { + ssize_t sz; + + sz = read(fd, buffer, BUFFER_SIZE); + if (sz < 0) + return log_error_errno(errno, "Failed to read '%s': %m", arg_sections[c]); + if (sz == 0) /* EOF */ + break; + + for (size_t i = 0; i < n; i++) + if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + m += sz; + } + + if (c == UNIFIED_SECTION_LINUX) { + _cleanup_free_ PeHeader *pe_header = NULL; + + r = pe_load_headers(fd, /*ret_dos_header=*/ NULL, &pe_header); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel image file '%s', ignoring: %m", arg_sections[c]); + else if (m < pe_header->optional.SizeOfImage) { + memzero(buffer, BUFFER_SIZE); + + /* Our EFI stub measures VirtualSize bytes of the .linux section into PCR 11. + * Notably, VirtualSize can be larger than the section's size on disk. In + * that case the extra space is initialized with zeros, so the stub ends up + * measuring a bunch of zeros. To accommodate this, we have to measure the + * same number of zeros here. We opt to measure extra zeros here instead of + * modifying the stub to only measure the number of bytes on disk as we want + * newer ukify + systemd-measure to work with older versions of the stub and + * as of 6.12 the kernel image's VirtualSize won't be larger than its size on + * disk anymore (see https://github.com/systemd/systemd/issues/34578#issuecomment-2382459515). + */ + + while (m < pe_header->optional.SizeOfImage) { + uint64_t sz = MIN(BUFFER_SIZE, pe_header->optional.SizeOfImage - m); + + for (size_t i = 0; i < n; i++) + if (EVP_DigestUpdate(mdctx[i], buffer, sz) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run digest."); + + m += sz; + } + } + } + + fd = safe_close(fd); + + if (m == 0) /* We skip over empty files, the stub does so too */ + continue; + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ void *data_hash = NULL; + unsigned data_hash_size; + + data_hash = malloc(pcr_states[i].value_size); + if (!data_hash) + return log_oom(); + + /* Measure name of section */ + if (EVP_Digest(unified_sections[c], strlen(unified_sections[c]) + 1, data_hash, &data_hash_size, pcr_states[i].md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash section name with %s.", pcr_states[i].bank); + + assert(data_hash_size == (unsigned) pcr_states[i].value_size); + + r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); + if (r < 0) + return r; + + /* Retrieve hash of data and measure it */ + if (EVP_DigestFinal_ex(mdctx[i], data_hash, &data_hash_size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); + + assert(data_hash_size == (unsigned) pcr_states[i].value_size); + + r = pcr_state_extend(pcr_states + i, data_hash, data_hash_size); + if (r < 0) + return r; + } + } + + return 0; +} + +static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(pcr_states); + assert(n > 0); + + /* Measure a phase string into PCR 11. This splits up the "phase" expression at colons, and then + * virtually extends each specified word into PCR 11, to model how during boot we measure a series of + * words into PCR 11, one for each phase. */ + + l = strv_split(phase, ":"); + if (!l) + return log_oom(); + + STRV_FOREACH(word, l) { + size_t wl; + + if (isempty(*word)) + continue; + + wl = strlen(*word); + + for (size_t i = 0; i < n; i++) { /* For each bank */ + _cleanup_free_ void *b = NULL; + int bsz; + + bsz = EVP_MD_size(pcr_states[i].md); + assert(bsz > 0); + + b = malloc(bsz); + if (!b) + return log_oom(); + + /* First hash the word itself */ + if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word); + + /* And then extend the PCR with the resulting hash */ + r = pcr_state_extend(pcr_states + i, b, bsz); + if (r < 0) + return r; + } + } + + return 0; +} + +static int pcr_states_allocate(PcrState **ret) { + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + size_t n = 0; + + pcr_states = new0(PcrState, strv_length(arg_banks) + 1); + if (!pcr_states) + return log_oom(); + + /* Allocate a PCR state structure, one for each bank */ + STRV_FOREACH(d, arg_banks) { + const EVP_MD *implementation; + _cleanup_free_ void *v = NULL; + _cleanup_free_ char *b = NULL; + int sz; + + assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ + + b = strdup(EVP_MD_name(implementation)); + if (!b) + return log_oom(); + + sz = EVP_MD_size(implementation); + if (sz <= 0 || sz >= INT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected digest size: %i", sz); + + v = malloc0(sz); /* initial PCR state is all zeroes */ + if (!v) + return log_oom(); + + pcr_states[n++] = (struct PcrState) { + .bank = ascii_strlower(TAKE_PTR(b)), + .md = implementation, + .value = TAKE_PTR(v), + .value_size = sz, + }; + } + + *ret = TAKE_PTR(pcr_states); + return (int) n; +} + +static int pcr_states_save(PcrState *pcr_states, size_t n) { + assert(pcr_states); + assert(n > 0); + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ void *saved = NULL; + + if (!pcr_states[i].value) + continue; + + saved = memdup(pcr_states[i].value, pcr_states[i].value_size); + if (!saved) + return log_oom(); + + free_and_replace(pcr_states[i].saved_value, saved); + } + + return 0; +} + +static void pcr_states_restore(PcrState *pcr_states, size_t n) { + assert(pcr_states); + assert(n > 0); + + for (size_t i = 0; i < n; i++) { + + assert(pcr_states[i].value); + assert(pcr_states[i].saved_value); + + memcpy(pcr_states[i].value, pcr_states[i].saved_value, pcr_states[i].value_size); + } +} + +static int verb_calculate(int argc, char *argv[], void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + int r; + + if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Either --linux= or --current must be specified, refusing."); + if (arg_append) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --append= switch is only supported for 'sign', not 'calculate'."); + + assert(!strv_isempty(arg_banks)); + assert(!strv_isempty(arg_phase)); + + r = pcr_states_allocate(&pcr_states); + if (r < 0) + return r; + + size_t n = r; + + r = measure_kernel(pcr_states, n); + if (r < 0) + return r; + + /* Save the current state, so that we later can restore to it. This way we can measure the PCR values + * for multiple different boot phases without heaving to start from zero each time */ + r = pcr_states_save(pcr_states, n); + if (r < 0) + return r; + + STRV_FOREACH(phase, arg_phase) { + + r = measure_phase(pcr_states, n, *phase); + if (r < 0) + return r; + + for (size_t i = 0; i < n; i++) { + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *hd = NULL; + + if (i == 0) { + fflush(stdout); + fprintf(stderr, "%s# PCR[%i] Phase <%s>%s\n", + ansi_grey(), + TPM2_PCR_KERNEL_BOOT, + isempty(*phase) ? ":" : *phase, + ansi_normal()); + fflush(stderr); + } + + hd = hexmem(pcr_states[i].value, pcr_states[i].value_size); + if (!hd) + return log_oom(); + + printf("%i:%s=%s\n", TPM2_PCR_KERNEL_BOOT, pcr_states[i].bank, hd); + } else { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + + array = sd_json_variant_ref(sd_json_variant_by_key(w, pcr_states[i].bank)); + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_CONDITION(!isempty(*phase), "phase", SD_JSON_BUILD_STRING(*phase)), + SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_INTEGER(TPM2_PCR_KERNEL_BOOT)), + SD_JSON_BUILD_PAIR("hash", SD_JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))); + if (r < 0) + return log_error_errno(r, "Failed to append JSON object to array: %m"); + + r = sd_json_variant_set_field(&w, pcr_states[i].bank, array); + if (r < 0) + return log_error_errno(r, "Failed to add bank info to object: %m"); + } + } + + /* Return to the original kernel measurement for the next phase calculation */ + pcr_states_restore(pcr_states, n); + } + + if (sd_json_format_enabled(arg_json_format_flags)) { + + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(w, arg_json_format_flags, stdout, NULL); + } + + return 0; +} + +static int verb_sign(int argc, char *argv[], void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; + _cleanup_(X509_freep) X509 *certificate = NULL; + size_t n; + int r; + + if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Either --linux= or --current must be specified, refusing."); + + if (!arg_private_key) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No private key specified, use --private-key=."); + + assert(!strv_isempty(arg_banks)); + assert(!strv_isempty(arg_phase)); + + if (arg_append) { + r = sd_json_parse_file(NULL, arg_append, 0, &v, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s': %m", arg_append); + + if (!sd_json_variant_is_object(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "File '%s' is not a valid JSON object, refusing.", arg_append); + } + + /* When signing we only support JSON output */ + arg_json_format_flags &= ~SD_JSON_FORMAT_OFF; + + /* This must be done before openssl_load_private_key() otherwise it will get stuck */ + if (arg_certificate) { + r = openssl_load_x509_certificate(arg_certificate, &certificate); + if (r < 0) + return log_error_errno(r, "Failed to load X.509 certificate from %s: %m", arg_certificate); + } + + if (arg_private_key) { + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &arg_private_key); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + } + + r = openssl_load_private_key( + arg_private_key_source_type, + arg_private_key_source, + arg_private_key, + &(AskPasswordRequest) { + .id = "measure-private-key-pin", + .keyring = arg_private_key, + .credential = "measure.private-key-pin", + }, + &privkey, + &ui); + if (r < 0) + return log_error_errno(r, "Failed to load private key from %s: %m", arg_private_key); + } + + if (arg_public_key) { + _cleanup_fclose_ FILE *pubkeyf = NULL; + + pubkeyf = fopen(arg_public_key, "re"); + if (!pubkeyf) + return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key); + + pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); + if (!pubkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); + } else if (certificate) { + pubkey = X509_get_pubkey(certificate); + if (!pubkey) + return log_error_errno( + SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from certificate %s.", + arg_certificate); + } else { + _cleanup_(memstream_done) MemStream m = {}; + FILE *tf; + + /* No public key was specified, let's derive it automatically, if we can */ + + tf = memstream_init(&m); + if (!tf) + return log_oom(); + + if (i2d_PUBKEY_fp(tf, privkey) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from private key file '%s'.", arg_private_key); + + fflush(tf); + rewind(tf); + + if (!d2i_PUBKEY_fp(tf, &pubkey)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to parse extracted public key of private key file '%s'.", arg_private_key); + } + + r = pcr_states_allocate(&pcr_states); + if (r < 0) + return r; + + n = (size_t) r; + + r = measure_kernel(pcr_states, n); + if (r < 0) + return r; + + r = pcr_states_save(pcr_states, n); + if (r < 0) + return r; + + STRV_FOREACH(phase, arg_phase) { + + r = measure_phase(pcr_states, n, *phase); + if (r < 0) + return r; + + for (size_t i = 0; i < n; i++) { + PcrState *p = pcr_states + i; + + int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); + if (tpmalg < 0) + return log_error_errno(tpmalg, "Unsupported PCR bank"); + + Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM2_PCR_KERNEL_BOOT, + tpmalg, + TPM2B_DIGEST_MAKE(p->value, p->value_size)); + + TPM2B_DIGEST pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); + if (r < 0) + return log_error_errno(r, "Could not calculate PolicyPCR digest: %m"); + + _cleanup_free_ void *sig = NULL; + size_t ss; + + r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss); + if (r < 0) + return log_error_errno(r, "Failed to sign PCR policy: %m"); + + _cleanup_free_ void *pubkey_fp = NULL; + size_t pubkey_fp_size = 0; + r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *a = NULL; + r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM2_PCR_KERNEL_BOOT, &a); + if (r < 0) + return log_error_errno(r, "Failed to build JSON PCR mask array: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL; + r = sd_json_buildo(&bv, + SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */ + SD_JSON_BUILD_PAIR("pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ + SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */ + SD_JSON_BUILD_PAIR("sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */ + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *av = NULL; + av = sd_json_variant_ref(sd_json_variant_by_key(v, p->bank)); + + r = sd_json_variant_append_array_nodup(&av, bv); + if (r < 0) + return log_error_errno(r, "Failed to append JSON object: %m"); + + r = sd_json_variant_set_field(&v, p->bank, av); + if (r < 0) + return log_error_errno(r, "Failed to add JSON field: %m"); + } + + /* Return to the original kernel measurement for the next phase calculation */ + pcr_states_restore(pcr_states, n); + } + + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + + return 0; +} + +static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { + _cleanup_free_ char *s = NULL; + uint32_t v; + int r; + + r = efi_get_variable_string(varname, &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); + + r = safe_atou32(s, &v); + if (r < 0) + return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); + + if (pcr != v) + log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" + "The measurements are likely inconsistent.", description, v, pcr); + + return 0; +} + +static int validate_stub(void) { + uint64_t features; + bool found = false; + int r; + + if (!tpm2_is_fully_supported()) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); + + r = efi_stub_get_features(&features); + if (r < 0) + return log_error_errno(r, "Unable to get stub features: %m"); + + if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) + log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" + "The PCR measurements seen are unlikely to be valid."); + + r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) + return log_oom(); + + if (access(p, F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); + } else + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); + + return 0; +} + +static int verb_status(int argc, char *argv[], void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = validate_stub(); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; + _cleanup_free_ void *h = NULL; + size_t l; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *f = NULL; + + f = hexmem(h, l); + if (!h) + return log_oom(); + + if (bank == arg_banks) { + /* before the first line for each PCR, write a short descriptive text to + * stderr, and leave the primary content on stdout */ + fflush(stdout); + fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", + ansi_grey(), + (uint32_t) TPM2_PCR_KERNEL_BOOT, + tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); + + } else { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; + + r = sd_json_buildo( + &bv, + SD_JSON_BUILD_PAIR("pcr", SD_JSON_BUILD_INTEGER(TPM2_PCR_KERNEL_BOOT)), + SD_JSON_BUILD_PAIR("hash", SD_JSON_BUILD_HEX(h, l))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); + + r = sd_json_variant_append_array(&a, bv); + if (r < 0) + return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); + + r = sd_json_variant_set_field(&v, b, a); + if (r < 0) + return log_error_errno(r, "Failed to add bank info to object: %m"); + } + } + + if (sd_json_format_enabled(arg_json_format_flags)) { + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + return 0; +} + +static int measure_main(int argc, char *argv[]) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, + { "calculate", VERB_ANY, 1, 0, verb_calculate }, + { "sign", VERB_ANY, 1, 0, verb_sign }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return measure_main(argc, argv); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/measure/meson.build b/src/measure/meson.build new file mode 100644 index 00000000000..13a890170c5 --- /dev/null +++ b/src/measure/meson.build @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-measure', + 'conditions' : [ + 'HAVE_BLKID', + 'HAVE_OPENSSL', + 'HAVE_TPM2', + ], + 'sources' : files('measure.c'), + 'dependencies' : libopenssl, + }, +] diff --git a/src/boot/authenticode.h b/src/sbsign/authenticode.h similarity index 100% rename from src/boot/authenticode.h rename to src/sbsign/authenticode.h diff --git a/src/sbsign/meson.build b/src/sbsign/meson.build new file mode 100644 index 00000000000..b6e0dbcde9c --- /dev/null +++ b/src/sbsign/meson.build @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-sbsign', + 'conditions' : [ + 'HAVE_OPENSSL', + ], + 'sources' : files('sbsign.c'), + 'dependencies' : libopenssl, + }, +] diff --git a/src/boot/sbsign.c b/src/sbsign/sbsign.c similarity index 100% rename from src/boot/sbsign.c rename to src/sbsign/sbsign.c