diff --git a/TODO b/TODO index 133ae1bf7f1..8f4c231ab49 100644 --- a/TODO +++ b/TODO @@ -119,6 +119,14 @@ Features: on other disks. Always boot into them via NextBoot EFI variable, to not affect PCR values. +* systemd-measure tool: + - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 + - sign pre-calculated hashes in a way compatible with TPM2 PCR hash signature + policies, in a way they can be included in unified PE kernel images, and + made available to userspace. There, this should be consumed by + systemd-cryptsetup to implement PCR signature based TPM volume unlock + policies. + * in sd-boot: load EFI drivers from a new PE section. That way, one can have a "supercharged" sd-boot binary, that could carry ext4 drivers built-in. @@ -385,12 +393,6 @@ Features: case the same wd is reused multiple times before we start processing IN_IGNORED again) -* sd-stub: set efi var indicating stub features, i.e. whether they pick up - creds, sysexts and so on. similar to existing variable of sd-boot - -* sd-stub: set efi vars declaring TPM PCRs we measured creds/cmdline + sysext - into (even if we hardcode them) - * systemd-fstab-generator: support addition mount specifications via kernel cmdline. Usecase: invoke a VM, and mount a host homedir into it via virtio-fs. @@ -413,10 +415,6 @@ Features: - sd-stub: automatically pick up microcode from ESP (/loader/microcode/*) and synthesize initrd from it, and measure it. Signing is not necessary, as microcode does that on its own. Pass as first initrd to kernel. - - sd-stub should measure the kernel/initrd/… into a separate PCR, so that we - have one PCR we can bind the encrypted creds to that is not effected by - anything else but what we drop in via kernel-install, i.e. by earlier EFI - code running (i.e. like PCR 4) * Add a new service type very similar to Type=notify, that goes one step further and extends the protocol to cover reloads. Specifically, SIGHUP will @@ -660,7 +658,7 @@ Features: dep in the base OS image) * sysext: automatically activate sysext images dropped in via new sd-stub - sysext pickup logic. + sysext pickup logic. (must insist on verity + signature on those though) * add concept for "exitrd" as inverse of "initrd", that we can transition to at shutdown, and has similar security semantics. This should then take the place @@ -708,9 +706,9 @@ Features: what must be read-only, what requires encryption, and what requires authentication. -* in uefi stub: query firmware regarding which PCRs are being used, store that - in EFI var. then use this when enrolling TPM2 in cryptsetup to verify that - the selected PCRs actually are used by firmware. +* in uefi stub: query firmware regarding which PCR banks are being used, store + that in EFI var. then use this when enrolling TPM2 in cryptsetup to verify + that the selected PCRs actually are used by firmware. * rework recursive read-only remount to use new mount API @@ -1606,7 +1604,6 @@ Features: - show whether UEFI audit mode is available - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host - - make it operate on loopback files, dissecting enough to find ESP to operate on - bootspec: properly support boot attempt counters when parsing entry file names * kernel-install: diff --git a/man/rules/meson.build b/man/rules/meson.build index 277ab5e5933..4f3fe0da7c7 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -953,6 +953,7 @@ manpages = [ 'systemd-makefs', 'systemd-mkswap@.service'], ''], + ['systemd-measure', '1', [], 'HAVE_GNU_EFI'], ['systemd-modules-load.service', '8', ['systemd-modules-load'], 'HAVE_KMOD'], ['systemd-mount', '1', ['systemd-umount'], ''], ['systemd-network-generator.service', '8', ['systemd-network-generator'], ''], diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index 8e1b9857a72..2aa396e3004 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -296,11 +296,21 @@ The IMA project measures its runtime state into this PCR. + + 11 + systemd-stub7 measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initial RAM disk (initrd). + + 12 systemd-boot7 measures any specified kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if systemd-boot and systemd-stub are used in combination the command line might be measured twice!) + + 13 + systemd-stub7 measures any systemd-sysext8 images it loads and passed to the booted kernel into this PCR. + + 14 The shim project measures its "MOK" certificates and hashes into this PCR. diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml new file mode 100644 index 00000000000..e6b8d31a881 --- /dev/null +++ b/man/systemd-measure.xml @@ -0,0 +1,154 @@ + + + + + + + + systemd-measure + systemd + + + + systemd-measure + 1 + + + + systemd-measure + Pre-calculate expected TPM2 PCR values for booted unified kernel images + + + + + /usr/lib/systemd/systemd-measure OPTIONS + + + + + Description + + Note: this command is experimental for now. While it is likely to become a regular component of + systemd, it might still change in behaviour and interface. + + systemd-measure is a tool that may be used to pre-calculate the expected TPM2 + PCR 11 values that should be seen when a unified Linux kernel image based on + systemd-stub7 is + booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file, + kernel command line file, + os-release5 file, and + boot splash file that make up the unified kernel image, and determines the PCR values expected to be in + place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a + fashion compatible with what systemd-stub does at boot. + + + + Commands + + The following commands are understood: + + + + status + + This is the default command if none is specified. This queries the local system's + TPM2 PCR 11+12+13 values and displays them. The data is written in a similar format as the + calculate command below, and may be used to quickly compare expectation with + reality. + + + + calculate + + Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified + kernel image consisting of the components specified with , + , , , + , , see below. Only is + mandatory. + + + + + + Options + + The following options are understood: + + + + + + + + + + + When used with the calculate verb, configures the files to read + the unified kernel image components from. Each option corresponds with the equally named section in + the unified kernel PE file. The switch expects the path to the ELF kernel + file that the unified PE kernel will wrap. All switches except are + optional. Each option may be used at most once. + + + + + + Controls the PCR banks to pre-calculate the PCR values for – in case + calculate is invoked –, or the banks to show in the status + output. May be used more then once to specify multiple banks. If not specified, defaults to the four + banks sha1, sha256, sha384, + sha512. + + + + + + + + + Examples + + + Generate a unified kernel image, and calculate the expected TPM PCR 11 value + + # objcopy \ + --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \ + --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \ + --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \ + --add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \ + --add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \ + --add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \ + /usr/lib/systemd/boot/efi/linuxx64.efi.stub \ + foo.efi +# systemd-measure calculate \ + --linux=vmlinux \ + --osrel=os-release \ + --cmdline=cmdline.txt \ + --initrd=initrd.cpio \ + --splash=splash.bmp \ + --dtb=devicetree.dtb +11:sha1=d775a7b4482450ac77e03ee19bda90bd792d6ec7 +11:sha256=bc6170f9ce28eb051ab465cd62be8cf63985276766cf9faf527ffefb66f45651 +11:sha384=1cf67dff4757e61e5a73d2a21a6694d668629bbc3761747d493f7f49ad720be02fd07263e1f93061243aec599d1ee4b4 +11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da + + + + + + Exit status + + On success, 0 is returned, a non-zero failure code otherwise. + + + + See Also + + systemd1, + systemd-stub7, + objcopy1 + + + + diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml index 92a20e259ed..1e9bb5d631b 100644 --- a/man/systemd-stub.xml +++ b/man/systemd-stub.xml @@ -53,6 +53,10 @@ The ELF Linux kernel images will be looked for in the .linux PE section of the executed image. + OS release information, i.e. the + os-release5 file of + the OS the kernel belongs to, in the .osrel PE section. + The initial RAM disk (initrd) will be looked for in the .initrd PE section. @@ -76,6 +80,9 @@ If a DeviceTree is embedded in the .dtb section, it replaces an existing DeviceTree in the corresponding EFI configuration table. systemd-stub will ask the firmware via the EFI_DT_FIXUP_PROTOCOL for hardware specific fixups to the DeviceTree. + + The contents of these six PE sections are measured into TPM PCR 11, that is otherwise not + used. Thus, it can be pre-calculated without too much effort. @@ -108,7 +115,7 @@ images to the initrd. See systemd-sysext8 for details on system extension images. The generated cpio archive containing these - system extension images is measured into TPM PCR 4 (if a TPM is present). + system extension images is measured into TPM PCR 13 (if a TPM is present). Files /loader/credentials/*.cred are packed up in a cpio archive and placed in the /.extra/global_credentials/ @@ -133,10 +140,10 @@ core kernel, the embedded initrd and kernel command line (see above for a full list). Also note that the Linux kernel will measure all initrds it receives into TPM PCR 9. This means - every type of initrd will be measured twice: the initrd embedded in the kernel image will be measured to - both PCR 4 and PCR 9; the initrd synthesized from credentials will be measured to both PCR 12 and PCR 9; - the initrd synthesized from system extensions will be measured to both PCR 4 and PCR 9. Let's summarize - the OS resources and the PCRs they are measured to: + every type of initrd will be measured two or three times: the initrd embedded in the kernel image will be + measured to PCR 4, PCR 9 and PCR 11; the initrd synthesized from credentials will be measured to both PCR + 9 and PCR 12; the initrd synthesized from system extensions will be measured to both PCR 4 and PCR + 9. Let's summarize the OS resources and the PCRs they are measured to: OS Resource PCR Summary @@ -160,22 +167,22 @@ Boot splash (embedded in the unified PE binary) - 4 + 4 + 11 Core kernel code (embedded in unified PE binary) - 4 + 4 + 11 Main initrd (embedded in unified PE binary) - 4 + 9 + 4 + 9 + 11 Default kernel command line (embedded in unified PE binary) - 4 + 4 + 11 @@ -185,12 +192,12 @@ Credentials (synthesized initrd from companion files) - 12 + 9 + 9 + 12 System Extensions (synthesized initrd from companion files) - 4 + 9 + 9 + 13 @@ -239,6 +246,32 @@ bootctl1 to view this data. + + + StubPcrKernelImage + + The PCR register index the ELF kernel image/initial RAM disk image/boot + splash/devicetree database/embedded command line are measured into, formatted as decimal ASCII string + (i.e. 11). This variable is set if a measurement was successfully completed, and + remains unset otherwise. + + + + StubPcrKernelParameters + + The PCR register index the kernel command line and credentials are measured into, + formatted as decimal ASCII string (i.e. 12). This variable is set if a measurement + was successfully completed, and remains unset otherwise. + + + + StubPcrInitRDSysExts + + The PCR register index the systemd extensions for the initial RAM disk image, which + are picked up from the file system the kernel image is located on. Formatted as decimal ASCII string + (i.e. 13). This variable is set if a measurement was successfully completed, and + remains unset otherwise. + Note that some of the variables above may also be set by the boot loader. The stub will only set diff --git a/meson.build b/meson.build index 6bbb2db55d1..cf3aa2b4964 100644 --- a/meson.build +++ b/meson.build @@ -310,6 +310,8 @@ conf.set_quoted('STATUS_UNIT_FORMAT_DEFAULT_STR', status_unit_format conf.set10('FIRST_BOOT_FULL_PRESET', get_option('first-boot-full-preset')) +conf.set10('EFI_TPM_PCR_COMPAT', get_option('efi-tpm-pcr-compat')) + ##################################################################### cc = meson.get_compiler('c') @@ -2539,6 +2541,18 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 install_rpath : rootpkglibdir, install : true, install_dir : systemgeneratordir) + + if conf.get('HAVE_OPENSSL') == 1 + executable( + 'systemd-measure', + 'src/boot/measure.c', + include_directories : includes, + link_with : [libshared], + dependencies : [libopenssl], + install_rpath : rootpkglibdir, + install : true, + install_dir : rootlibexecdir) + endif endif executable( diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 7470157ecd5..f04463157a5 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -456,13 +456,21 @@ static const char *get_efi_arch(void) { return EFI_MACHINE_TYPE_NAME; } -static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { +static int enumerate_binaries( + const char *esp_path, + const char *path, + const char *prefix, + char **previous, + bool *is_first) { + _cleanup_closedir_ DIR *d = NULL; const char *p; int c = 0, r; assert(esp_path); assert(path); + assert(previous); + assert(is_first); p = prefix_roota(esp_path, path); d = opendir(p); @@ -490,10 +498,24 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char r = get_file_version(fd, &v); if (r < 0) return r; + + if (*previous) { /* let's output the previous entry now, since now we know that there will be one more, and can draw the tree glyph properly */ + printf(" %s %s%s\n", + *is_first ? "File:" : " ", + special_glyph(SPECIAL_GLYPH_TREE_BRANCH), *previous); + *is_first = false; + *previous = mfree(*previous); + } + + /* Do not output this entry immediately, but store what should be printed in a state + * variable, because we only will know the tree glyph to print (branch or final edge) once we + * read one more entry */ if (r > 0) - printf(" File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal()); + r = asprintf(previous, "/%s/%s (%s%s%s)", path, de->d_name, ansi_highlight(), v, ansi_normal()); else - printf(" File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name); + r = asprintf(previous, "/%s/%s", path, de->d_name); + if (r < 0) + return log_oom(); c++; } @@ -502,9 +524,11 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char } static int status_binaries(const char *esp_path, sd_id128_t partition) { - int r; + _cleanup_free_ char *last = NULL; + bool is_first = true; + int r, k; - printf("Available Boot Loaders on ESP:\n"); + printf("%sAvailable Boot Loaders on ESP:%s\n", ansi_underline(), ansi_normal()); if (!esp_path) { printf(" ESP: Cannot find or access mount point of ESP.\n\n"); @@ -516,42 +540,56 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition)); printf("\n"); - r = enumerate_binaries(esp_path, "EFI/systemd", NULL); - if (r < 0) - goto finish; + r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first); + if (r < 0) { + printf("\n"); + return r; + } + + k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first); + if (k < 0) { + printf("\n"); + return k; + } + + if (last) /* let's output the last entry now, since now we know that there will be no more, and can draw the tree glyph properly */ + printf(" %s %s%s\n", + is_first ? "File:" : " ", + special_glyph(SPECIAL_GLYPH_TREE_RIGHT), last); + if (r == 0 && !arg_quiet) log_info("systemd-boot not installed in ESP."); - - r = enumerate_binaries(esp_path, "EFI/BOOT", "boot"); - if (r < 0) - goto finish; - if (r == 0 && !arg_quiet) + if (k == 0 && !arg_quiet) log_info("No default/fallback boot loader installed in ESP."); - r = 0; - -finish: printf("\n"); - return r; + return 0; } -static int print_efi_option(uint16_t id, bool in_order) { +static int print_efi_option(uint16_t id, int *n_printed, bool in_order) { _cleanup_free_ char *title = NULL; _cleanup_free_ char *path = NULL; sd_id128_t partition; bool active; int r; + assert(n_printed); + r = efi_get_boot_option(id, &title, &partition, &path, &active); if (r < 0) - return r; + return log_error_errno(r, "Failed to read boot option %u: %m", id); /* print only configured entries with partition information */ - if (!path || sd_id128_is_null(partition)) + if (!path || sd_id128_is_null(partition)) { + log_debug("Ignoring boot entry %u without partition information.", id); return 0; + } efi_tilt_backslashes(path); + if (*n_printed == 0) /* Print section title before first entry */ + printf("%sBoot Loaders Listed in EFI Variables:%s\n", ansi_underline(), ansi_normal()); + printf(" Title: %s%s%s\n", ansi_highlight(), strna(title), ansi_normal()); printf(" ID: 0x%04X\n", id); printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); @@ -560,12 +598,13 @@ static int print_efi_option(uint16_t id, bool in_order) { printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path); printf("\n"); - return 0; + (*n_printed)++; + return 1; } static int status_variables(void) { _cleanup_free_ uint16_t *options = NULL, *order = NULL; - int n_options, n_order; + int n_options, n_order, n_printed = 0; n_options = efi_get_boot_options(&options); if (n_options == -ENOENT) @@ -582,9 +621,8 @@ static int status_variables(void) { return log_error_errno(n_order, "Failed to read EFI boot order: %m"); /* print entries in BootOrder first */ - printf("Boot Loaders Listed in EFI Variables:\n"); for (int i = 0; i < n_order; i++) - print_efi_option(order[i], true); + (void) print_efi_option(order[i], &n_printed, /* in_order= */ true); /* print remaining entries */ for (int i = 0; i < n_options; i++) { @@ -592,12 +630,15 @@ static int status_variables(void) { if (options[i] == order[j]) goto next_option; - print_efi_option(options[i], false); + (void) print_efi_option(options[i], &n_printed, /* in_order= */ false); next_option: continue; } + if (n_printed == 0) + printf("No boot loaders listed in EFI Variables.\n\n"); + return 0; } @@ -655,8 +696,8 @@ static int status_entries( dollar_boot_partition_uuid = esp_partition_uuid; } - printf("Boot Loader Entries:\n" - " $BOOT: %s", dollar_boot_path); + printf("%sBoot Loader Entries:%s\n" + " $BOOT: %s", ansi_underline(), ansi_normal(), dollar_boot_path); if (!sd_id128_is_null(dollar_boot_partition_uuid)) printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid)); @@ -665,7 +706,7 @@ static int status_entries( if (config->default_entry < 0) printf("%zu entries, no entry could be determined as default.\n", config->n_entries); else { - printf("Default Boot Loader Entry:\n"); + printf("%sDefault Boot Loader Entry:%s\n", ansi_underline(), ansi_normal()); r = show_boot_entry( boot_config_default_entry(config), @@ -1714,7 +1755,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { static const struct { uint64_t flag; const char *name; - } flags[] = { + } loader_flags[] = { { EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" }, { EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" }, { EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" }, @@ -1723,10 +1764,22 @@ static int verb_status(int argc, char *argv[], void *userdata) { { EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" }, { EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, { EFI_LOADER_FEATURE_LOAD_DRIVER, "Load drop-in drivers" }, + { EFI_LOADER_FEATURE_SORT_KEY, "Support Type #1 sort-key field" }, + { EFI_LOADER_FEATURE_SAVED_ENTRY, "Support @saved pseudo-entry" }, + { EFI_LOADER_FEATURE_DEVICETREE, "Support Type #1 devicetree field" }, + }; + static const struct { + uint64_t flag; + const char *name; + } stub_flags[] = { + { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, + { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, + { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; - uint64_t loader_features = 0; + uint64_t loader_features = 0, stub_features = 0; Tpm2Support s; int have; @@ -1736,6 +1789,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { read_efi_var(EFI_LOADER_VARIABLE(StubInfo), &stub); read_efi_var(EFI_LOADER_VARIABLE(LoaderImageIdentifier), &loader_path); (void) efi_loader_get_features(&loader_features); + (void) efi_stub_get_features(&stub_features); if (loader_path) efi_tilt_backslashes(loader_path); @@ -1745,7 +1799,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = log_warning_errno(k, "Failed to read EFI variable LoaderDevicePartUUID: %m"); SecureBootMode secure = efi_get_secure_boot_mode(); - printf("System:\n"); + printf("%sSystem:%s\n", ansi_underline(), ansi_normal()); printf(" Firmware: %s%s (%s)%s\n", ansi_highlight(), strna(fw_type), strna(fw_info), ansi_normal()); printf(" Firmware Arch: %s\n", get_efi_arch()); printf(" Secure Boot: %sd (%s)\n", @@ -1774,11 +1828,11 @@ static int verb_status(int argc, char *argv[], void *userdata) { } printf("\n"); - printf("Current Boot Loader:\n"); + printf("%sCurrent Boot Loader:%s\n", ansi_underline(), ansi_normal()); printf(" Product: %s%s%s\n", ansi_highlight(), strna(loader), ansi_normal()); - for (size_t i = 0; i < ELEMENTSOF(flags); i++) - print_yes_no_line(i == 0, FLAGS_SET(loader_features, flags[i].flag), flags[i].name); + for (size_t i = 0; i < ELEMENTSOF(loader_flags); i++) + print_yes_no_line(i == 0, FLAGS_SET(loader_features, loader_flags[i].flag), loader_flags[i].name); sd_id128_t bootloader_esp_uuid; bool have_bootloader_esp_uuid = efi_loader_get_device_part_uuid(&bootloader_esp_uuid) >= 0; @@ -1787,8 +1841,11 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (have_bootloader_esp_uuid && !sd_id128_equal(esp_uuid, bootloader_esp_uuid)) printf("WARNING: The boot loader reports a different ESP UUID than detected!\n"); - if (stub) + if (stub) { printf(" Stub: %s\n", stub); + for (size_t i = 0; i < ELEMENTSOF(stub_flags); i++) + print_yes_no_line(i == 0, FLAGS_SET(stub_features, stub_flags[i].flag), stub_flags[i].name); + } if (!sd_id128_is_null(loader_part_uuid)) printf(" ESP: /dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(loader_part_uuid)); @@ -1797,7 +1854,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path)); printf("\n"); - printf("Random Seed:\n"); + printf("%sRandom Seed:%s\n", ansi_underline(), ansi_normal()); have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderRandomSeed)), F_OK) >= 0; printf(" Passed to OS: %s\n", yes_no(have)); have = access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderSystemToken)), F_OK) >= 0; @@ -1816,7 +1873,9 @@ static int verb_status(int argc, char *argv[], void *userdata) { printf("\n"); } else - printf("System:\n Not booted with EFI\n\n"); + printf("%sSystem:%s\n" + "Not booted with EFI\n\n", + ansi_underline(), ansi_normal()); if (arg_esp_path) { k = status_binaries(arg_esp_path, esp_uuid); diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index ece1c11cd5e..b3f424d8ba0 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -2339,7 +2339,7 @@ static EFI_STATUS image_start( loaded_image->LoadOptionsSize = strsize16(options); /* Try to log any options to the TPM, especially to catch manually edited options */ - (void) tpm_log_load_options(options); + (void) tpm_log_load_options(options, NULL); } efivar_set_time_usec(LOADER_GUID, L"LoaderTimeExecUSec", 0); @@ -2443,6 +2443,9 @@ static void export_variables( EFI_LOADER_FEATURE_XBOOTLDR | EFI_LOADER_FEATURE_RANDOM_SEED | EFI_LOADER_FEATURE_LOAD_DRIVER | + EFI_LOADER_FEATURE_SORT_KEY | + EFI_LOADER_FEATURE_SAVED_ENTRY | + EFI_LOADER_FEATURE_DEVICETREE | 0; _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL; diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index 6905e07c851..0d41102d2d8 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -315,7 +315,8 @@ EFI_STATUS pack_cpio( UINTN n_tpm_pcr, const char16_t *tpm_description, void **ret_buffer, - UINTN *ret_buffer_size) { + UINTN *ret_buffer_size, + bool *ret_measured) { _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL; UINTN dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0; @@ -324,6 +325,7 @@ EFI_STATUS pack_cpio( _cleanup_(strv_freep) char16_t **items = NULL; _cleanup_free_ void *buffer = NULL; uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ + int measured = -1; EFI_STATUS err; assert(loaded_image); @@ -432,23 +434,40 @@ EFI_STATUS pack_cpio( return log_error_status_stall(err, L"Failed to pack cpio trailer: %r"); for (UINTN i = 0; i < n_tpm_pcr; i++) { + bool m; + + if (tpm_pcr[i] == UINT32_MAX) /* Disabled */ + continue; + err = tpm_log_event( tpm_pcr[i], POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, - tpm_description); - if (err != EFI_SUCCESS) + tpm_description, + &m); + if (err != EFI_SUCCESS) { log_error_stall(L"Unable to add initrd TPM measurement for PCR %u (%s), ignoring: %r", tpm_pcr[i], tpm_description, err); + measured = false; + continue; + } + + measured = measured < 0 ? m : (measured && m); } *ret_buffer = TAKE_PTR(buffer); *ret_buffer_size = buffer_size; + if (ret_measured) + *ret_measured = measured; + return EFI_SUCCESS; nothing: *ret_buffer = NULL; *ret_buffer_size = 0; + if (ret_measured) + *ret_measured = true; + return EFI_SUCCESS; } diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index e3d206d7e89..672a751825f 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include EFI_STATUS pack_cpio( @@ -15,4 +16,5 @@ EFI_STATUS pack_cpio( UINTN n_tpm_pcr, const char16_t *tpm_description, void **ret_buffer, - UINTN *ret_buffer_size); + UINTN *ret_buffer_size, + bool *ret_measured); diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 71085546818..9a16920787b 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -5,6 +5,7 @@ #include #include +#include "tpm-pcr.h" #include "macro-fundamental.h" #include "measure.h" #include "missing_efi.h" @@ -141,43 +142,79 @@ bool tpm_present(void) { return tcg2_interface_check() || tcg1_interface_check(); } -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) { - EFI_TCG *tpm1; +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) { EFI_TCG2 *tpm2; + EFI_STATUS err; assert(description); - /* PCR disabled */ - if (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; + } tpm2 = tcg2_interface_check(); if (tpm2) - return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + else { + EFI_TCG *tpm1; - tpm1 = tcg1_interface_check(); - if (tpm1) - return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + tpm1 = tcg1_interface_check(); + if (tpm1) + err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); + else { + /* No active TPM found, so don't return an error */ - /* No active TPM found, so don't return an error */ - return EFI_SUCCESS; + if (ret_measured) + *ret_measured = false; + + return EFI_SUCCESS; + } + } + + if (err == EFI_SUCCESS && ret_measured) + *ret_measured = true; + + return err; } -EFI_STATUS tpm_log_load_options(const char16_t *load_options) { +EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) { + _cleanup_free_ char16_t *c = NULL; + + if (description) + c = xstra_to_str(description); + + return tpm_log_event(pcrindex, buffer, buffer_size, c, ret_measured); +} + +EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) { + int measured = -1; EFI_STATUS err; /* Measures a load options string into the TPM2, i.e. the kernel command line */ for (UINTN i = 0; i < 2; i++) { uint32_t pcr = i == 0 ? TPM_PCR_INDEX_KERNEL_PARAMETERS : TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT; + bool m; - err = tpm_log_event(pcr, - POINTER_TO_PHYSICAL_ADDRESS(load_options), - strsize16(load_options), load_options); + if (pcr == UINT32_MAX) /* Skip this one, if it's invalid, so that our 'measured' return value is not corrupted by it */ + continue; + + err = tpm_log_event(pcr, POINTER_TO_PHYSICAL_ADDRESS(load_options), strsize16(load_options), load_options, &m); if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Unable to add load options (i.e. kernel command) line measurement to PCR %u: %r", pcr, err); + + measured = measured < 0 ? m : (measured && m); } + if (ret_measured) + *ret_measured = measured < 0 ? false : measured; + return EFI_SUCCESS; } diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index af1cc62cdd0..19a50f47e7d 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -5,25 +5,12 @@ #include #include -/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */ -#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U - -/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for - * some compatibility. (Remove in 2023!) */ -#if EFI_TPM_PCR_COMPAT -#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U -#else -#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX -#endif - -/* This TPM PCR is where most Linux infrastructure extends the initrd binary images into, and so do we. */ -#define TPM_PCR_INDEX_INITRD 4U - #if ENABLE_TPM bool tpm_present(void); -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description); -EFI_STATUS tpm_log_load_options(const char16_t *cmdline); +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured); +EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured); +EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured); #else @@ -31,11 +18,21 @@ static inline bool tpm_present(void) { return false; } -static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description) { +static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char16_t *description, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; return EFI_SUCCESS; } -static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline) { +static inline EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const char *description, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; + return EFI_SUCCESS; +} + +static inline EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured) { + if (ret_measured) + *ret_measured = false; return EFI_SUCCESS; } diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 83badd96cda..49ece419619 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -12,6 +12,7 @@ #include "pe.h" #include "secure-boot.h" #include "splash.h" +#include "tpm-pcr.h" #include "util.h" /* magic string to find in the binary image */ @@ -102,6 +103,13 @@ static EFI_STATUS combine_initrd( } static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { + static const uint64_t stub_features = + EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ + EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ + EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */ + EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */ + 0; + char16_t uuid[37]; assert(loaded_image); @@ -142,31 +150,15 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { efivar_set(LOADER_GUID, L"LoaderFirmwareType", s, 0); } + /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our * own data) */ (void) efivar_set(LOADER_GUID, L"StubInfo", L"systemd-stub " GIT_VERSION, 0); + + (void) efivar_set_uint64_le(LOADER_GUID, L"StubFeatures", stub_features, 0); } EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { - - enum { - SECTION_CMDLINE, - SECTION_LINUX, - SECTION_INITRD, - SECTION_SPLASH, - SECTION_DTB, - _SECTION_MAX, - }; - - static const char * const sections[_SECTION_MAX + 1] = { - [SECTION_CMDLINE] = ".cmdline", - [SECTION_LINUX] = ".linux", - [SECTION_INITRD] = ".initrd", - [SECTION_SPLASH] = ".splash", - [SECTION_DTB] = ".dtb", - NULL, - }; - UINTN cmdline_len = 0, linux_size, initrd_size, dt_size; UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0; _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL; @@ -174,10 +166,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; EFI_LOADED_IMAGE_PROTOCOL *loaded_image; - UINTN addrs[_SECTION_MAX] = {}; - UINTN szs[_SECTION_MAX] = {}; + UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; char *cmdline = NULL; _cleanup_free_ char *cmdline_owned = NULL; + int sections_measured = -1, parameters_measured = -1; + bool sysext_measured = false, m; EFI_STATUS err; InitializeLib(image, sys_table); @@ -195,83 +188,136 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { if (err != EFI_SUCCESS) return log_error_status_stall(err, L"Error getting a LoadedImageProtocol handle: %r", err); - err = pe_memory_locate_sections(loaded_image->ImageBase, sections, addrs, szs); - if (err != EFI_SUCCESS || szs[SECTION_LINUX] == 0) { + err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs); + if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; return log_error_status_stall(err, L"Unable to locate embedded .linux section: %r", err); } - /* Show splash screen as early as possible */ - graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[SECTION_SPLASH], szs[SECTION_SPLASH], NULL); + /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written + * into so far), so that we have one PCR that we can nicely write policies against because it + * contains all static data of this image, and thus can be easily be pre-calculated. */ + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + m = false; - if (szs[SECTION_CMDLINE] > 0) { - cmdline = (char *) loaded_image->ImageBase + addrs[SECTION_CMDLINE]; - cmdline_len = szs[SECTION_CMDLINE]; + if (szs[section] == 0) /* not found */ + continue; + + /* First measure the name of the section */ + (void) tpm_log_event_ascii( + TPM_PCR_INDEX_KERNEL_IMAGE, + POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]), + strsize8(unified_sections[section]), /* including NUL byte */ + unified_sections[section], + &m); + + sections_measured = sections_measured < 0 ? m : (sections_measured && m); + + /* Then measure the data of the section */ + (void) tpm_log_event_ascii( + TPM_PCR_INDEX_KERNEL_IMAGE, + POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], + szs[section], + unified_sections[section], + &m); + + sections_measured = sections_measured < 0 ? m : (sections_measured && m); } - /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace the built-in one */ - if ((!secure_boot_enabled() || cmdline_len == 0) && loaded_image->LoadOptionsSize > 0 && - *(char16_t *) loaded_image->LoadOptions > 0x1F) { - cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char); - cmdline = cmdline_owned = xmalloc(cmdline_len); + /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode + * in it which PCR was used. */ + if (sections_measured > 0) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelImage", TPM_PCR_INDEX_KERNEL_IMAGE, 0); - for (UINTN i = 0; i < cmdline_len; i++) - cmdline[i] = ((char16_t *) loaded_image->LoadOptions)[i]; + /* Show splash screen as early as possible */ + graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH], NULL); + + if (szs[UNIFIED_SECTION_CMDLINE] > 0) { + cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE]; + cmdline_len = szs[UNIFIED_SECTION_CMDLINE]; + } + + /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace + * the built-in one. We also do a superficial check whether first chararacter of passed command line + * is printable character (for compat with some Dell systems which fill in garbage?). */ + if ((!secure_boot_enabled() || cmdline_len == 0) && + loaded_image->LoadOptionsSize > 0 && + ((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) { + cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char); + cmdline = cmdline_owned = xnew(char, cmdline_len); + + for (UINTN i = 0; i < cmdline_len; i++) { + char16_t c = ((char16_t *) loaded_image->LoadOptions)[i]; + cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */ + } /* Let's measure the passed kernel command line into the TPM. Note that this possibly * duplicates what we already did in the boot menu, if that was already used. However, since * we want the boot menu to support an EFI binary, and want to this stub to be usable from * any boot menu, let's measure things anyway. */ - (void) tpm_log_load_options(loaded_image->LoadOptions); + m = false; + (void) tpm_log_load_options(loaded_image->LoadOptions, &m); + parameters_measured = m; } export_variables(loaded_image); - (void) pack_cpio(loaded_image, - NULL, - L".cred", - ".extra/credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, - /* n_tpm_pcr= */ 2, - L"Credentials initrd", - &credential_initrd, - &credential_initrd_size); + if (pack_cpio(loaded_image, + NULL, + L".cred", + ".extra/credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, + /* n_tpm_pcr= */ 2, + L"Credentials initrd", + &credential_initrd, + &credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - (void) pack_cpio(loaded_image, - L"\\loader\\credentials", - L".cred", - ".extra/global_credentials", - /* dir_mode= */ 0500, - /* access_mode= */ 0400, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, - /* n_tpm_pcr= */ 2, - L"Global credentials initrd", - &global_credential_initrd, - &global_credential_initrd_size); + if (pack_cpio(loaded_image, + L"\\loader\\credentials", + L".cred", + ".extra/global_credentials", + /* dir_mode= */ 0500, + /* access_mode= */ 0400, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_KERNEL_PARAMETERS, TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT }, + /* n_tpm_pcr= */ 2, + L"Global credentials initrd", + &global_credential_initrd, + &global_credential_initrd_size, + &m) == EFI_SUCCESS) + parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - (void) pack_cpio(loaded_image, - NULL, - L".raw", - ".extra/sysext", - /* dir_mode= */ 0555, - /* access_mode= */ 0444, - /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD }, - /* n_tpm_pcr= */ 1, - L"System extension initrd", - &sysext_initrd, - &sysext_initrd_size); + if (pack_cpio(loaded_image, + NULL, + L".raw", + ".extra/sysext", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ (uint32_t[]) { TPM_PCR_INDEX_INITRD_SYSEXTS }, + /* n_tpm_pcr= */ 1, + L"System extension initrd", + &sysext_initrd, + &sysext_initrd_size, + &m) == EFI_SUCCESS) + sysext_measured = m; - linux_size = szs[SECTION_LINUX]; - linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_LINUX]; + if (parameters_measured > 0) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrKernelParameters", TPM_PCR_INDEX_KERNEL_PARAMETERS, 0); + if (sysext_measured) + (void) efivar_set_uint_string(LOADER_GUID, L"StubPcrInitRDSysExts", TPM_PCR_INDEX_INITRD_SYSEXTS, 0); - initrd_size = szs[SECTION_INITRD]; - initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_INITRD] : 0; + linux_size = szs[UNIFIED_SECTION_LINUX]; + linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; - dt_size = szs[SECTION_DTB]; - dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[SECTION_DTB] : 0; + initrd_size = szs[UNIFIED_SECTION_INITRD]; + initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; + + dt_size = szs[UNIFIED_SECTION_DTB]; + dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; if (credential_initrd || global_credential_initrd || sysext_initrd) { /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index 7603eb8f534..6fcf9b31211 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -637,6 +637,31 @@ __attribute__((noinline)) void debug_break(void) { } #endif + +#ifdef EFI_DEBUG +void hexdump(const char16_t *prefix, const void *data, UINTN size) { + static const char hex[16] = "0123456789abcdef"; + _cleanup_free_ char16_t *buf = NULL; + const uint8_t *d = data; + + assert(prefix); + assert(data || size == 0); + + /* Debugging helper — please keep this around, even if not used */ + + buf = xnew(char16_t, size*2+1); + + for (UINTN i = 0; i < size; i++) { + buf[i*2] = hex[d[i] >> 4]; + buf[i*2+1] = hex[d[i] & 0x0F]; + } + + buf[size*2] = 0; + + log_error_stall(L"%s[%" PRIuN "]: %s", prefix, size, buf); +} +#endif + #if defined(__i386__) || defined(__x86_64__) static inline uint8_t inb(uint16_t port) { uint8_t value; diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 4de799ea0e4..bb4bb64e0e2 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -167,6 +167,10 @@ extern uint8_t _text, _data; # define debug_hook(identity) #endif +#ifdef EFI_DEBUG +void hexdump(const char16_t *prefix, const void *data, UINTN size); +#endif + #if defined(__i386__) || defined(__x86_64__) void beep(UINTN beep_count); #else diff --git a/src/boot/measure.c b/src/boot/measure.c new file mode 100644 index 00000000000..29568c5b0d1 --- /dev/null +++ b/src/boot/measure.c @@ -0,0 +1,533 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "alloc-util.h" +#include "efi-loader.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "main-func.h" +#include "openssl-util.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "terminal-util.h" +#include "tpm-pcr.h" +#include "tpm2-util.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_DESTRUCTOR_REGISTER(arg_banks, strv_freep); + +static inline 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 PCR hash for kernel image.%6$s\n" + "\n%3$sCommands:%4$s\n" + " status Show current PCR values\n" + " calculate Calculate expected PCR values\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" + " --linux=PATH Path Linux kernel ELF image\n" + " --osrel=PATH Path to os-release file\n" + " --cmdline=PATH Path to file with kernel command line\n" + " --initrd=PATH Path to initrd image\n" + " --splash=PATH Path to splash bitmap\n" + " --dtb=PATH Path to Devicetree file\n" + " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + _ARG_SECTION_FIRST, + ARG_LINUX = _ARG_SECTION_FIRST, + ARG_OSREL, + ARG_CMDLINE, + ARG_INITRD, + ARG_SPLASH, + _ARG_SECTION_LAST, + ARG_DTB = _ARG_SECTION_LAST, + ARG_BANK, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "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 }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "bank", required_argument, NULL, ARG_BANK }, + {} + }; + + 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, "h", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(0, NULL, NULL); + return 0; + + case ARG_VERSION: + return version(); + + 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 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 '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + 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(arg_banks); + strv_uniq(arg_banks); + + return 1; +} + +typedef struct PcrState { + const EVP_MD *md; + void *value; + size_t value_size; +} 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].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.", EVP_MD_name(pcr_state->md)); + + /* 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_pcr(PcrState *pcr_states, size_t n) { + _cleanup_free_ void *buffer = NULL; + int r; + + assert(n > 0); + assert(pcr_states); + + 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 = -1; + 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.", EVP_MD_name(pcr_states[i].md)); + } + + 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; + } + + 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.", EVP_MD_name(pcr_states[i].md)); + + 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 an 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 verb_calculate(int argc, char *argv[], void *userdata) { + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + size_t n = 0; + int r; + + if (!arg_sections[UNIFIED_SECTION_LINUX]) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--linux= switch must be specified, refusing."); + + 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; + int sz; + + assert_se(implementation = EVP_get_digestbyname(*d)); /* Must work, we already checked while parsing command line */ + + 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) { + .md = implementation, + .value = TAKE_PTR(v), + .value_size = sz, + }; + } + + r = measure_pcr(pcr_states, n); + if (r < 0) + return r; + + for (size_t i = 0; i < n; i++) { + _cleanup_free_ char *hd = NULL, *b = NULL; + + hd = hexmem(pcr_states[i].value, pcr_states[i].value_size); + if (!hd) + return log_oom(); + + b = strdup(EVP_MD_name(pcr_states[i].md)); + if (!b) + return log_oom(); + + printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, ascii_strlower(b), hd); + } + + 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_support() != TPM2_SUPPORT_FULL) + 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(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE("StubPcrKernelParameters"), "kernel parameters"); + if (r < 0) + return r; + + r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE("StubPcrInitRDSysExts"), "initrd system extension images"); + 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) { + + static const struct { + uint32_t nr; + const char *description; + } relevant_pcrs[] = { + { TPM_PCR_INDEX_KERNEL_IMAGE, "Unified Kernel Image" }, + { TPM_PCR_INDEX_KERNEL_PARAMETERS, "Kernel Parameters" }, + { TPM_PCR_INDEX_INITRD_SYSEXTS, "initrd System Extensions" }, + }; + + int r; + + r = validate_stub(); + if (r < 0) + return r; + + for (size_t i = 0; i < ELEMENTSOF(relevant_pcrs); i++) { + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL, *f = 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), relevant_pcrs[i].nr) < 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), SIZE_MAX, &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + 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(), + relevant_pcrs[i].nr, + relevant_pcrs[i].description, + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", relevant_pcrs[i].nr, b, f); + } + } + + 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 }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int run(int argc, char *argv[]) { + int r; + + log_show_color(true); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return measure_main(argc, argv); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index d1b70eafdce..fe34e6c714a 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -4,6 +4,7 @@ #include #include "string-util-fundamental.h" +/* Features of the loader, i.e. systemd-boot */ #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT (UINT64_C(1) << 0) #define EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT (UINT64_C(1) << 1) #define EFI_LOADER_FEATURE_ENTRY_DEFAULT (UINT64_C(1) << 2) @@ -12,6 +13,15 @@ #define EFI_LOADER_FEATURE_XBOOTLDR (UINT64_C(1) << 5) #define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6) #define EFI_LOADER_FEATURE_LOAD_DRIVER (UINT64_C(1) << 7) +#define EFI_LOADER_FEATURE_SORT_KEY (UINT64_C(1) << 8) +#define EFI_LOADER_FEATURE_SAVED_ENTRY (UINT64_C(1) << 9) +#define EFI_LOADER_FEATURE_DEVICETREE (UINT64_C(1) << 10) + +/* Features of the stub, i.e. systemd-stub */ +#define EFI_STUB_FEATURE_REPORT_BOOT_PARTITION (UINT64_C(1) << 0) +#define EFI_STUB_FEATURE_PICK_UP_CREDENTIALS (UINT64_C(1) << 1) +#define EFI_STUB_FEATURE_PICK_UP_SYSEXTS (UINT64_C(1) << 2) +#define EFI_STUB_FEATURE_THREE_PCRS (UINT64_C(1) << 3) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index 2ec4a28433d..3810d6b456d 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -8,6 +8,7 @@ fundamental_headers = files( 'macro-fundamental.h', 'sha256.h', 'string-util-fundamental.h', + 'tpm-pcr.h', ) # for sd-boot @@ -16,6 +17,7 @@ fundamental_source_paths = files( 'efivars-fundamental.c', 'sha256.c', 'string-util-fundamental.c', + 'tpm-pcr.c', ) # for libbasic diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c new file mode 100644 index 00000000000..97b3c7b9d2a --- /dev/null +++ b/src/fundamental/tpm-pcr.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "tpm-pcr.h" + +const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_LINUX] = ".linux", + [UNIFIED_SECTION_OSREL] = ".osrel", + [UNIFIED_SECTION_CMDLINE] = ".cmdline", + [UNIFIED_SECTION_INITRD] = ".initrd", + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", + NULL, +}; diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h new file mode 100644 index 00000000000..fb0774f70d1 --- /dev/null +++ b/src/fundamental/tpm-pcr.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* The various TPM PCRs we measure into from sd-stub and sd-boot. */ + +/* This TPM PCR is where we extend the sd-stub "payloads" into, before using them. i.e. the kernel ELF image, + * embedded initrd, and so on. In contrast to PCR 4 (which also contains this data, given the whole + * surrounding PE image is measured into it) this should be reasonably pre-calculatable, because it *only* + * consists of static data from the kernel PE image. */ +#define TPM_PCR_INDEX_KERNEL_IMAGE 11U + +/* This TPM PCR is where sd-stub extends the kernel command line and any passed credentials into. */ +#define TPM_PCR_INDEX_KERNEL_PARAMETERS 12U + +/* sd-stub used to write the kernel command line/credentials into PCR 8, in systemd <= 250. Let's provide for + * some compatibility. (Remove in 2023!) */ +#if EFI_TPM_PCR_COMPAT +#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT 8U +#else +#define TPM_PCR_INDEX_KERNEL_PARAMETERS_COMPAT UINT32_MAX +#endif + +/* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ +#define TPM_PCR_INDEX_INITRD_SYSEXTS 13U + +/* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in + * which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */ +typedef enum UnifiedSection { + UNIFIED_SECTION_LINUX, + UNIFIED_SECTION_OSREL, + UNIFIED_SECTION_CMDLINE, + UNIFIED_SECTION_INITRD, + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, + _UNIFIED_SECTION_MAX, +} UnifiedSection; + +extern const char* const unified_sections[_UNIFIED_SECTION_MAX + 1]; diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index e114d5ea2c6..3eed44a533b 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -187,6 +187,54 @@ int efi_loader_get_features(uint64_t *ret) { return 0; } +int efi_stub_get_features(uint64_t *ret) { + _cleanup_free_ void *v = NULL; + size_t s; + int r; + + assert(ret); + + if (!is_efi_boot()) { + *ret = 0; + return 0; + } + + r = efi_get_variable(EFI_LOADER_VARIABLE(StubFeatures), NULL, &v, &s); + if (r == -ENOENT) { + _cleanup_free_ char *info = NULL; + + /* The new (v252+) StubFeatures variable is not supported, let's see if it's systemd-stub at all */ + r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubInfo), &info); + if (r < 0) { + if (r != -ENOENT) + return r; + + /* Variable not set, definitely means not systemd-stub */ + + } else if (first_word(info, "systemd-stub")) { + + /* An older systemd-stub version. Let's hardcode the feature set, since it was pretty + * static in all its versions. */ + + *ret = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION; + return 0; + } + + /* No features supported */ + *ret = 0; + return 0; + } + if (r < 0) + return r; + + if (s != sizeof(uint64_t)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "StubFeatures EFI variable doesn't have the right size."); + + memcpy(ret, v, sizeof(uint64_t)); + return 0; +} + int efi_loader_get_config_timeout_one_shot(usec_t *ret) { _cleanup_free_ char *v = NULL; static struct stat cache_stat = {}; diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h index 507160e50de..84968869abe 100644 --- a/src/shared/efi-loader.h +++ b/src/shared/efi-loader.h @@ -16,6 +16,7 @@ int efi_loader_get_boot_usec(usec_t *ret_firmware, usec_t *ret_loader); int efi_loader_get_entries(char ***ret); int efi_loader_get_features(uint64_t *ret); +int efi_stub_get_features(uint64_t *ret); int efi_loader_get_config_timeout_one_shot(usec_t *ret); int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); @@ -38,6 +39,10 @@ static inline int efi_loader_get_features(uint64_t *ret) { return -EOPNOTSUPP; } +static inline int efi_stub_get_features(uint64_t *ret) { + return -EOPNOTSUPP; +} + static inline int efi_loader_get_config_timeout_one_shot(usec_t *ret) { return -EOPNOTSUPP; } diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh index 09f78a0226b..f0bbee9436c 100755 --- a/test/units/testsuite-70.sh +++ b/test/units/testsuite-70.sh @@ -43,6 +43,18 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $ tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1 +echo HALLO > /tmp/tpmdata1 +echo foobar > /tmp/tpmdata2 + +cat > /tmp/result </testok exit 0