From ac2a0355fbf9b209cec4a4b8a41d512d10c17661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 19:22:11 +0200 Subject: [PATCH] pcrlock: reorder function definitions to match --help The order was random, different in the source code, different in the verb list, different in --help. Order source code by --help to make the next patch manageable. --- src/pcrlock/pcrlock.c | 1962 ++++++++++++++++++++--------------------- 1 file changed, 981 insertions(+), 981 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index fd8b9e95a4..7820774fe0 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -2968,987 +2968,6 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { return 0; } -static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - if (arg_pcr_mask == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); - - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } - - r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); - if (r < 0) - return r; - - return write_pcrlock(records, NULL); -} - -static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(NULL); -} - -static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - static const struct { - sd_id128_t id; - const char *name; - int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ - } variables[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, - { EFI_VENDOR_GLOBAL, "PK", 1 }, - { EFI_VENDOR_GLOBAL, "KEK", 1 }, - { EFI_VENDOR_DATABASE, "db", 1 }, - { EFI_VENDOR_DATABASE, "dbx", 1 }, - { EFI_VENDOR_DATABASE, "dbt", -1 }, - { EFI_VENDOR_DATABASE, "dbr", -1 }, - }; - - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - int r; - - /* Generates expected records from the current SecureBoot state, as readable in the EFI variables - * right now. */ - - FOREACH_ELEMENT(vv, variables) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; - - _cleanup_free_ char *name = NULL; - if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) - return log_oom(); - - _cleanup_free_ void *data = NULL; - size_t data_size; - r = efi_get_variable(name, NULL, &data, &data_size); - if (r < 0) { - if (r != -ENOENT || vv->synthesize_empty == 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); - if (vv->synthesize_empty < 0) - continue; - - /* If the main database variables are not set we don't consider this an error, but - * measure an empty database instead. */ - log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); - data_size = 0; - } - - _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); - if (!name16) - return log_oom(); - size_t name16_bytes = char16_strlen(name16) * 2; - - size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; - _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); - if (!vdata) - return log_oom(); - - *vdata = (UEFI_VARIABLE_DATA) { - .unicodeNameLength = name16_bytes / 2, - .variableDataLength = data_size, - }; - - efi_id128_to_guid(vv->id, vdata->variableName); - memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); - - r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); - if (r < 0) - return r; - - r = sd_json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - } - - return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); -} - -static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); -} - -static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { - _cleanup_free_ char *found_name = NULL; - sd_id128_t found_uuid; - int r; - - assert(rec); - assert(name); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; - - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; - - if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) - return false; - - r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); - if (r == -EBADMSG) - return false; - if (r < 0) - return r; - - if (!sd_id128_equal(found_uuid, uuid)) - return false; - - return streq(found_name, name); -} - -static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { - assert(rec); - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) - return false; - - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) - return false; - - return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; -} - -static int event_log_ensure_secureboot_consistency(EventLog *el) { - static const struct { - sd_id128_t id; - const char *name; - bool required; - } table[] = { - { EFI_VENDOR_GLOBAL, "SecureBoot", true }, - { EFI_VENDOR_GLOBAL, "PK", true }, - { EFI_VENDOR_GLOBAL, "KEK", true }, - { EFI_VENDOR_DATABASE, "db", true }, - { EFI_VENDOR_DATABASE, "dbx", true }, - { EFI_VENDOR_DATABASE, "dbt", false }, - { EFI_VENDOR_DATABASE, "dbr", false }, - // FIXME: ensure we also find the separator here - }; - - EventLogRecord *records[ELEMENTSOF(table)] = {}; - EventLogRecord *first_authority = NULL; - - assert(el); - - /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to - * ensure its state is actually consistent. */ - - FOREACH_ARRAY(rr, el->records, el->n_records) { - EventLogRecord *rec = *rr; - size_t found = SIZE_MAX; - - if (event_log_record_is_secureboot_authority(rec)) { - if (first_authority) - continue; - - first_authority = rec; - // FIXME: also check that each authority record's data is also listed in 'db' - continue; - } - - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { - found = i; - break; - } - if (found == SIZE_MAX) - continue; - - /* Require the authority records always come *after* database measurements */ - if (first_authority) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); - - /* Check for duplicates */ - if (records[found]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); - - /* Check for order */ - for (size_t j = found + 1; j < ELEMENTSOF(table); j++) - if (records[j]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); - - records[found] = rec; - } - - /* Check for existence */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) - if (table[i].required && !records[i]) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); - - /* At this point we know that all required variables have been measured, in the right order. */ - return 0; -} - -static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - int r; - - /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too - * much value in locking this down too much, since it stores only the result of the primary database - * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension - * card firmware validation will result in additional records here. */ - - if (!is_efi_secure_boot()) { - log_info("SecureBoot disabled, not generating authority .pcrlock file."); - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); - } - - el = event_log_new(); - if (!el) - return log_oom(); - - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; - - r = event_log_load(el); - if (r < 0) - return r; - - r = event_log_read_pcrs(el); - if (r < 0) - return r; - - r = event_log_calculate_pcrs(el); - if (r < 0) - return r; - - /* Before we base anything on the event log records, let's check that the event log state checks - * out. */ - - r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); - if (r < 0) - return r; - - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; - - r = event_log_ensure_secureboot_consistency(el); - if (r < 0) - return r; - - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; - - if (!event_log_record_is_secureboot_authority(rec)) - continue; - - log_debug("Locking down authority '%s'.", strna(rec->description)); - - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } - - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); - if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); - } - - return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); -} - -static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; - _cleanup_(sd_device_unrefp) sd_device *d = NULL; - uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ - uint64_t start, n_members, member_size; - _cleanup_close_ int fd = -EBADF; - const GptHeader *p; - size_t found = 0; - ssize_t n; - int r; - - r = block_device_new_from_path( - argc >= 2 ? argv[1] : "/", - BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, - &d); - if (r < 0) - return log_error_errno(r, "Failed to determine root block device: %m"); - - fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); - if (fd < 0) - return log_error_errno(fd, "Failed to open root block device: %m"); - - n = pread(fd, &h, sizeof(h), 0); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT header of block device: %m"); - if ((size_t) n != sizeof(h)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); - - /* Try a couple of sector sizes */ - for (size_t sz = 512; sz <= 4096; sz <<= 1) { - assert(sizeof(h) >= sz * 2); - p = (const GptHeader*) (h + sz); /* 2nd sector */ - - if (!gpt_header_has_signature(p)) - continue; - - if (found != 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Disk has partition table for multiple sector sizes, refusing."); - - found = sz; - } - - if (found == 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Disk does not have GPT partition table, refusing."); - - p = (const GptHeader*) (h + found); - - if (le32toh(p->header_size) > found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); - - start = le64toh(p->partition_entry_lba); - if (start > UINT64_MAX / found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table start offset overflow, refusing."); - - member_size = le32toh(p->size_of_partition_entry); - if (member_size < sizeof(GptPartitionEntry)) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition entry size too short, refusing."); - - n_members = le32toh(p->number_of_partition_entries); - uint64_t member_bufsz = n_members * member_size; - if (member_bufsz > 1U*1024U*1024U) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), - "Partition table size too large, refusing."); - - member_bufsz = ROUND_UP(member_bufsz, found); - - _cleanup_free_ void *members = malloc(member_bufsz); - if (!members) - return log_oom(); - - n = pread(fd, members, member_bufsz, start * found); - if (n < 0) - return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); - if ((size_t) n != member_bufsz) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); - - size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; - _cleanup_free_ void *vdata = malloc0(vdata_size); - if (!vdata) - return log_oom(); - - void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ - - void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); - - for (uint64_t i = 0; i < n_members; i++) { - const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); - - if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) - continue; - - qq = mempcpy(qq, entry, member_size); - unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); - } - - vdata_size = (uint8_t*) qq - (uint8_t*) vdata; - - r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); - if (r < 0) - return r; - - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - - return write_pcrlock(array, PCRLOCK_GPT_PATH); -} - -static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_GPT_PATH); -} - -static bool event_log_record_is_separator(const EventLogRecord *rec) { - assert(rec); - - /* Recognizes EV_SEPARATOR events */ - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->firmware_event_type != EV_SEPARATOR) - return false; - - return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ -} - -static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { - _cleanup_free_ char *d = NULL; - int r; - - assert(rec); - - /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - return false; - - if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) - return false; - - if (rec->firmware_event_type != EV_EFI_ACTION) - return false; - - if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ - return false; - - r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); - if (r < 0) - return r; - - return streq(d, "Calling EFI Application from Boot Option"); -} - -static void enable_json_sse(void) { - /* We shall write this to a single output stream? We have to output two files, hence try to be smart - * and enable JSON SSE */ - - if (!arg_pcrlock_path && arg_pcrlock_auto) - return; - - if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) - return; - - log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); - arg_json_format_flags |= SD_JSON_FORMAT_SSE; -} - -static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; - _cleanup_(event_log_freep) EventLog *el = NULL; - uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; - const char *default_pcrlock_early_path, *default_pcrlock_late_path; - int r; - - enable_json_sse(); - - /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config - * here – but the latter only until the "separator" events are seen, which tell us where transition - * into OS boot loader happens. This reflects the fact that on some systems the firmware already - * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ - if (endswith(argv[0], "firmware-code")) { - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ - - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ - - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - assert(endswith(argv[0], "firmware-config")); - always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ - (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ - - separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ - - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; - } - - el = event_log_new(); - if (!el) - return log_oom(); - - r = event_log_add_algorithms_from_environment(el); - if (r < 0) - return r; - - r = event_log_load(el); - if (r < 0) - return r; - - r = event_log_read_pcrs(el); - if (r < 0) - return r; - - r = event_log_calculate_pcrs(el); - if (r < 0) - return r; - - r = event_log_validate_record_hashes(el); - if (r < 0) - return r; - - /* Before we base anything on the event log records for any of the selected PCRs, let's check that - * the event log state checks out for them. */ - - r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); - if (r < 0) - return r; - - // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, - // and exactly once - - FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - EventLogRecord *rec = *rr; - uint32_t bit = UINT32_C(1) << rec->pcr; - - if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) - continue; - - if (!FLAGS_SET(always_mask, bit) && - !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) - continue; - - /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ - if (event_log_record_is_separator(rec)) { - separator_seen_mask |= bit; - continue; - } - - /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the - * same as a separator here, as that's where firmware passes control to boot loader. Note - * that some EFI implementations forget to generate one of them. */ - r = event_log_record_is_action_calling_efi_app(rec); - if (r < 0) - return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); - if (r > 0) { - action_seen_mask |= bit; - continue; - } - - LIST_FOREACH(banks, bank, rec->banks) { - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to build digests array: %m"); - } - - r = sd_json_variant_append_arraybo( - FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); - if (r < 0) - return log_error_errno(r, "Failed to build record array: %m"); - } - - r = write_pcrlock(array_early, default_pcrlock_early_path); - if (r < 0) - return r; - - return write_pcrlock(array_late, default_pcrlock_late_path); -} - -static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char *default_pcrlock_early_path, *default_pcrlock_late_path; - int r; - - if (endswith(argv[0], "firmware-code")) { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; - } else { - default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; - default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; - } - - r = unlink_pcrlock(default_pcrlock_early_path); - if (r < 0) - return r; - - if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ - return 0; - - r = unlink_pcrlock(default_pcrlock_late_path); - if (r < 0) - return r; - - return 0; -} - -static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *word = NULL; - int r; - - r = pcrextend_machine_id_word(&word); - if (r < 0) - return r; - - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); - if (r < 0) - return r; - - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); - - return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); -} - -static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); -} - -static int pcrlock_file_system_path(const char *normalized_path, char **ret) { - _cleanup_free_ char *s = NULL; - - assert(normalized_path); - assert(ret); - - if (path_equal(normalized_path, "/")) - s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); - else { - /* We reuse the escaping we use for turning paths into unit names */ - _cleanup_free_ char *escaped = NULL; - - assert(normalized_path[0] == '/'); - assert(normalized_path[1] != '/'); - - escaped = unit_name_escape(normalized_path + 1); - if (!escaped) - return log_oom(); - - s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); - } - if (!s) - return log_oom(); - - *ret = TAKE_PTR(s); - return 0; -} - -static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char* paths[3] = {}; - int r; - - if (argc > 1) - paths[0] = argv[1]; - else { - dev_t a, b; - paths[0] = "/"; - - r = get_block_device("/", &a); - if (r < 0) - return log_error_errno(r, "Failed to get device of root file system: %m"); - - r = get_block_device("/var", &b); - if (r < 0) - return log_error_errno(r, "Failed to get device of /var/ file system: %m"); - - /* if backing device is distinct, then measure /var/ too */ - if (a != b) - paths[1] = "/var"; - - enable_json_sse(); - } - - STRV_FOREACH(p, paths) { - _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - - r = pcrextend_file_system_word(*p, &word, &normalized_path); - if (r < 0) - return r; - - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; - - r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); - if (r < 0) - return r; - - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); - - r = write_pcrlock(array, pcrlock_file); - if (r < 0) - return r; - } - - return 0; -} - -static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { - const char* paths[3] = {}; - int r; - - if (argc > 1) - paths[0] = argv[1]; - else { - paths[0] = "/"; - paths[1] = "/var"; - } - - STRV_FOREACH(p, paths) { - _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; - - r = chase(*p, NULL, 0, &normalized_path, NULL); - if (r < 0) - return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); - - r = pcrlock_file_system_path(normalized_path, &pcrlock_file); - if (r < 0) - return r; - - r = unlink_pcrlock(pcrlock_file); - if (r < 0) - return r; - } - - return 0; -} - -static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; - _cleanup_close_ int fd = -EBADF; - int r; - - // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that - // covers this PE plus its hash, as alternatives under the same component name - - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } - - if (arg_pcr_mask == 0) - arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; - - for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; - - if (!BIT_SET(arg_pcr_mask, i)) - continue; - - FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { - _cleanup_free_ void *hash = NULL; - size_t hash_size; - const EVP_MD *md; - const char *a; - - assert_se(a = tpm2_hash_alg_to_string(*pa)); - assert_se(md = EVP_get_digestbyname(a)); - - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); - if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); - - r = sd_json_variant_append_arraybo( - &digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } - - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), - SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); - if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - } - - return write_pcrlock(array, NULL); -} - -typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; - -static void section_hashes_array_done(SectionHashArray *array) { - assert(array); - - for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) - free((*array)[i]); -} - -static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; - _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; - size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; - _cleanup_close_ int fd = -EBADF; - int r; - - if (arg_pcr_mask != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); - - if (argc >= 2) { - fd = open(argv[1], O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } - - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - _cleanup_free_ void *peh = NULL; - const EVP_MD *md; - const char *a; - - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - assert_se(md = EVP_get_digestbyname(a)); - - r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); - if (r < 0) - return log_error_errno(r, "Failed to hash PE binary: %m"); - - r = sd_json_variant_append_arraybo( - &pe_digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - - r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); - if (r < 0) - return log_error_errno(r, "Failed to UKI hash PE binary: %m"); - } - - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), - SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); - if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - - for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; - - if (!unified_section_measure(section)) - continue; - - for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { - const char *a; - void *hash; - - hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; - if (!hash) - continue; - - assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); - - r = sd_json_variant_append_arraybo( - §ion_digests, - SD_JSON_BUILD_PAIR_STRING("hashAlg", a), - SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); - if (r < 0) - return log_error_errno(r, "Failed to build JSON digest object: %m"); - } - - if (!section_digests) - continue; - - /* So we have digests for this section, hence generate a record for the section name first. */ - r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); - if (r < 0) - return r; - - r = sd_json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append JSON record array: %m"); - - /* And then append a record for the section contents digests as well */ - r = sd_json_variant_append_arraybo( - &array, - SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), - SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); - if (r < 0) - return log_error_errno(r, "Failed to append record object: %m"); - } - - return write_pcrlock(array, NULL); -} - -static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; - _cleanup_free_ char *cmdline = NULL; - int r; - - if (argc > 1) { - if (empty_or_dash(argv[1])) - r = read_full_stream(stdin, &cmdline, NULL); - else - r = read_full_file(argv[1], &cmdline, NULL); - } else - r = proc_cmdline(&cmdline); - if (r < 0) - return log_error_errno(r, "Failed to read cmdline: %m"); - - delete_trailing_chars(cmdline, "\n"); - - _cleanup_free_ char16_t *u = NULL; - u = utf8_to_utf16(cmdline, SIZE_MAX); - if (!u) - return log_oom(); - - r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); - if (r < 0) - return r; - - r = sd_json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); - - r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); - if (r < 0) - return r; - - return 0; -} - -static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); -} - -static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; - _cleanup_fclose_ FILE *f = NULL; - uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; - int r; - - if (argc >= 2) { - f = fopen(argv[1], "re"); - if (!f) - return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); - } - - r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); - if (r < 0) - return r; - - r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); - if (r < 0) - return r; - - return 0; -} - -static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { - return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); -} - static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { _cleanup_free_ char *dropped = NULL, *kept = NULL; @@ -5066,6 +4085,987 @@ static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *user return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK); } +static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) { + _cleanup_free_ char *d = NULL; + int r; + + assert(rec); + + /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */ + + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; + + if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE) + return false; + + if (rec->firmware_event_type != EV_EFI_ACTION) + return false; + + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */ + return false; + + r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d); + if (r < 0) + return r; + + return streq(d, "Calling EFI Application from Boot Option"); +} + +static void enable_json_sse(void) { + /* We shall write this to a single output stream? We have to output two files, hence try to be smart + * and enable JSON SSE */ + + if (!arg_pcrlock_path && arg_pcrlock_auto) + return; + + if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE)) + return; + + log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output."); + arg_json_format_flags |= SD_JSON_FORMAT_SSE; +} + +static bool event_log_record_is_separator(const EventLogRecord *rec) { + assert(rec); + + /* Recognizes EV_SEPARATOR events */ + + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; + + if (rec->firmware_event_type != EV_SEPARATOR) + return false; + + return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ +} + +static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; + uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0; + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; + + enable_json_sse(); + + /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config + * here – but the latter only until the "separator" events are seen, which tell us where transition + * into OS boot loader happens. This reflects the fact that on some systems the firmware already + * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */ + if (endswith(argv[0], "firmware-code")) { + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) | /* → 0 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE); /* → 2 */ + + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */ + + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + assert(endswith(argv[0], "firmware-config")); + always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) | /* → 1 */ + (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG); /* → 3 */ + + separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */ + + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } + + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_add_algorithms_from_environment(el); + if (r < 0) + return r; + + r = event_log_load(el); + if (r < 0) + return r; + + r = event_log_read_pcrs(el); + if (r < 0) + return r; + + r = event_log_calculate_pcrs(el); + if (r < 0) + return r; + + r = event_log_validate_record_hashes(el); + if (r < 0) + return r; + + /* Before we base anything on the event log records for any of the selected PCRs, let's check that + * the event log state checks out for them. */ + + r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask); + if (r < 0) + return r; + + // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries, + // and exactly once + + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; + uint32_t bit = UINT32_C(1) << rec->pcr; + + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + continue; + + if (!FLAGS_SET(always_mask, bit) && + !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit))) + continue; + + /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */ + if (event_log_record_is_separator(rec)) { + separator_seen_mask |= bit; + continue; + } + + /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the + * same as a separator here, as that's where firmware passes control to boot loader. Note + * that some EFI implementations forget to generate one of them. */ + r = event_log_record_is_action_calling_efi_app(rec); + if (r < 0) + return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m"); + if (r > 0) { + action_seen_mask |= bit; + continue; + } + + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to build digests array: %m"); + } + + r = sd_json_variant_append_arraybo( + FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); + } + + r = write_pcrlock(array_early, default_pcrlock_early_path); + if (r < 0) + return r; + + return write_pcrlock(array_late, default_pcrlock_late_path); +} + +static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char *default_pcrlock_early_path, *default_pcrlock_late_path; + int r; + + if (endswith(argv[0], "firmware-code")) { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH; + } else { + default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH; + default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH; + } + + r = unlink_pcrlock(default_pcrlock_early_path); + if (r < 0) + return r; + + if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */ + return 0; + + r = unlink_pcrlock(default_pcrlock_late_path); + if (r < 0) + return r; + + return 0; +} + +static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + static const struct { + sd_id128_t id; + const char *name; + int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */ + } variables[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", 0 }, + { EFI_VENDOR_GLOBAL, "PK", 1 }, + { EFI_VENDOR_GLOBAL, "KEK", 1 }, + { EFI_VENDOR_DATABASE, "db", 1 }, + { EFI_VENDOR_DATABASE, "dbx", 1 }, + { EFI_VENDOR_DATABASE, "dbt", -1 }, + { EFI_VENDOR_DATABASE, "dbr", -1 }, + }; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + int r; + + /* Generates expected records from the current SecureBoot state, as readable in the EFI variables + * right now. */ + + FOREACH_ELEMENT(vv, variables) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL; + + _cleanup_free_ char *name = NULL; + if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0) + return log_oom(); + + _cleanup_free_ void *data = NULL; + size_t data_size; + r = efi_get_variable(name, NULL, &data, &data_size); + if (r < 0) { + if (r != -ENOENT || vv->synthesize_empty == 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", name); + if (vv->synthesize_empty < 0) + continue; + + /* If the main database variables are not set we don't consider this an error, but + * measure an empty database instead. */ + log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name); + data_size = 0; + } + + _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX); + if (!name16) + return log_oom(); + size_t name16_bytes = char16_strlen(name16) * 2; + + size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size; + _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size); + if (!vdata) + return log_oom(); + + *vdata = (UEFI_VARIABLE_DATA) { + .unicodeNameLength = name16_bytes / 2, + .variableDataLength = data_size, + }; + + efi_id128_to_guid(vv->id, vdata->variableName); + memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size); + + r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); + } + + return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); +} + +static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); +} + +static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) { + _cleanup_free_ char *found_name = NULL; + sd_id128_t found_uuid; + int r; + + assert(rec); + assert(name); + + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; + + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; + + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; + + if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG) + return false; + + r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name); + if (r == -EBADMSG) + return false; + if (r < 0) + return r; + + if (!sd_id128_equal(found_uuid, uuid)) + return false; + + return streq(found_name, name); +} + +static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) { + assert(rec); + + if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec)) + return false; + + if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY) + return false; + + if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) + return false; + + return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY; +} + +static int event_log_ensure_secureboot_consistency(EventLog *el) { + static const struct { + sd_id128_t id; + const char *name; + bool required; + } table[] = { + { EFI_VENDOR_GLOBAL, "SecureBoot", true }, + { EFI_VENDOR_GLOBAL, "PK", true }, + { EFI_VENDOR_GLOBAL, "KEK", true }, + { EFI_VENDOR_DATABASE, "db", true }, + { EFI_VENDOR_DATABASE, "dbx", true }, + { EFI_VENDOR_DATABASE, "dbt", false }, + { EFI_VENDOR_DATABASE, "dbr", false }, + // FIXME: ensure we also find the separator here + }; + + EventLogRecord *records[ELEMENTSOF(table)] = {}; + EventLogRecord *first_authority = NULL; + + assert(el); + + /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to + * ensure its state is actually consistent. */ + + FOREACH_ARRAY(rr, el->records, el->n_records) { + EventLogRecord *rec = *rr; + size_t found = SIZE_MAX; + + if (event_log_record_is_secureboot_authority(rec)) { + if (first_authority) + continue; + + first_authority = rec; + // FIXME: also check that each authority record's data is also listed in 'db' + continue; + } + + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) { + found = i; + break; + } + if (found == SIZE_MAX) + continue; + + /* Require the authority records always come *after* database measurements */ + if (first_authority) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing."); + + /* Check for duplicates */ + if (records[found]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description); + + /* Check for order */ + for (size_t j = found + 1; j < ELEMENTSOF(table); j++) + if (records[j]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description); + + records[found] = rec; + } + + /* Check for existence */ + for (size_t i = 0; i < ELEMENTSOF(table); i++) + if (table[i].required && !records[i]) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name); + + /* At this point we know that all required variables have been measured, in the right order. */ + return 0; +} + +static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_(event_log_freep) EventLog *el = NULL; + int r; + + /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too + * much value in locking this down too much, since it stores only the result of the primary database + * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension + * card firmware validation will result in additional records here. */ + + if (!is_efi_secure_boot()) { + log_info("SecureBoot disabled, not generating authority .pcrlock file."); + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); + } + + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_add_algorithms_from_environment(el); + if (r < 0) + return r; + + r = event_log_load(el); + if (r < 0) + return r; + + r = event_log_read_pcrs(el); + if (r < 0) + return r; + + r = event_log_calculate_pcrs(el); + if (r < 0) + return r; + + /* Before we base anything on the event log records, let's check that the event log state checks + * out. */ + + r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY); + if (r < 0) + return r; + + r = event_log_validate_record_hashes(el); + if (r < 0) + return r; + + r = event_log_ensure_secureboot_consistency(el); + if (r < 0) + return r; + + FOREACH_ARRAY(rr, el->records, el->n_records) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + EventLogRecord *rec = *rr; + + if (!event_log_record_is_secureboot_authority(rec)) + continue; + + log_debug("Locking down authority '%s'.", strna(rec->description)); + + LIST_FOREACH(banks, bank, rec->banks) { + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to build digests array: %m"); + } + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to build record array: %m"); + } + + return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} + +static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); +} + +static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL; + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */ + uint64_t start, n_members, member_size; + _cleanup_close_ int fd = -EBADF; + const GptHeader *p; + size_t found = 0; + ssize_t n; + int r; + + r = block_device_new_from_path( + argc >= 2 ? argv[1] : "/", + BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING, + &d); + if (r < 0) + return log_error_errno(r, "Failed to determine root block device: %m"); + + fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY); + if (fd < 0) + return log_error_errno(fd, "Failed to open root block device: %m"); + + n = pread(fd, &h, sizeof(h), 0); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT header of block device: %m"); + if ((size_t) n != sizeof(h)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); + + /* Try a couple of sector sizes */ + for (size_t sz = 512; sz <= 4096; sz <<= 1) { + assert(sizeof(h) >= sz * 2); + p = (const GptHeader*) (h + sz); /* 2nd sector */ + + if (!gpt_header_has_signature(p)) + continue; + + if (found != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Disk has partition table for multiple sector sizes, refusing."); + + found = sz; + } + + if (found == 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Disk does not have GPT partition table, refusing."); + + p = (const GptHeader*) (h + found); + + if (le32toh(p->header_size) > found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size)); + + start = le64toh(p->partition_entry_lba); + if (start > UINT64_MAX / found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table start offset overflow, refusing."); + + member_size = le32toh(p->size_of_partition_entry); + if (member_size < sizeof(GptPartitionEntry)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition entry size too short, refusing."); + + n_members = le32toh(p->number_of_partition_entries); + uint64_t member_bufsz = n_members * member_size; + if (member_bufsz > 1U*1024U*1024U) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Partition table size too large, refusing."); + + member_bufsz = ROUND_UP(member_bufsz, found); + + _cleanup_free_ void *members = malloc(member_bufsz); + if (!members) + return log_oom(); + + n = pread(fd, members, member_bufsz, start * found); + if (n < 0) + return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); + if ((size_t) n != member_bufsz) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); + + size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; + _cleanup_free_ void *vdata = malloc0(vdata_size); + if (!vdata) + return log_oom(); + + void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */ + + void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t); + + for (uint64_t i = 0; i < n_members; i++) { + const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i)); + + if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid))) + continue; + + qq = mempcpy(qq, entry, member_size); + unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1); + } + + vdata_size = (uint8_t*) qq - (uint8_t*) vdata; + + r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record); + if (r < 0) + return r; + + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); + + return write_pcrlock(array, PCRLOCK_GPT_PATH); +} + +static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_GPT_PATH); +} + +static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that + // covers this PE plus its hash, as alternatives under the same component name + + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + } + + if (arg_pcr_mask == 0) + arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; + + for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL; + + if (!BIT_SET(arg_pcr_mask, i)) + continue; + + FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) { + _cleanup_free_ void *hash = NULL; + size_t hash_size; + const EVP_MD *md; + const char *a; + + assert_se(a = tpm2_hash_alg_to_string(*pa)); + assert_se(md = EVP_get_digestbyname(a)); + + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size); + if (r < 0) + return log_error_errno(r, "Failed to hash PE binary: %m"); + + r = sd_json_variant_append_arraybo( + &digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i), + SD_JSON_BUILD_PAIR_VARIANT("digests", digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); + } + + return write_pcrlock(array, NULL); +} + +static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(NULL); +} + +typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS]; + +static void section_hashes_array_done(SectionHashArray *array) { + assert(array); + + for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++) + free((*array)[i]); +} + +static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL; + _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {}; + size_t hash_sizes[TPM2_N_HASH_ALGORITHMS]; + _cleanup_close_ int fd = -EBADF; + int r; + + if (arg_pcr_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down."); + + if (argc >= 2) { + fd = open(argv[1], O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + } + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + _cleanup_free_ void *peh = NULL; + const EVP_MD *md; + const char *a; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = EVP_get_digestbyname(a)); + + r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i); + if (r < 0) + return log_error_errno(r, "Failed to hash PE binary: %m"); + + r = sd_json_variant_append_arraybo( + &pe_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i]))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + + r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i); + if (r < 0) + return log_error_errno(r, "Failed to UKI hash PE binary: %m"); + } + + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE), + SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); + + for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL; + + if (!unified_section_measure(section)) + continue; + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + void *hash; + + hash = section_hashes[i * _UNIFIED_SECTION_MAX + section]; + if (!hash) + continue; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + + r = sd_json_variant_append_arraybo( + §ion_digests, + SD_JSON_BUILD_PAIR_STRING("hashAlg", a), + SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i]))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } + + if (!section_digests) + continue; + + /* So we have digests for this section, hence generate a record for the section name first. */ + r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record); + if (r < 0) + return r; + + r = sd_json_variant_append_array(&array, record); + if (r < 0) + return log_error_errno(r, "Failed to append JSON record array: %m"); + + /* And then append a record for the section contents digests as well */ + r = sd_json_variant_append_arraybo( + &array, + SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests)); + if (r < 0) + return log_error_errno(r, "Failed to append record object: %m"); + } + + return write_pcrlock(array, NULL); +} + +static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *word = NULL; + int r; + + r = pcrextend_machine_id_word(&word); + if (r < 0) + return r; + + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; + + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); + + return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); +} + +static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); +} + +static int pcrlock_file_system_path(const char *normalized_path, char **ret) { + _cleanup_free_ char *s = NULL; + + assert(normalized_path); + assert(ret); + + if (path_equal(normalized_path, "/")) + s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH); + else { + /* We reuse the escaping we use for turning paths into unit names */ + _cleanup_free_ char *escaped = NULL; + + assert(normalized_path[0] == '/'); + assert(normalized_path[1] != '/'); + + escaped = unit_name_escape(normalized_path + 1); + if (!escaped) + return log_oom(); + + s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock"); + } + if (!s) + return log_oom(); + + *ret = TAKE_PTR(s); + return 0; +} + +static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; + int r; + + if (argc > 1) + paths[0] = argv[1]; + else { + dev_t a, b; + paths[0] = "/"; + + r = get_block_device("/", &a); + if (r < 0) + return log_error_errno(r, "Failed to get device of root file system: %m"); + + r = get_block_device("/var", &b); + if (r < 0) + return log_error_errno(r, "Failed to get device of /var/ file system: %m"); + + /* if backing device is distinct, then measure /var/ too */ + if (a != b) + paths[1] = "/var"; + + enable_json_sse(); + } + + STRV_FOREACH(p, paths) { + _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + + r = pcrextend_file_system_word(*p, &word, &normalized_path); + if (r < 0) + return r; + + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); + if (r < 0) + return r; + + r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record); + if (r < 0) + return r; + + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); + + r = write_pcrlock(array, pcrlock_file); + if (r < 0) + return r; + } + + return 0; +} + +static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { + const char* paths[3] = {}; + int r; + + if (argc > 1) + paths[0] = argv[1]; + else { + paths[0] = "/"; + paths[1] = "/var"; + } + + STRV_FOREACH(p, paths) { + _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL; + + r = chase(*p, NULL, 0, &normalized_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]); + + r = pcrlock_file_system_path(normalized_path, &pcrlock_file); + if (r < 0) + return r; + + r = unlink_pcrlock(pcrlock_file); + if (r < 0) + return r; + } + + return 0; +} + +static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL; + _cleanup_free_ char *cmdline = NULL; + int r; + + if (argc > 1) { + if (empty_or_dash(argv[1])) + r = read_full_stream(stdin, &cmdline, NULL); + else + r = read_full_file(argv[1], &cmdline, NULL); + } else + r = proc_cmdline(&cmdline); + if (r < 0) + return log_error_errno(r, "Failed to read cmdline: %m"); + + delete_trailing_chars(cmdline, "\n"); + + _cleanup_free_ char16_t *u = NULL; + u = utf8_to_utf16(cmdline, SIZE_MAX); + if (!u) + return log_oom(); + + r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record); + if (r < 0) + return r; + + r = sd_json_variant_new_array(&array, &record, 1); + if (r < 0) + return log_error_errno(r, "Failed to create record array: %m"); + + r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH); + if (r < 0) + return r; + + return 0; +} + +static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); +} + +static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; + uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; + int r; + + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + } + + r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); + if (r < 0) + return r; + + r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); + if (r < 0) + return r; + + return 0; +} + +static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { + return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); +} + +static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + if (arg_pcr_mask == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing."); + + if (argc >= 2) { + f = fopen(argv[1], "re"); + if (!f) + return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); + } + + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); + if (r < 0) + return r; + + return write_pcrlock(records, NULL); +} + static int help(void) { _cleanup_free_ char *link = NULL; int r;