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).
+
+
12systemd-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.
+
+
14The 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 + 11Core kernel code (embedded in unified PE binary)
- 4
+ 4 + 11Main initrd (embedded in unified PE binary)
- 4 + 9
+ 4 + 9 + 11Default kernel command line (embedded in unified PE binary)
- 4
+ 4 + 11
@@ -185,12 +192,12 @@
Credentials (synthesized initrd from companion files)
- 12 + 9
+ 9 + 12System 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