From 0be3db467f042cc030f9a4a840854ce36a0f8317 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 16:48:28 +0100 Subject: [PATCH 01/13] Remove obsolete pcr_policy_sign_systemd() Signed-off-by: Olaf Kirch --- src/pcr-policy.c | 51 ------------------------------------------------ src/pcr.h | 3 --- 2 files changed, 54 deletions(-) diff --git a/src/pcr-policy.c b/src/pcr-policy.c index 95a4299..8f2c42c 100644 --- a/src/pcr-policy.c +++ b/src/pcr-policy.c @@ -1539,57 +1539,6 @@ pcr_unseal_secret(const target_platform_t *platform, return platform->unseal_secret(input_path, output_path, pcr_selection, signed_policy_path, public_key_file); } -bool -pcr_policy_sign_systemd(const tpm_pcr_bank_t *bank, - const stored_key_t *private_key_file, - const char *output_path) -{ - bool ok = false; - FILE *fp = NULL; - tpm_rsa_key_t *rsa_key = NULL; - const tpm_evdigest_t *digest; - ESYS_CONTEXT *esys_context = tss_esys_context(); - TPM2B_DIGEST *pcr_policy = NULL; - TPMT_SIGNATURE *signed_policy = NULL; - - if (!(fp = fopen(output_path, "w"))) { - error("Cannot open systemd JSON file %s: %m\n", output_path); - goto out; - } - - if (!(rsa_key = stored_key_read_rsa_private(private_key_file))) - goto out; - digest = tpm_rsa_key_public_digest(rsa_key); - - if (!(pcr_policy = __pcr_policy_make(esys_context, bank))) - goto out; - - if (!__pcr_policy_sign(rsa_key, pcr_policy, &signed_policy)) - goto out; - - fprintf(fp, "{\n"); - fprintf(fp, "\t\"%s\": [\n", bank->algo_name); - fprintf(fp, "\t\t\{\n"); - fprintf(fp, "\t\t\t\"pcrs\": [\n"); - fprintf(fp, "\t\t\t\t%s\n", print_pcr_mask(bank->pcr_mask)); - fprintf(fp, "\t\t\t],\n"); - fprintf(fp, "\t\t\t\"pkfp\": \"%s\",\n", print_hex_string(digest->data, digest->size)); - fprintf(fp, "\t\t\t\"pol\": \"%s\",\n", print_hex_string(pcr_policy->buffer, pcr_policy->size)); - fprintf(fp, "\t\t\t\"sig\": \"%s\"\n", print_base64_value(signed_policy->signature.rsassa.sig.buffer, signed_policy->signature.rsassa.sig.size)); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t]\n"); - fprintf(fp, "}\n"); - - ok = true; - -out: - if (rsa_key) - tpm_rsa_key_free(rsa_key); - - fclose(fp); - return ok; -} - /* * Depending on the target platform, sealed data, authorized policies etc are * written to different types of files. diff --git a/src/pcr.h b/src/pcr.h index 1b0f544..4d8f816 100644 --- a/src/pcr.h +++ b/src/pcr.h @@ -65,9 +65,6 @@ extern bool pcr_policy_sign(const target_platform_t *platform, const tpm_pcr_ba const stored_key_t *private_key_file, const char *input_path, const char *output_path, const char *policy_name); -extern bool pcr_policy_sign_systemd(const tpm_pcr_bank_t *bank, - const stored_key_t *private_key_file, - const char *output_path); extern bool pcr_authorized_policy_seal_secret(const target_platform_t *platform, const char *authorized_policy, const char *input_path, const char *output_path); From 3a66b6e1dc953ac95a715a4b1123e772024fba2c Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 17:18:14 +0100 Subject: [PATCH 02/13] Introduce read_single_line_file() and use it in sd-boot.c Signed-off-by: Olaf Kirch --- src/sd-boot.c | 30 ++---------------------------- src/util.c | 19 +++++++++++++++++++ src/util.h | 1 + 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/src/sd-boot.c b/src/sd-boot.c index 3eea1a1..bbdb498 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -37,21 +37,8 @@ static const char * read_entry_token(void) { static char id[SDB_LINE_MAX]; - FILE *fp; - - if (!(fp = fopen("/etc/kernel/entry-token", "r"))) { - debug("Cannot open /etc/kernel/entry-token\n"); - goto fail; - } - - if (fgets(id, SDB_LINE_MAX, fp)) - id[strcspn(id, "\n")] = 0; - fclose(fp); - return id; - -fail: - return NULL; + return read_single_line_file("/etc/kernel/entry-token", id, sizeof(id)); } static const char * @@ -109,21 +96,8 @@ static const char * read_machine_id(void) { static char id[SDB_LINE_MAX]; - FILE *fp; - - if (!(fp = fopen("/etc/machine-id", "r"))) { - error("Cannot open /etc/machine_id: %m\n"); - goto fail; - } - - if (fgets(id, SDB_LINE_MAX, fp)) - id[strcspn(id, "\n")] = 0; - fclose(fp); - return id; - -fail: - return NULL; + return read_single_line_file("/etc/machine-id", id, sizeof(id)); } static bool diff --git a/src/util.c b/src/util.c index 1f2640f..644d138 100644 --- a/src/util.c +++ b/src/util.c @@ -514,3 +514,22 @@ path_has_file_extension(const char *path, const char *suffix) return !strcasecmp(path + n, suffix); } + +const char * +read_single_line_file(const char *path, char *buffer, size_t size) +{ + FILE *fp; + + if (!(fp = fopen(path, "r"))) { + debug("Cannot open %s: %m\n", path); + return NULL; + } + + if (fgets(buffer, size, fp) != NULL) + buffer[strcspn(buffer, "\n")] = 0; + else + buffer[0] = '\0'; + + fclose(fp); + return buffer; +} diff --git a/src/util.h b/src/util.h index c98017f..e63ef16 100644 --- a/src/util.h +++ b/src/util.h @@ -142,6 +142,7 @@ extern double timing_since(double); extern const char * path_unix2dos(const char *path); extern const char * path_dos2unix(const char *path); extern bool path_has_file_extension(const char *path, const char *suffix); +extern const char * read_single_line_file(const char *path, char *buffer, size_t size); extern int version_string_compare(const char *, const char *); From 32680d9efa0b866bd78d7ad5bd362aca511efc05 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 18:26:36 +0100 Subject: [PATCH 03/13] tpm_event_get_digest: changed second argument from algo_name to an algo_info pointer Signed-off-by: Olaf Kirch --- src/efi-application.c | 2 +- src/efi-variable.c | 6 +++--- src/eventlog.c | 6 +----- src/eventlog.h | 2 +- src/oracle.c | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/efi-application.c b/src/efi-application.c index 14a2450..06ac9ff 100644 --- a/src/efi-application.c +++ b/src/efi-application.c @@ -287,7 +287,7 @@ __tpm_event_efi_bsa_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *pars */ if (!evspec->efi_application) { debug("Unable to locate boot service application - probably not a file\n"); - return tpm_event_get_digest(ev, ctx->algo->openssl_name); + return tpm_event_get_digest(ev, ctx->algo); } /* The next boot can have a different kernel */ diff --git a/src/efi-variable.c b/src/efi-variable.c index 7e9e38b..6c08cde 100644 --- a/src/efi-variable.c +++ b/src/efi-variable.c @@ -141,7 +141,7 @@ __tpm_event_efi_variable_detect_hash_strategy(const tpm_event_t *ev, const tpm_p { const tpm_evdigest_t *md, *old_md; - old_md = tpm_event_get_digest(ev, algo->openssl_name); + old_md = tpm_event_get_digest(ev, algo); if (old_md == NULL) { debug("Event does not provide a digest for algorithm %s\n", algo->openssl_name); return -1; @@ -199,7 +199,7 @@ __tpm_event_efi_variable_rehash(const tpm_event_t *ev, const tpm_parsed_event_t * For the time being, just pretend these cannot be changed from * within the running system. */ - md = tpm_event_get_digest(ev, algo->openssl_name); + md = tpm_event_get_digest(ev, algo); goto out; } } else { @@ -214,7 +214,7 @@ __tpm_event_efi_variable_rehash(const tpm_event_t *ev, const tpm_parsed_event_t /* The content of the variable doesn't exist during the measurement * and is also not available at runtime. Let's skip this event. */ - md = tpm_event_get_digest(ev, algo->openssl_name); + md = tpm_event_get_digest(ev, algo); } goto out; } diff --git a/src/eventlog.c b/src/eventlog.c index e99a20f..32cc75c 100644 --- a/src/eventlog.c +++ b/src/eventlog.c @@ -406,14 +406,10 @@ tpm_event_type_to_string(unsigned int event_type) } const tpm_evdigest_t * -tpm_event_get_digest(const tpm_event_t *ev, const char *algo_name) +tpm_event_get_digest(const tpm_event_t *ev, const tpm_algo_info_t *algo_info) { - const tpm_algo_info_t *algo_info; unsigned int i; - if ((algo_info = digest_by_name(algo_name)) < 0) - fatal("Unknown algo name \"%s\"\n", algo_name); - for (i = 0; i < ev->pcr_count; ++i) { const tpm_evdigest_t *md = &ev->pcr_values[i]; diff --git a/src/eventlog.h b/src/eventlog.h index 8951765..bd1106f 100644 --- a/src/eventlog.h +++ b/src/eventlog.h @@ -291,7 +291,7 @@ extern void tpm_event_log_scan_ctx_init(tpm_event_log_scan_ctx_t *); extern void tpm_event_log_scan_ctx_destroy(tpm_event_log_scan_ctx_t *); extern tpm_parsed_event_t * tpm_event_parse(tpm_event_t *ev, tpm_event_log_scan_ctx_t *); extern const char * tpm_event_type_to_string(unsigned int event_type); -extern const tpm_evdigest_t * tpm_event_get_digest(const tpm_event_t *ev, const char *algo_name); +extern const tpm_evdigest_t * tpm_event_get_digest(const tpm_event_t *ev, const tpm_algo_info_t *algo_info); extern void tpm_parsed_event_print(tpm_parsed_event_t *parsed, tpm_event_bit_printer *); extern const char * tpm_parsed_event_describe(tpm_parsed_event_t *parsed); diff --git a/src/oracle.c b/src/oracle.c index 52fe261..c4e4873 100644 --- a/src/oracle.c +++ b/src/oracle.c @@ -670,7 +670,7 @@ predictor_update_eventlog(struct predictor *pred) debug("\n"); __tpm_event_print(ev, debug); - if (!(old_digest = tpm_event_get_digest(ev, pred->algo))) + if (!(old_digest = tpm_event_get_digest(ev, pred->algo_info))) fatal("Event log lacks a hash for digest algorithm %s\n", pred->algo); if (false) { From 4d88450e9da882c759a68ba8c15267b22a377cdc Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 18:31:42 +0100 Subject: [PATCH 04/13] Add uapi_ types and functions for dealing with UAPI boot entries Signed-off-by: Olaf Kirch --- Makefile.in | 3 +- src/types.h | 1 + src/uapi.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/uapi.h | 43 +++++++ 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 src/uapi.c create mode 100644 src/uapi.h diff --git a/Makefile.in b/Makefile.in index a0508e9..02a915b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -33,7 +33,8 @@ ORACLE_SRCS = oracle.c \ bufparser.c \ store.c \ util.c \ - sd-boot.c + sd-boot.c \ + uapi.c ORACLE_OBJS = $(addprefix build/,$(patsubst %.c,%.o,$(ORACLE_SRCS))) all: $(TOOLS) $(MANPAGES) diff --git a/src/types.h b/src/types.h index e76903a..5b01079 100644 --- a/src/types.h +++ b/src/types.h @@ -35,6 +35,7 @@ typedef struct pecoff_image_info pecoff_image_info_t; typedef struct testcase testcase_t; typedef struct stored_key stored_key_t; typedef struct target_platform target_platform_t; +typedef struct uapi_boot_entry uapi_boot_entry_t; #endif /* TYPES_H */ diff --git a/src/uapi.c b/src/uapi.c new file mode 100644 index 0000000..bbcaea9 --- /dev/null +++ b/src/uapi.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2022, 2023 SUSE LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Written by Alberto Planas , Olaf Kirch + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "uapi.h" +#include "util.h" + +#define UAPI_LINE_MAX 1024 + + +static int vercmp(const void *va, const void *vb); + +static uapi_boot_entry_t * +uapi_boot_entry_new(void) +{ + uapi_boot_entry_t *ube; + + ube = calloc(1, sizeof(*ube)); + return ube; +} + +static void +drop_boot_entry(uapi_boot_entry_t **entry_p) +{ + if (*entry_p) { + uapi_boot_entry_free(*entry_p); + *entry_p = NULL; + } +} + +static uapi_boot_entry_t * +uapi_boot_entry_load(const char *path) +{ + uapi_boot_entry_t *result = NULL; + char line[UAPI_LINE_MAX]; + FILE *fp; + + if (!(fp = fopen(path, "r"))) { + error("Unable to open %s: %m\n", path); + return NULL; + } + + result = uapi_boot_entry_new(); + while (fgets(line, sizeof(line), fp)) { + char *key, *value; + unsigned int i; + + if (!isalpha(line[0])) + continue; + + /* strip white space off the end */ + i = strlen(line); + while (i > 0 && isspace(line[i-1])) + line[--i] = '\0'; + + key = line; + for (i = 0; line[i]; ++i) { + if (isspace(line[i])) { + line[i++] = '\0'; + break; + } + } + + while (isspace(line[i])) + ++i; + value = line + i; + if (*value == '\0') + value = NULL; + + if (!strcmp("sort-key", key)) + assign_string(&result->sort_key, value); + else + if (!strcmp("machine-id", key)) + assign_string(&result->machine_id, value); + else + if (!strcmp("version", key)) + assign_string(&result->version, value); + else + if (!strcmp("options", key)) + assign_string(&result->options, value); + else + if (!strcmp("linux", key)) + assign_string(&result->image_path, value); + else + if (!strcmp("initrd", key)) + assign_string(&result->initrd_path, value); + } + + fclose(fp); + return result; +} + +static bool +uapi_boot_entry_applies(const uapi_boot_entry_t *entry, const char *machine_id, const char *architecture) +{ + if (entry->machine_id && machine_id && strcmp(entry->machine_id, machine_id)) + return false; + if (entry->architecture && architecture && strcmp(entry->architecture, architecture)) + return false; + /* We don't check the efi key because it can only express "entry is EFI only"; it cannot express + * "this is a legacy BIOS entry" */ + return true; +} + +/* + * Returns true iff entry_a is "more recent" or "better" than entry_b + */ +static bool +uapi_boot_entry_more_recent(const uapi_boot_entry_t *entry_a, const uapi_boot_entry_t *entry_b) +{ + int r = 0; + + /* having a sort key is better than not having one. */ + r = strcmp(entry_a->sort_key? : "", entry_b->sort_key? : ""); + + if (r == 0) + r = vercmp(entry_a->version? : "", entry_b->version? : ""); + + return r > 0; +} + +/* + * We pass in a best_ret pointer in case we need to extend this + * to search more than on directory + */ +uapi_boot_entry_t * +uapi_find_matching_boot_entry(const char *dir_path, + const char *entry_id, const char *machine_id, const char *architecture, + uapi_boot_entry_t **best_ret) +{ + uapi_boot_entry_t *best = *best_ret; + unsigned int entry_id_len = 0; + struct dirent *d; + DIR *dir; + + if (!(dir = opendir(dir_path))) { + if (errno != ENOENT) + error("Cannot open %s for reading: %m\n", dir_path); + return NULL; + } + + if (entry_id) + entry_id_len = strlen(entry_id); + + while ((d = readdir(dir)) != NULL) { + char config_path[PATH_MAX]; + uapi_boot_entry_t *entry; + + if (d->d_type != DT_REG) + continue; + + if (entry_id && strncmp(d->d_name, entry_id, entry_id_len)) + continue; + + snprintf(config_path, sizeof(config_path), "%s/%s", dir_path, d->d_name); + if (!(entry = uapi_boot_entry_load(config_path))) { + warning("Unable to process UAPI boot entry file at \"%s\"\n", config_path); + continue; + } + + if (uapi_boot_entry_applies(entry, machine_id, architecture)) { + if (best == NULL || uapi_boot_entry_more_recent(entry, best)) { + /* swap best and entry */ + uapi_boot_entry_t *tmp = best; + best = entry; + entry = tmp; + } + } + + drop_boot_entry(&entry); + } + + closedir(dir); + + *best_ret = best; + return best; +} + +uapi_boot_entry_t * +uapi_find_boot_entry(const char *id, const char *machine_id) +{ + uapi_boot_entry_t *best = NULL; + struct utsname uts; + const char *architecture; + + if (uname(&uts) >= 0) + architecture = uts.machine; + + return uapi_find_matching_boot_entry(UAPI_BOOT_DIRECTORY, + id, machine_id, architecture, + &best); +} + +void +uapi_boot_entry_free(uapi_boot_entry_t *ube) +{ + drop_string(&ube->title); + drop_string(&ube->version); + drop_string(&ube->machine_id); + drop_string(&ube->image_path); + drop_string(&ube->initrd_path); + drop_string(&ube->options); + free(ube); +} + +/* + * Version comparison + */ +static int +cmp(int a, int b) +{ + return a - b; +} + +static bool +isvalid(char a) +{ + return isalnum(a) || a == '~' || a == '-' || a == '^' || a == '.'; +} + +static int +natoi(const char *a, unsigned int n) +{ + char line[UAPI_LINE_MAX]; + + strncpy(line, a, MIN(UAPI_LINE_MAX, n)); + return atoi(line); +} + +static int +vercmp(const void *va, const void *vb) +{ + /* https://uapi-group.org/specifications/specs/version_format_specification/ */ + /* This code is based on strverscmp_improved from systemd */ + + const char *a = va; + const char *b = vb; + const char *sep = "~-^."; + + assert(a != NULL); + assert(b != NULL); + + for(;;) { + const char *aa, *bb; + int r; + + while (*a != '\0' && !isvalid(*a)) + a++; + while (*b != '\0' && !isvalid(*b)) + b++; + + /* The longer string is considered new */ + if (*a == '\0' || *b == '\0') + return cmp(*a, *b); + + for (int i = 0; i < strlen(sep); i++) { + char s = sep[i]; + + if (*a == s || *b == s) { + r = cmp(*a != s, *b != s); + if (r != 0) + return r; + + a++; + b++; + } + } + + if (isdigit(*a) || isdigit(*b)) { + for (aa = a; isdigit(*aa); aa++); + for (bb = b; isdigit(*bb); bb++); + + r = cmp(a != aa, b != bb); + if (r != 0) + return r; + + r = cmp(natoi(a, aa - a), natoi(b, bb - b)); + if (r != 0) + return r; + } else { + for (aa = a; isalpha(*aa); aa++); + for (bb = b; isalpha(*bb); bb++); + + r = cmp(strncmp(a, b, MIN(aa - a, bb - b)), 0); + if (r != 0) + return r; + + r = cmp(aa - a, bb - b); + if (r != 0) + return r; + } + + a = aa; + b = bb; + } +} + diff --git a/src/uapi.h b/src/uapi.h new file mode 100644 index 0000000..ba1147e --- /dev/null +++ b/src/uapi.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022, 2023 SUSE LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Written by Olaf Kirch + */ + +#ifndef UAPI_H +#define UAPI_H + +#include "types.h" + +struct uapi_boot_entry { + char * title; + bool efi; + char * sort_key; + char * version; + char * machine_id; + char * architecture; + char * image_path; + char * initrd_path; + char * options; +}; + +#define UAPI_BOOT_DIRECTORY "/boot/efi/loader/entries" + +extern uapi_boot_entry_t * uapi_find_boot_entry(const char *id, const char *machine_id); +extern void uapi_boot_entry_free(uapi_boot_entry_t *); + +#endif /* UAPI_H */ From a7a4c854c9668900c8eb9ea9c99d51b6e822f376 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 18:33:38 +0100 Subject: [PATCH 05/13] Add --next-kernel command line option; use uapi to locate next kernel when given Signed-off-by: Olaf Kirch --- man/pcr-oracle.8.in | 12 ++++++++++++ src/eventlog.h | 3 +++ src/oracle.c | 23 +++++++++++++++++++++-- src/sd-boot.c | 19 +++++++++++++++++++ src/sd-boot.h | 3 +++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/man/pcr-oracle.8.in b/man/pcr-oracle.8.in index eda3cfb..4bd60ba 100644 --- a/man/pcr-oracle.8.in +++ b/man/pcr-oracle.8.in @@ -495,6 +495,18 @@ events (tracked in PCR9). In this case, it would be totally sufficient to stop processing after grub loaded \fPgrub.cfg\fP from the EFI System Partition. .TP +.BI --next-kernel " id +In a systemd-boot environment, the values of PCR registers also depend on +the contents of the kernel and initrd image, as well as the kernel command line +options. After a kernel update or an initrd rebuild, these values will change. +So when predicting PCR values in this case, \fBpcr-oracle\fP needs to be +instructed to refer to the future kernel rather than the current one. +.IP +This is what the \fB--next-kernel\fP option is for. It takes one argument, which +is either an ID (in the sense of systemd-boot IDs), or \fBauto\fP. When +the latter is given, \fBpcr-oracle\fP will make a best guess as to what +kernel image will be used on next boot. +.TP .BI --authorized-policy " path Specify the location of the authorized policy. In conjunction with the \fBcreate-authorized-policy\fP action, the newly created policy diff --git a/src/eventlog.h b/src/eventlog.h index bd1106f..65e5322 100644 --- a/src/eventlog.h +++ b/src/eventlog.h @@ -200,6 +200,9 @@ typedef struct tpm_event_log_rehash_ctx { bool use_pesign; /* compute authenticode FP using external pesign application */ const pecoff_image_info_t *next_stage_img; + + /* This get set when the user specifies --next-kernel */ + uapi_boot_entry_t * next_kernel; } tpm_event_log_rehash_ctx_t; #define GRUB_COMMAND_ARGV_MAX 32 diff --git a/src/oracle.c b/src/oracle.c index c4e4873..2cc53c1 100644 --- a/src/oracle.c +++ b/src/oracle.c @@ -34,6 +34,7 @@ #include "rsa.h" #include "store.h" #include "testcase.h" +#include "sd-boot.h" enum { ACTION_NONE, @@ -58,6 +59,7 @@ struct predictor { const char * initial_source; const char * tpm_event_log_path; + const char * next_kernel_id; const char * algo; const tpm_algo_info_t * algo_info; @@ -98,6 +100,7 @@ enum { OPT_POLICY_NAME, OPT_POLICY_FORMAT, OPT_TARGET_PLATFORM, + OPT_NEXT_KERNEL, }; static struct option options[] = { @@ -114,6 +117,7 @@ static struct option options[] = { { "before", no_argument, 0, OPT_BEFORE }, { "verify", required_argument, 0, OPT_VERIFY }, { "use-pesign", no_argument, 0, OPT_USE_PESIGN }, + { "next-kernel", required_argument, 0, OPT_NEXT_KERNEL }, { "create-testcase", required_argument, 0, OPT_CREATE_TESTCASE }, { "replay-testcase", required_argument, 0, OPT_REPLAY_TESTCASE }, @@ -129,6 +133,7 @@ static struct option options[] = { { "policy-name", required_argument, 0, OPT_POLICY_NAME }, { "policy-format", required_argument, 0, OPT_POLICY_FORMAT }, { "target-platform", required_argument, 0, OPT_TARGET_PLATFORM }, + { "next-kernel", required_argument, 0, OPT_NEXT_KERNEL }, { NULL } }; @@ -250,7 +255,8 @@ predictor_load_eventlog(struct predictor *pred) static struct predictor * predictor_new(const tpm_pcr_selection_t *pcr_selection, const char *source, const char *tpm_eventlog_path, - const char *output_format) + const char *output_format, + const char *next_kernel_id) { struct predictor *pred; @@ -260,6 +266,7 @@ predictor_new(const tpm_pcr_selection_t *pcr_selection, const char *source, pred = calloc(1, sizeof(*pred)); pred->pcr_mask = pcr_selection->pcr_mask; pred->initial_source = source; + pred->next_kernel_id = next_kernel_id; pred->algo = pcr_selection->algo_info->openssl_name; pred->algo_info = pcr_selection->algo_info; @@ -651,6 +658,14 @@ predictor_update_eventlog(struct predictor *pred) tpm_event_log_rehash_ctx_init(&rehash_ctx, pred->algo_info); rehash_ctx.use_pesign = opt_use_pesign; + /* The argument given to --next-kernel will be either "auto" or the + * systemd ID of the next kernel entry to be booted. + * FIXME: we should probably hide this behind a target_platform function. + */ + if (pred->next_kernel_id != NULL + && !(rehash_ctx.next_kernel = sdb_identify_next_kernel(pred->next_kernel_id))) + fatal("unable to identify next kernel \"%s\"\n", pred->next_kernel_id); + for (ev = pred->event_log; ev; ev = ev->next) { tpm_evdigest_t *pcr; bool stop = false; @@ -1021,6 +1036,7 @@ main(int argc, char **argv) char *opt_rsa_bits = NULL; char *opt_policy_name = NULL; char *opt_target_platform = NULL; + char *opt_next_kernel = NULL; const target_platform_t *target; unsigned int action_flags = 0; unsigned int rsa_bits = 2048; @@ -1055,6 +1071,9 @@ main(int argc, char **argv) case OPT_USE_PESIGN: opt_use_pesign = 1; break; + case OPT_NEXT_KERNEL: + opt_next_kernel = optarg; + break; case OPT_STOP_EVENT: opt_stop_event = optarg; break; @@ -1296,7 +1315,7 @@ main(int argc, char **argv) fatal("BUG: action %u should have parsed a PCR selection argument", action); pred = predictor_new(pcr_selection, opt_from, opt_eventlog_path, - opt_output_format); + opt_output_format, opt_next_kernel); if (opt_stop_event) predictor_set_stop_event(pred, opt_stop_event, !opt_stop_before); diff --git a/src/sd-boot.c b/src/sd-boot.c index bbdb498..2e7ec61 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -387,6 +387,25 @@ sdb_get_next_kernel(void) return NULL; } +/* + * Identify the next kernel and initrd given an ID + */ +uapi_boot_entry_t * +sdb_identify_next_kernel(const char *id) +{ + const char *machine_id; + + if (id == NULL || !strcasecmp(id, "default")) { + if (!(id = get_token_id())) + return NULL; + } + + if (!(machine_id = read_machine_id())) + return NULL; + + return uapi_find_boot_entry(id, machine_id); +} + /* * Update the systemd json file */ diff --git a/src/sd-boot.h b/src/sd-boot.h index ef36ce7..fa56751 100644 --- a/src/sd-boot.h +++ b/src/sd-boot.h @@ -21,6 +21,8 @@ #define SD_BOOT_H #include +#include "uapi.h" +#include "types.h" #define SDB_MAX_ENTRIES 16 #define SDB_LINE_MAX 512 @@ -40,6 +42,7 @@ typedef struct sdb_entry_list { sdb_entry_data_t entries[SDB_MAX_ENTRIES]; } sdb_entry_list_t; +extern uapi_boot_entry_t * sdb_identify_next_kernel(const char *id); extern bool sdb_get_entry_list(sdb_entry_list_t *result); extern bool sdb_is_kernel(const char *application); extern const char * sdb_get_next_kernel(void); From dcfc751ac78400d5bca53b560768f6a3603443f7 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 18:41:26 +0100 Subject: [PATCH 06/13] Adjust EFI BSA and systemd event rehashing to use rehash_ctx->next_kernel Signed-off-by: Olaf Kirch --- src/efi-application.c | 4 ++-- src/eventlog.c | 38 +++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/efi-application.c b/src/efi-application.c index 06ac9ff..9d7e531 100644 --- a/src/efi-application.c +++ b/src/efi-application.c @@ -291,8 +291,8 @@ __tpm_event_efi_bsa_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *pars } /* The next boot can have a different kernel */ - if (sdb_is_kernel(evspec->efi_application)) { - new_application = sdb_get_next_kernel(); + if (sdb_is_kernel(evspec->efi_application) && ctx->next_kernel) { + new_application = ctx->next_kernel->image_path; if (new_application) { evspec_clone = *evspec; evspec_clone.efi_application = strdup(new_application); diff --git a/src/eventlog.c b/src/eventlog.c index 32cc75c..3498790 100644 --- a/src/eventlog.c +++ b/src/eventlog.c @@ -32,7 +32,7 @@ #include "runtime.h" #include "digest.h" #include "util.h" -#include "sd-boot.h" +#include "uapi.h" #define TPM_EVENT_LOG_MAX_ALGOS 64 @@ -789,21 +789,24 @@ __tpm_event_systemd_describe(const tpm_parsed_event_t *parsed) static const tpm_evdigest_t * __tpm_event_systemd_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx) { - sdb_entry_list_t entry_list; + const uapi_boot_entry_t *next_kernel = ctx->next_kernel; char initrd[2048]; char initrd_utf16[4096]; unsigned int len; - memset(&entry_list, 0, sizeof(entry_list)); - if (!sdb_get_entry_list(&entry_list)) { - error("Error generating the list of boot entries\n"); + /* If no --next-kernel option was given, do not rehash anything */ + if (next_kernel == NULL) + return tpm_event_get_digest(ev, ctx->algo); + + if (!next_kernel->image_path) { + error("Unable to identify the next kernel\n"); return NULL; } - debug("Next boot entry expected from: %s\n", entry_list.entries[0].path); + debug("Next boot entry expected from: %s %s\n", next_kernel->title, next_kernel->version? : ""); snprintf(initrd, sizeof(initrd), "initrd=%s %s", - path_unix2dos(entry_list.entries[0].path), - entry_list.entries[0].options); + path_unix2dos(next_kernel->initrd_path), + next_kernel->options? : ""); len = (strlen(initrd) + 1) << 1; assert(len <= sizeof(initrd_utf16)); @@ -860,19 +863,20 @@ __tpm_event_tag_initrd_describe(const tpm_parsed_event_t *parsed) static const tpm_evdigest_t * __tpm_event_tag_initrd_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx) { - sdb_entry_list_t entry_list; - const tpm_evdigest_t *md = NULL; + const uapi_boot_entry_t *next_kernel = ctx->next_kernel; + + /* If no --next-kernel option was given, do not rehash anything */ + if (next_kernel == NULL) + return tpm_event_get_digest(ev, ctx->algo); - memset(&entry_list, 0, sizeof(entry_list)); - if (!sdb_get_entry_list(&entry_list)) { - error("Error generating the list of boot entries\n"); + if (!next_kernel->initrd_path) { + /* Can this happen eg when going from a split kernel to a unified kernel? */ + error("Unable to identify the next initrd\n"); return NULL; } - debug("Next boot entry expected from: %s\n", entry_list.entries[0].path); - md = runtime_digest_efi_file(ctx->algo, entry_list.entries[0].initrd); - - return md; + debug("Next boot entry expected from: %s %s\n", next_kernel->title, next_kernel->version? : ""); + return runtime_digest_efi_file(ctx->algo, next_kernel->initrd_path); } /* From 13f67d840ffadccbd0daf4fb133f9471a5b1a439 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Mon, 27 Nov 2023 18:44:05 +0100 Subject: [PATCH 07/13] Remove old code from sd-boot.* Signed-off-by: Olaf Kirch --- src/sd-boot.c | 222 +------------------------------------------------- src/sd-boot.h | 2 - 2 files changed, 3 insertions(+), 221 deletions(-) diff --git a/src/sd-boot.c b/src/sd-boot.c index 2e7ec61..c878f6c 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -100,163 +100,6 @@ read_machine_id(void) return read_single_line_file("/etc/machine-id", id, sizeof(id)); } -static bool -read_entry(sdb_entry_data_t *result) -{ - FILE *fp; - char line[SDB_LINE_MAX]; - - if (!(fp = fopen(result->path, "r"))) { - error("Cannot open %s: %m\n", result->path); - goto fail; - } - - while (fgets(line, SDB_LINE_MAX, fp)) { - char *dest = NULL; - - if (!strncmp("sort-key", line, strlen("sort-key"))) - dest = result->sort_key; - else - if (!strncmp("machine-id", line, strlen("machine-id"))) - dest = result->machine_id; - else - if (!strncmp("version", line, strlen("version"))) - dest = result->version; - else - if (!strncmp("options", line, strlen("options"))) - dest = result->options; - else - if (!strncmp("linux", line, strlen("linux"))) - dest = result->image; - else - if (!strncmp("initrd", line, strlen("initrd"))) - dest = result->initrd; - else - continue; - - /* Position the index on the value section of the line */ - unsigned int index = 0; - while (line[++index] != ' '); - while (line[++index] == ' '); - strncpy(dest, &line[index], strlen(&line[index]) - 1); - } - - fclose(fp); - return true; - -fail: - return false; -} - -static int -cmp(int a, int b) -{ - return a - b; -} - -static bool -isvalid(char a) -{ - return isalnum(a) || a == '~' || a == '-' || a == '^' || a == '.'; -} - -static int -natoi(const char *a, unsigned int n) -{ - char line[SDB_LINE_MAX]; - - strncpy(line, a, MIN(SDB_LINE_MAX, n)); - return atoi(line); -} - -static int -vercmp(const void *va, const void *vb) -{ - /* https://uapi-group.org/specifications/specs/version_format_specification/ */ - /* This code is based on strverscmp_improved from systemd */ - - const char *a = va; - const char *b = vb; - const char *sep = "~-^."; - - assert(a != NULL); - assert(b != NULL); - - for(;;) { - const char *aa, *bb; - int r; - - while (*a != '\0' && !isvalid(*a)) - a++; - while (*b != '\0' && !isvalid(*b)) - b++; - - /* The longer string is considered new */ - if (*a == '\0' || *b == '\0') - return cmp(*a, *b); - - for (int i = 0; i < strlen(sep); i++) { - char s = sep[i]; - - if (*a == s || *b == s) { - r = cmp(*a != s, *b != s); - if (r != 0) - return r; - - a++; - b++; - } - } - - if (isdigit(*a) || isdigit(*b)) { - for (aa = a; isdigit(*aa); aa++); - for (bb = b; isdigit(*bb); bb++); - - r = cmp(a != aa, b != bb); - if (r != 0) - return r; - - r = cmp(natoi(a, aa - a), natoi(b, bb - b)); - if (r != 0) - return r; - } else { - for (aa = a; isalpha(*aa); aa++); - for (bb = b; isalpha(*bb); bb++); - - r = cmp(strncmp(a, b, MIN(aa - a, bb - b)), 0); - if (r != 0) - return r; - - r = cmp(aa - a, bb - b); - if (r != 0) - return r; - } - - a = aa; - b = bb; - } -} - -static int -entrycmp(const void *va, const void *vb) -{ - /* https://uapi-group.org/specifications/specs/boot_loader_specification/#sorting */ - int result; - const sdb_entry_data_t *a = va; - const sdb_entry_data_t *b = vb; - - result = strcmp(a->sort_key, b->sort_key); - - if (result == 0) - result = strcmp(a->machine_id, b->machine_id); - - if (result == 0) - result = vercmp(a->version, b->version); - - /* Reverse the order, so new kernels appears first */ - return -result; -} - static bool exists_efi_dir(const char *path) { @@ -302,49 +145,9 @@ get_token_id(void) return token_id; } -bool -sdb_get_entry_list(sdb_entry_list_t *result) -{ - const char *token_id = NULL; - DIR *d = NULL; - struct dirent *dir; - char *path = "/boot/efi/loader/entries"; - - if (!(token_id = get_token_id())) - goto fail; - - if (!(d = opendir(path))) { - error("Cannot read directory contents from /boot/efi/loader/entries: %m\n"); - goto fail; - } - - while ((dir = readdir(d)) != NULL) { - if (result->num_entries >= SDB_MAX_ENTRIES) - break; - - if (strncmp(token_id, dir->d_name, strlen(token_id))) - continue; - - debug("Bootloader entry %s\n", dir->d_name); - - snprintf(result->entries[result->num_entries].path, PATH_MAX, "%s/%s", path, dir->d_name); - if (!read_entry(&result->entries[result->num_entries])) { - error("Cannot read bootloader entry %s\n", dir->d_name); - continue; - } - - result->num_entries++; - } - - qsort(result->entries, result->num_entries, sizeof(result->entries[0]), entrycmp); - - closedir(d); - return true; - -fail: - return false; -} - +/* + * This should probably use UAPI boot entry logic as well + */ bool sdb_is_kernel(const char *application) { @@ -368,25 +171,6 @@ sdb_is_kernel(const char *application) return false; } -const char * -sdb_get_next_kernel(void) -{ - static char result[SDB_LINE_MAX]; - sdb_entry_list_t entry_list; - - memset(&entry_list, 0, sizeof(entry_list)); - if (!sdb_get_entry_list(&entry_list)) { - error("Error generating the list of boot entries\n"); - goto fail; - } - - strncpy(result, entry_list.entries[0].image, SDB_LINE_MAX); - return result; - -fail: - return NULL; -} - /* * Identify the next kernel and initrd given an ID */ diff --git a/src/sd-boot.h b/src/sd-boot.h index fa56751..2b11dc1 100644 --- a/src/sd-boot.h +++ b/src/sd-boot.h @@ -43,9 +43,7 @@ typedef struct sdb_entry_list { } sdb_entry_list_t; extern uapi_boot_entry_t * sdb_identify_next_kernel(const char *id); -extern bool sdb_get_entry_list(sdb_entry_list_t *result); extern bool sdb_is_kernel(const char *application); -extern const char * sdb_get_next_kernel(void); /* This will have to update the systemd json file, and add a new entry. */ extern bool sdb_policy_file_add_entry(const char *filename, From 737d8d561cfbd1dd03e7c4d37d7c515b709cce11 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 10:30:10 +0100 Subject: [PATCH 08/13] Manpage fixes Signed-off-by: Olaf Kirch --- man/pcr-oracle.8.in | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/man/pcr-oracle.8.in b/man/pcr-oracle.8.in index 4bd60ba..3682afa 100644 --- a/man/pcr-oracle.8.in +++ b/man/pcr-oracle.8.in @@ -82,7 +82,7 @@ object in a native format defined in the TPM specification. Finally, the systemd-boot implementation for unlocking disk partitions stores policies in a JSON formatted file, which can be fed to the \fBsystemd-cryptenroll\fP tool. This file will contain any number of -signed policies that we want \fBsystemd-cryptsetup\fB to recognize during +signed policies that we want \fBsystemd-cryptsetup\fP to recognize during boot time. Each entry contains the list of PCRs that compose the PCR policy, a finger print of the public key, the hash of the policy and the signature (in base64) of the PCR policy. @@ -366,7 +366,7 @@ that. The main difference is in the way the signed policy is stored: --from eventlog \\ --output tpm2-pcr-signature.json \\ --policy-format systemd \\ - sign 0,2,4,7,9,11 + sign 0,2,4,7,9,12 .fi .P This will predict a set of PCR values, sign them using the specified @@ -384,9 +384,8 @@ These policies can then be enrolled for future system boots using # systemd-cryptenroll \\ --tpm2-device=auto \\ --tpm2-public-key=public-key.pem \\ - --tpm2-public-key-pcrs="0,2,4,7,9,11" \ + --tpm2-public-key-pcrs="0,2,4,7,9,12" \ --tpm2-signature=tpm2-pcr-signature.json \\ - --policy-format systemd \\ /dev/sda3 .fi .\" ################################################################## @@ -510,7 +509,7 @@ kernel image will be used on next boot. .BI --authorized-policy " path Specify the location of the authorized policy. In conjunction with the \fBcreate-authorized-policy\fP action, the newly created policy -is written to this location. For subsequent actions, such as \fBseal\fB, +is written to this location. For subsequent actions, such as \fBseal\fP, the policy will be read from this location. .TP .BI --private-key " path From c0011884d8685d4f59523e45f69bd77d2c113838 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 10:42:12 +0100 Subject: [PATCH 09/13] Several fixes resulting from review by alberto Signed-off-by: Olaf Kirch --- src/uapi.c | 4 +--- src/util.c | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uapi.c b/src/uapi.c index bbcaea9..a12ad21 100644 --- a/src/uapi.c +++ b/src/uapi.c @@ -123,8 +123,6 @@ uapi_boot_entry_applies(const uapi_boot_entry_t *entry, const char *machine_id, return false; if (entry->architecture && architecture && strcmp(entry->architecture, architecture)) return false; - /* We don't check the efi key because it can only express "entry is EFI only"; it cannot express - * "this is a legacy BIOS entry" */ return true; } @@ -147,7 +145,7 @@ uapi_boot_entry_more_recent(const uapi_boot_entry_t *entry_a, const uapi_boot_en /* * We pass in a best_ret pointer in case we need to extend this - * to search more than on directory + * to search more than one directory */ uapi_boot_entry_t * uapi_find_matching_boot_entry(const char *dir_path, diff --git a/src/util.c b/src/util.c index 644d138..7343ffb 100644 --- a/src/util.c +++ b/src/util.c @@ -526,7 +526,7 @@ read_single_line_file(const char *path, char *buffer, size_t size) } if (fgets(buffer, size, fp) != NULL) - buffer[strcspn(buffer, "\n")] = 0; + buffer[strcspn(buffer, "\n")] = '\0'; else buffer[0] = '\0'; From f0c09557aa72794c7149529056e6fc9ea1cd2763 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 10:45:15 +0100 Subject: [PATCH 10/13] When matching UAPI boot entries, check against all possible entry-tokens Signed-off-by: Olaf Kirch --- src/sd-boot.c | 87 +++++++++++++++++++++++++++++++++++++-------------- src/uapi.c | 50 ++++++++++++++++++++++++----- src/uapi.h | 11 ++++++- 3 files changed, 115 insertions(+), 33 deletions(-) diff --git a/src/sd-boot.c b/src/sd-boot.c index c878f6c..8dc9bf1 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -32,7 +32,6 @@ #include "sd-boot.h" #include "util.h" - static const char * read_entry_token(void) { @@ -100,6 +99,34 @@ read_machine_id(void) return read_single_line_file("/etc/machine-id", id, sizeof(id)); } +/* + * Kernels installed by kernel-install can use a variety of IDs as entry-token. + * Try to cater to all of them. + */ +static const uapi_kernel_entry_tokens_t * +get_valid_kernel_entry_tokens(void) +{ + static uapi_kernel_entry_tokens_t valid_tokens; + const char *token; + + if (valid_tokens.count != 0) + return &valid_tokens; /* I've been here before */ + + if ((token = read_entry_token()) != NULL) + uapi_kernel_entry_tokens_add(&valid_tokens, token); + + if ((token = read_machine_id()) != NULL) + uapi_kernel_entry_tokens_add(&valid_tokens, token); + + if ((token = read_os_release("ID")) != NULL) + uapi_kernel_entry_tokens_add(&valid_tokens, token); + + if ((token = read_os_release("IMAGE_ID")) != NULL) + uapi_kernel_entry_tokens_add(&valid_tokens, token); + + return &valid_tokens; +} + static bool exists_efi_dir(const char *path) { @@ -151,24 +178,28 @@ get_token_id(void) bool sdb_is_kernel(const char *application) { - const char *token_id; - const char *prefix = "linux-"; - char path[PATH_MAX]; - - if (!(token_id = get_token_id())) - goto fail; - - snprintf(path, PATH_MAX, "/%s/", token_id); - if (strncmp(path, application, strlen(path))) - goto fail; - - strncpy(path, application, PATH_MAX); - for (char *ptr = strtok(path, "/"); ptr; ptr = strtok(NULL, "/")) - if (!strncmp(ptr, prefix, strlen(prefix))) - return true; + static const char prefix[] = "linux-"; + const uapi_kernel_entry_tokens_t *match; + char *path_copy; + int found = 0; + + match = get_valid_kernel_entry_tokens(); + path_copy = strdup(application); + + for (char *ptr = strtok(path_copy, "/"); ptr; ptr = strtok(NULL, "/")) { + unsigned int i; + for (i = 0; i < match->count; ++i) { + const char *token = match->entry_token[i]; + + if (!strcmp(ptr, token)) + found |= 1; + else if (!strncmp(ptr, prefix, sizeof(prefix) - 1)) + found |= 2; + } + } -fail: - return false; + free(path_copy); + return (found == 3); } /* @@ -177,17 +208,25 @@ sdb_is_kernel(const char *application) uapi_boot_entry_t * sdb_identify_next_kernel(const char *id) { + uapi_kernel_entry_tokens_t id_match = { 0 }; + const uapi_kernel_entry_tokens_t *match; const char *machine_id; + uapi_boot_entry_t *result = NULL; - if (id == NULL || !strcasecmp(id, "default")) { - if (!(id = get_token_id())) - return NULL; + if (id == NULL || !strcasecmp(id, "auto")) { + match = get_valid_kernel_entry_tokens(); + } else { + uapi_kernel_entry_tokens_add(&id_match, id); + match = &id_match; } - if (!(machine_id = read_machine_id())) - return NULL; + machine_id = read_machine_id(); + + if (machine_id != NULL) + result = uapi_find_boot_entry(match, machine_id); - return uapi_find_boot_entry(id, machine_id); + uapi_kernel_entry_tokens_destroy(&id_match); + return result; } /* diff --git a/src/uapi.c b/src/uapi.c index a12ad21..acba37a 100644 --- a/src/uapi.c +++ b/src/uapi.c @@ -149,11 +149,10 @@ uapi_boot_entry_more_recent(const uapi_boot_entry_t *entry_a, const uapi_boot_en */ uapi_boot_entry_t * uapi_find_matching_boot_entry(const char *dir_path, - const char *entry_id, const char *machine_id, const char *architecture, + const uapi_kernel_entry_tokens_t *match, const char *machine_id, const char *architecture, uapi_boot_entry_t **best_ret) { uapi_boot_entry_t *best = *best_ret; - unsigned int entry_id_len = 0; struct dirent *d; DIR *dir; @@ -163,9 +162,6 @@ uapi_find_matching_boot_entry(const char *dir_path, return NULL; } - if (entry_id) - entry_id_len = strlen(entry_id); - while ((d = readdir(dir)) != NULL) { char config_path[PATH_MAX]; uapi_boot_entry_t *entry; @@ -173,7 +169,7 @@ uapi_find_matching_boot_entry(const char *dir_path, if (d->d_type != DT_REG) continue; - if (entry_id && strncmp(d->d_name, entry_id, entry_id_len)) + if (match && !uapi_kernel_entry_tokens_match_filename(match, d->d_name)) continue; snprintf(config_path, sizeof(config_path), "%s/%s", dir_path, d->d_name); @@ -201,7 +197,7 @@ uapi_find_matching_boot_entry(const char *dir_path, } uapi_boot_entry_t * -uapi_find_boot_entry(const char *id, const char *machine_id) +uapi_find_boot_entry(const uapi_kernel_entry_tokens_t *match, const char *machine_id) { uapi_boot_entry_t *best = NULL; struct utsname uts; @@ -211,7 +207,7 @@ uapi_find_boot_entry(const char *id, const char *machine_id) architecture = uts.machine; return uapi_find_matching_boot_entry(UAPI_BOOT_DIRECTORY, - id, machine_id, architecture, + match, machine_id, architecture, &best); } @@ -227,6 +223,44 @@ uapi_boot_entry_free(uapi_boot_entry_t *ube) free(ube); } +/* + * Manage list of valid entry-tokens + */ +void +uapi_kernel_entry_tokens_add(uapi_kernel_entry_tokens_t *match, const char *id) +{ + if (id == NULL) + return; + + if (match->count >= UAPI_MAX_ENTRY_TOKENS) + fatal("%s: too many tokens\n", __func__); + + match->entry_token[match->count++] = strdup(id); +} + +void +uapi_kernel_entry_tokens_destroy(uapi_kernel_entry_tokens_t *match) +{ + while (match->count) + drop_string(&match->entry_token[--(match->count)]); +} + +bool +uapi_kernel_entry_tokens_match_filename(const uapi_kernel_entry_tokens_t *token_list, const char *filename) +{ + unsigned int i; + + for (i = 0; i < token_list->count; ++i) { + const char *token = token_list->entry_token[i]; + int len = strlen(token); + + if (!strncmp(filename, token, len) && filename[len] == '-') + return true; + } + + return false; +} + /* * Version comparison */ diff --git a/src/uapi.h b/src/uapi.h index ba1147e..cc38395 100644 --- a/src/uapi.h +++ b/src/uapi.h @@ -35,9 +35,18 @@ struct uapi_boot_entry { char * options; }; +#define UAPI_MAX_ENTRY_TOKENS 8 +typedef struct uapi_kernel_entry_tokens { + unsigned int count; + char * entry_token[UAPI_MAX_ENTRY_TOKENS]; +} uapi_kernel_entry_tokens_t; + #define UAPI_BOOT_DIRECTORY "/boot/efi/loader/entries" -extern uapi_boot_entry_t * uapi_find_boot_entry(const char *id, const char *machine_id); +extern uapi_boot_entry_t * uapi_find_boot_entry(const uapi_kernel_entry_tokens_t *match, const char *machine_id); extern void uapi_boot_entry_free(uapi_boot_entry_t *); +extern void uapi_kernel_entry_tokens_add(uapi_kernel_entry_tokens_t *, const char *); +extern void uapi_kernel_entry_tokens_destroy(uapi_kernel_entry_tokens_t *); +extern bool uapi_kernel_entry_tokens_match_filename(const uapi_kernel_entry_tokens_t *, const char *filename); #endif /* UAPI_H */ From 8a0d1e8050340034e4690123c205a8b98abd7d24 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 10:46:20 +0100 Subject: [PATCH 11/13] Remove code that's been obsoleted by uapi_kernel_entry_tokens stuff Signed-off-by: Olaf Kirch --- src/sd-boot.c | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/sd-boot.c b/src/sd-boot.c index 8dc9bf1..3b72e37 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -127,51 +127,6 @@ get_valid_kernel_entry_tokens(void) return &valid_tokens; } -static bool -exists_efi_dir(const char *path) -{ - DIR *d = NULL; - char full_path[PATH_MAX]; - - if (path == NULL) - return false; - - snprintf(full_path, PATH_MAX, "/boot/efi/%s", path); - if (!(d = opendir(full_path))) - return false; - - closedir(d); - return true; -} - -static const char * -get_token_id(void) -{ - static const char *token_id = NULL; - const char *id = NULL; - const char *image_id = NULL; - const char *machine_id = NULL; - - /* All IDs are optional (cannot be present), except machine_id */ - token_id = read_entry_token(); - id = read_os_release("ID"); - image_id = read_os_release("IMAGE_ID"); - if (!(machine_id = read_machine_id())) - return NULL; - - /* The order is not correct, and it is using some heuristics - * to find the correct prefix. Other tools like sdbootutil - * seems to use parameters to decide */ - if (token_id == NULL && exists_efi_dir(id)) - token_id = id; - if (token_id == NULL && exists_efi_dir(image_id)) - token_id = id; - if (token_id == NULL && exists_efi_dir(machine_id)) - token_id = machine_id; - - return token_id; -} - /* * This should probably use UAPI boot entry logic as well */ From 07203a598a95a11afb4cf4a6f50bb92c7d4ae981 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 14:01:40 +0100 Subject: [PATCH 12/13] Fix an issue with not being able to find an entry referenced exactly by the boot id Signed-off-by: Olaf Kirch --- src/sd-boot.c | 8 ++++++++ src/uapi.c | 31 ++++++++++++++++++++++++++++++- src/uapi.h | 1 + 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/sd-boot.c b/src/sd-boot.c index 3b72e37..e3e0568 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -171,6 +171,13 @@ sdb_identify_next_kernel(const char *id) if (id == NULL || !strcasecmp(id, "auto")) { match = get_valid_kernel_entry_tokens(); } else { + /* Try to load the entry referenced _exactly_ by the + * given id. */ + result = uapi_get_boot_entry(id); + if (result != NULL) + goto done; + + /* No cigar, revert to a prefix based search */ uapi_kernel_entry_tokens_add(&id_match, id); match = &id_match; } @@ -180,6 +187,7 @@ sdb_identify_next_kernel(const char *id) if (machine_id != NULL) result = uapi_find_boot_entry(match, machine_id); +done: uapi_kernel_entry_tokens_destroy(&id_match); return result; } diff --git a/src/uapi.c b/src/uapi.c index acba37a..f0db62a 100644 --- a/src/uapi.c +++ b/src/uapi.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -196,6 +197,29 @@ uapi_find_matching_boot_entry(const char *dir_path, return best; } +/* + * Get the exact boot entry identified by id. + * This is mainly for iterating over entries like + * for name in "/boot/efi/loader/entries/"*.conf; do + * pcr-oracle --boot-entry $(basename $name .conf) ... + * done + */ +uapi_boot_entry_t * +uapi_get_boot_entry(const char *id) +{ + char path[PATH_MAX]; + + if (path_has_file_extension(id, ".conf")) + snprintf(path, sizeof(path), "%s/%s", UAPI_BOOT_DIRECTORY, id); + else + snprintf(path, sizeof(path), "%s/%s.conf", UAPI_BOOT_DIRECTORY, id); + + if (access(path, R_OK) < 0) + return NULL; + + return uapi_boot_entry_load(path); +} + uapi_boot_entry_t * uapi_find_boot_entry(const uapi_kernel_entry_tokens_t *match, const char *machine_id) { @@ -253,8 +277,13 @@ uapi_kernel_entry_tokens_match_filename(const uapi_kernel_entry_tokens_t *token_ for (i = 0; i < token_list->count; ++i) { const char *token = token_list->entry_token[i]; int len = strlen(token); + char cc; + + if (strncmp(filename, token, len)) + continue; - if (!strncmp(filename, token, len) && filename[len] == '-') + cc = filename[len]; + if (cc == '\0' || cc == '-' || cc == '.') return true; } diff --git a/src/uapi.h b/src/uapi.h index cc38395..96ca7ed 100644 --- a/src/uapi.h +++ b/src/uapi.h @@ -43,6 +43,7 @@ typedef struct uapi_kernel_entry_tokens { #define UAPI_BOOT_DIRECTORY "/boot/efi/loader/entries" +extern uapi_boot_entry_t * uapi_get_boot_entry(const char *id); extern uapi_boot_entry_t * uapi_find_boot_entry(const uapi_kernel_entry_tokens_t *match, const char *machine_id); extern void uapi_boot_entry_free(uapi_boot_entry_t *); extern void uapi_kernel_entry_tokens_add(uapi_kernel_entry_tokens_t *, const char *); From 0db6dae5c065944506255b92033314c97b45e4e4 Mon Sep 17 00:00:00 2001 From: Olaf Kirch Date: Tue, 28 Nov 2023 14:04:43 +0100 Subject: [PATCH 13/13] Rename --next-kernel to --boot-entry Signed-off-by: Olaf Kirch --- man/pcr-oracle.8.in | 4 ++-- src/efi-application.c | 4 ++-- src/eventlog.c | 22 +++++++++++----------- src/eventlog.h | 2 +- src/oracle.c | 26 +++++++++++++------------- src/sd-boot.c | 2 +- src/sd-boot.h | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/man/pcr-oracle.8.in b/man/pcr-oracle.8.in index 3682afa..86ac275 100644 --- a/man/pcr-oracle.8.in +++ b/man/pcr-oracle.8.in @@ -494,14 +494,14 @@ events (tracked in PCR9). In this case, it would be totally sufficient to stop processing after grub loaded \fPgrub.cfg\fP from the EFI System Partition. .TP -.BI --next-kernel " id +.BI --boot-entry " id In a systemd-boot environment, the values of PCR registers also depend on the contents of the kernel and initrd image, as well as the kernel command line options. After a kernel update or an initrd rebuild, these values will change. So when predicting PCR values in this case, \fBpcr-oracle\fP needs to be instructed to refer to the future kernel rather than the current one. .IP -This is what the \fB--next-kernel\fP option is for. It takes one argument, which +This is what the \fB--boot-entry\fP option is for. It takes one argument, which is either an ID (in the sense of systemd-boot IDs), or \fBauto\fP. When the latter is given, \fBpcr-oracle\fP will make a best guess as to what kernel image will be used on next boot. diff --git a/src/efi-application.c b/src/efi-application.c index 9d7e531..3e80083 100644 --- a/src/efi-application.c +++ b/src/efi-application.c @@ -291,8 +291,8 @@ __tpm_event_efi_bsa_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *pars } /* The next boot can have a different kernel */ - if (sdb_is_kernel(evspec->efi_application) && ctx->next_kernel) { - new_application = ctx->next_kernel->image_path; + if (sdb_is_kernel(evspec->efi_application) && ctx->boot_entry) { + new_application = ctx->boot_entry->image_path; if (new_application) { evspec_clone = *evspec; evspec_clone.efi_application = strdup(new_application); diff --git a/src/eventlog.c b/src/eventlog.c index 3498790..4277d42 100644 --- a/src/eventlog.c +++ b/src/eventlog.c @@ -789,24 +789,24 @@ __tpm_event_systemd_describe(const tpm_parsed_event_t *parsed) static const tpm_evdigest_t * __tpm_event_systemd_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx) { - const uapi_boot_entry_t *next_kernel = ctx->next_kernel; + const uapi_boot_entry_t *boot_entry = ctx->boot_entry; char initrd[2048]; char initrd_utf16[4096]; unsigned int len; /* If no --next-kernel option was given, do not rehash anything */ - if (next_kernel == NULL) + if (boot_entry == NULL) return tpm_event_get_digest(ev, ctx->algo); - if (!next_kernel->image_path) { + if (!boot_entry->image_path) { error("Unable to identify the next kernel\n"); return NULL; } - debug("Next boot entry expected from: %s %s\n", next_kernel->title, next_kernel->version? : ""); + debug("Next boot entry expected from: %s %s\n", boot_entry->title, boot_entry->version? : ""); snprintf(initrd, sizeof(initrd), "initrd=%s %s", - path_unix2dos(next_kernel->initrd_path), - next_kernel->options? : ""); + path_unix2dos(boot_entry->initrd_path), + boot_entry->options? : ""); len = (strlen(initrd) + 1) << 1; assert(len <= sizeof(initrd_utf16)); @@ -863,20 +863,20 @@ __tpm_event_tag_initrd_describe(const tpm_parsed_event_t *parsed) static const tpm_evdigest_t * __tpm_event_tag_initrd_rehash(const tpm_event_t *ev, const tpm_parsed_event_t *parsed, tpm_event_log_rehash_ctx_t *ctx) { - const uapi_boot_entry_t *next_kernel = ctx->next_kernel; + const uapi_boot_entry_t *boot_entry = ctx->boot_entry; /* If no --next-kernel option was given, do not rehash anything */ - if (next_kernel == NULL) + if (boot_entry == NULL) return tpm_event_get_digest(ev, ctx->algo); - if (!next_kernel->initrd_path) { + if (!boot_entry->initrd_path) { /* Can this happen eg when going from a split kernel to a unified kernel? */ error("Unable to identify the next initrd\n"); return NULL; } - debug("Next boot entry expected from: %s %s\n", next_kernel->title, next_kernel->version? : ""); - return runtime_digest_efi_file(ctx->algo, next_kernel->initrd_path); + debug("Next boot entry expected from: %s %s\n", boot_entry->title, boot_entry->version? : ""); + return runtime_digest_efi_file(ctx->algo, boot_entry->initrd_path); } /* diff --git a/src/eventlog.h b/src/eventlog.h index 65e5322..3741b58 100644 --- a/src/eventlog.h +++ b/src/eventlog.h @@ -202,7 +202,7 @@ typedef struct tpm_event_log_rehash_ctx { const pecoff_image_info_t *next_stage_img; /* This get set when the user specifies --next-kernel */ - uapi_boot_entry_t * next_kernel; + uapi_boot_entry_t * boot_entry; } tpm_event_log_rehash_ctx_t; #define GRUB_COMMAND_ARGV_MAX 32 diff --git a/src/oracle.c b/src/oracle.c index 2cc53c1..1cafafc 100644 --- a/src/oracle.c +++ b/src/oracle.c @@ -59,7 +59,7 @@ struct predictor { const char * initial_source; const char * tpm_event_log_path; - const char * next_kernel_id; + const char * boot_entry_id; const char * algo; const tpm_algo_info_t * algo_info; @@ -100,7 +100,7 @@ enum { OPT_POLICY_NAME, OPT_POLICY_FORMAT, OPT_TARGET_PLATFORM, - OPT_NEXT_KERNEL, + OPT_BOOT_ENTRY, }; static struct option options[] = { @@ -117,7 +117,7 @@ static struct option options[] = { { "before", no_argument, 0, OPT_BEFORE }, { "verify", required_argument, 0, OPT_VERIFY }, { "use-pesign", no_argument, 0, OPT_USE_PESIGN }, - { "next-kernel", required_argument, 0, OPT_NEXT_KERNEL }, + { "boot-entry", required_argument, 0, OPT_BOOT_ENTRY }, { "create-testcase", required_argument, 0, OPT_CREATE_TESTCASE }, { "replay-testcase", required_argument, 0, OPT_REPLAY_TESTCASE }, @@ -133,7 +133,7 @@ static struct option options[] = { { "policy-name", required_argument, 0, OPT_POLICY_NAME }, { "policy-format", required_argument, 0, OPT_POLICY_FORMAT }, { "target-platform", required_argument, 0, OPT_TARGET_PLATFORM }, - { "next-kernel", required_argument, 0, OPT_NEXT_KERNEL }, + { "next-kernel", required_argument, 0, OPT_BOOT_ENTRY }, { NULL } }; @@ -256,7 +256,7 @@ static struct predictor * predictor_new(const tpm_pcr_selection_t *pcr_selection, const char *source, const char *tpm_eventlog_path, const char *output_format, - const char *next_kernel_id) + const char *boot_entry_id) { struct predictor *pred; @@ -266,7 +266,7 @@ predictor_new(const tpm_pcr_selection_t *pcr_selection, const char *source, pred = calloc(1, sizeof(*pred)); pred->pcr_mask = pcr_selection->pcr_mask; pred->initial_source = source; - pred->next_kernel_id = next_kernel_id; + pred->boot_entry_id = boot_entry_id; pred->algo = pcr_selection->algo_info->openssl_name; pred->algo_info = pcr_selection->algo_info; @@ -662,9 +662,9 @@ predictor_update_eventlog(struct predictor *pred) * systemd ID of the next kernel entry to be booted. * FIXME: we should probably hide this behind a target_platform function. */ - if (pred->next_kernel_id != NULL - && !(rehash_ctx.next_kernel = sdb_identify_next_kernel(pred->next_kernel_id))) - fatal("unable to identify next kernel \"%s\"\n", pred->next_kernel_id); + if (pred->boot_entry_id != NULL + && !(rehash_ctx.boot_entry = sdb_identify_boot_entry(pred->boot_entry_id))) + fatal("unable to identify next kernel \"%s\"\n", pred->boot_entry_id); for (ev = pred->event_log; ev; ev = ev->next) { tpm_evdigest_t *pcr; @@ -1036,7 +1036,7 @@ main(int argc, char **argv) char *opt_rsa_bits = NULL; char *opt_policy_name = NULL; char *opt_target_platform = NULL; - char *opt_next_kernel = NULL; + char *opt_boot_entry = NULL; const target_platform_t *target; unsigned int action_flags = 0; unsigned int rsa_bits = 2048; @@ -1071,8 +1071,8 @@ main(int argc, char **argv) case OPT_USE_PESIGN: opt_use_pesign = 1; break; - case OPT_NEXT_KERNEL: - opt_next_kernel = optarg; + case OPT_BOOT_ENTRY: + opt_boot_entry = optarg; break; case OPT_STOP_EVENT: opt_stop_event = optarg; @@ -1315,7 +1315,7 @@ main(int argc, char **argv) fatal("BUG: action %u should have parsed a PCR selection argument", action); pred = predictor_new(pcr_selection, opt_from, opt_eventlog_path, - opt_output_format, opt_next_kernel); + opt_output_format, opt_boot_entry); if (opt_stop_event) predictor_set_stop_event(pred, opt_stop_event, !opt_stop_before); diff --git a/src/sd-boot.c b/src/sd-boot.c index e3e0568..ad13617 100644 --- a/src/sd-boot.c +++ b/src/sd-boot.c @@ -161,7 +161,7 @@ sdb_is_kernel(const char *application) * Identify the next kernel and initrd given an ID */ uapi_boot_entry_t * -sdb_identify_next_kernel(const char *id) +sdb_identify_boot_entry(const char *id) { uapi_kernel_entry_tokens_t id_match = { 0 }; const uapi_kernel_entry_tokens_t *match; diff --git a/src/sd-boot.h b/src/sd-boot.h index 2b11dc1..0472320 100644 --- a/src/sd-boot.h +++ b/src/sd-boot.h @@ -42,7 +42,7 @@ typedef struct sdb_entry_list { sdb_entry_data_t entries[SDB_MAX_ENTRIES]; } sdb_entry_list_t; -extern uapi_boot_entry_t * sdb_identify_next_kernel(const char *id); +extern uapi_boot_entry_t * sdb_identify_boot_entry(const char *id); extern bool sdb_is_kernel(const char *application); /* This will have to update the systemd json file, and add a new entry. */