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 1/3] 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; From 33061044685a449dd98c747a371c530d29afbf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 23:07:11 +0200 Subject: [PATCH 2/3] pcrlock: convert to the new option and verb parsers The VERB definitions are done in order to retain the logical presentation of verbs in lock+unlock pairs. Previously --help output was too wide, it now fits in 80 columns. Cosmetic changes in --help output only. Co-developed-by: Claude Opus 4.7 --- src/pcrlock/pcrlock.c | 343 ++++++++++++++++++++---------------------- 1 file changed, 159 insertions(+), 184 deletions(-) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 7820774fe0..62a84a26cb 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -38,6 +37,7 @@ #include "list.h" #include "main-func.h" #include "mkdir-label.h" +#include "options.h" #include "ordered-set.h" #include "parse-argument.h" #include "parse-util.h" @@ -2500,6 +2500,8 @@ static int event_log_load_and_process(EventLog **ret) { return 0; } +VERB(verb_show_log, "log", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show measurement log"); static int verb_show_log(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *log_table = NULL, *pcr_table = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -2613,6 +2615,8 @@ static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, sd_ return 0; } +VERB_NOARG(verb_show_cel, "cel", + "Show measurement log in TCG CEL-JSON format"); static int verb_show_cel(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; @@ -2648,6 +2652,8 @@ static int verb_show_cel(int argc, char *argv[], uintptr_t _data, void *userdata return 0; } +VERB_NOARG(verb_list_components, "list-components", + "List defined .pcrlock components"); static int verb_list_components(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; _cleanup_(table_unrefp) Table *table = NULL; @@ -3348,6 +3354,8 @@ static int tpm2_pcr_prediction_run( return 0; } +VERB_NOARG(verb_predict, "predict", + "Predict PCR values"); static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = { arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK, @@ -3927,6 +3935,8 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { return 1; /* installed new policy */ } +VERB_NOARG(verb_make_policy, "make-policy", + "Predict PCR values and generate TPM2 policy from it"); static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -4027,6 +4037,8 @@ static int remove_policy(void) { return ret; } +VERB_NOARG(verb_remove_policy, "remove-policy", + "Remove TPM2 policy"); static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return remove_policy(); } @@ -4061,6 +4073,8 @@ static int test_tpm2_support_pcrlock(Tpm2Support *ret) { return 0; } +VERB_NOARG(verb_is_supported, "is-supported", + "Tests if TPM2 supports necessary features"); static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) { int r; @@ -4140,6 +4154,10 @@ static bool event_log_record_is_separator(const EventLogRecord *rec) { return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */ } +VERB_GROUP("Protections"); + +VERB(verb_lock_firmware, "lock-firmware-code", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware code"); 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; @@ -4259,6 +4277,8 @@ static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *use return write_pcrlock(array_late, default_pcrlock_late_path); } +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-code", + "Remove .pcrlock file for firmware code"); 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; @@ -4285,6 +4305,14 @@ static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_lock_firmware, "lock-firmware-config", NULL, VERB_ANY, 2, 0, + "Generate a .pcrlock file from current firmware configuration"); + +VERB_NOARG(verb_unlock_firmware, "unlock-firmware-config", + "Remove .pcrlock file for firmware configuration"); + +VERB_NOARG(verb_lock_secureboot_policy, "lock-secureboot-policy", + "Generate a .pcrlock file from current SecureBoot policy"); static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct { sd_id128_t id; @@ -4358,6 +4386,8 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH); } +VERB_NOARG(verb_unlock_secureboot_policy, "unlock-secureboot-policy", + "Remove .pcrlock file for SecureBoot policy"); static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH); } @@ -4479,6 +4509,8 @@ static int event_log_ensure_secureboot_consistency(EventLog *el) { return 0; } +VERB_NOARG(verb_lock_secureboot_authority, "lock-secureboot-authority", + "Generate a .pcrlock file from current SecureBoot authority"); 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; @@ -4558,10 +4590,14 @@ static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _dat return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } +VERB_NOARG(verb_unlock_secureboot_authority, "unlock-secureboot-authority", + "Remove .pcrlock file for SecureBoot authority"); static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH); } +VERB(verb_lock_gpt, "lock-gpt", "[DISK]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from GPT header"); 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; @@ -4675,10 +4711,14 @@ static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata return write_pcrlock(array, PCRLOCK_GPT_PATH); } +VERB_NOARG(verb_unlock_gpt, "unlock-gpt", + "Remove .pcrlock file for GPT header"); static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_GPT_PATH); } +VERB(verb_lock_pe, "lock-pe", "[BINARY]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from PE binary"); 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; @@ -4734,6 +4774,8 @@ static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) return write_pcrlock(array, NULL); } +VERB_NOARG(verb_unlock_simple, "unlock-pe", + "Remove .pcrlock file for PE binary"); static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(NULL); } @@ -4747,6 +4789,8 @@ static void section_hashes_array_done(SectionHashArray *array) { free((*array)[i]); } +VERB(verb_lock_uki, "lock-uki", "[UKI]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from UKI PE binary"); 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 = {}; @@ -4842,6 +4886,11 @@ static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata return write_pcrlock(array, NULL); } +VERB_NOARG(verb_unlock_simple, "unlock-uki", + "Remove .pcrlock file for UKI PE binary"); + +VERB_NOARG(verb_lock_machine_id, "lock-machine-id", + "Generate a .pcrlock file from current machine ID"); 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; @@ -4862,6 +4911,8 @@ static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *u return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH); } +VERB_NOARG(verb_unlock_machine_id, "unlock-machine-id", + "Remove .pcrlock file for machine ID"); static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH); } @@ -4894,6 +4945,8 @@ static int pcrlock_file_system_path(const char *normalized_path, char **ret) { return 0; } +VERB(verb_lock_file_system, "lock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from current root fs + /var/"); static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -4947,6 +5000,8 @@ static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void * return 0; } +VERB(verb_unlock_file_system, "unlock-file-system", "[PATH]", VERB_ANY, 2, 0, + "Remove .pcrlock file for root fs + /var/"); static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) { const char* paths[3] = {}; int r; @@ -4977,6 +5032,8 @@ static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB(verb_lock_kernel_cmdline, "lock-kernel-cmdline", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from kernel command line"); 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; @@ -5014,10 +5071,14 @@ static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, voi return 0; } +VERB_NOARG(verb_unlock_kernel_cmdline, "unlock-kernel-cmdline", + "Remove .pcrlock file for kernel command line"); static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH); } +VERB(verb_lock_kernel_initrd, "lock-kernel-initrd", "FILE", VERB_ANY, 2, 0, + "Generate a .pcrlock file from an initrd file"); 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; @@ -5041,10 +5102,14 @@ static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void return 0; } +VERB_NOARG(verb_unlock_kernel_initrd, "unlock-kernel-initrd", + "Remove .pcrlock file for an initrd file"); static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) { return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH); } +VERB(verb_lock_raw, "lock-raw", "[FILE]", VERB_ANY, 2, 0, + "Generate a .pcrlock file from raw data"); 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; @@ -5068,162 +5133,108 @@ static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *commands = NULL, *protections = NULL, *options = NULL; int r; r = terminal_urlify_man("systemd-pcrlock", "8", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sManage a TPM2 PCR lock.%6$s\n" - "\n%3$sCommands:%4$s\n" - " log Show measurement log\n" - " cel Show measurement log in TCG CEL-JSON format\n" - " list-components List defined .pcrlock components\n" - " predict Predict PCR values\n" - " make-policy Predict PCR values and generate TPM2 policy from it\n" - " remove-policy Remove TPM2 policy\n" - " is-supported Tests if TPM2 supports necessary features\n" - "\n%3$sProtections:%4$s\n" - " lock-firmware-code Generate a .pcrlock file from current firmware code\n" - " unlock-firmware-code Remove .pcrlock file for firmware code\n" - " lock-firmware-config Generate a .pcrlock file from current firmware configuration\n" - " unlock-firmware-config Remove .pcrlock file for firmware configuration\n" - " lock-secureboot-policy Generate a .pcrlock file from current SecureBoot policy\n" - " unlock-secureboot-policy Remove .pcrlock file for SecureBoot policy\n" - " lock-secureboot-authority Generate a .pcrlock file from current SecureBoot authority\n" - " unlock-secureboot-authority Remove .pcrlock file for SecureBoot authority\n" - " lock-gpt [DISK] Generate a .pcrlock file from GPT header\n" - " unlock-gpt Remove .pcrlock file for GPT header\n" - " lock-pe [BINARY] Generate a .pcrlock file from PE binary\n" - " unlock-pe Remove .pcrlock file for PE binary\n" - " lock-uki [UKI] Generate a .pcrlock file from UKI PE binary\n" - " unlock-uki Remove .pcrlock file for UKI PE binary\n" - " lock-machine-id Generate a .pcrlock file from current machine ID\n" - " unlock-machine-id Remove .pcrlock file for machine ID\n" - " lock-file-system [PATH] Generate a .pcrlock file from current root fs + /var/\n" - " unlock-file-system [PATH] Remove .pcrlock file for root fs + /var/\n" - " lock-kernel-cmdline [FILE] Generate a .pcrlock file from kernel command line\n" - " unlock-kernel-cmdline Remove .pcrlock file for kernel command line\n" - " lock-kernel-initrd FILE Generate a .pcrlock file from an initrd file\n" - " unlock-kernel-initrd Remove .pcrlock file for an initrd file\n" - " lock-raw [FILE] Generate a .pcrlock file from raw data\n" - " unlock-raw Remove .pcrlock file for raw data\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " --json=pretty|short|off Generate JSON output\n" - " --raw-description Show raw firmware record data as description in table\n" - " --pcr=NR Generate .pcrlock for specified PCR\n" - " --nv-index=NUMBER Use the specified NV index, instead of a random one\n" - " --components=PATH Directory to read .pcrlock files from\n" - " --location=STRING[:STRING]\n" - " Do not process components beyond this component name\n" - " --recovery-pin=MODE Controls whether to show, hide, or ask for a recovery PIN\n" - " --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n" - " --policy=PATH JSON file to write policy output to\n" - " --force Write policy even if it matches existing policy\n" - " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" - " Boot entry token to use for this installation\n" - " -q --quiet Suppress unnecessary output\n" - "\nSee the %2$s for details.\n", + r = verbs_get_help_table(&commands); + if (r < 0) + return r; + + r = verbs_get_help_table_group("Protections", &protections); + if (r < 0) + return r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, commands, protections, options); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sManage a TPM2 PCR lock.%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(commands); + if (r < 0) + return r; + + printf("\n%sProtections:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(protections); + if (r < 0) + return r; + + printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal()); + r = table_print_or_warn(options); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_NOARG(verb_unlock_simple, "unlock-raw", + "Remove .pcrlock file for raw data"); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_JSON, - ARG_RAW_DESCRIPTION, - ARG_PCR, - ARG_NV_INDEX, - ARG_COMPONENTS, - ARG_LOCATION, - ARG_RECOVERY_PIN, - ARG_PCRLOCK, - ARG_POLICY, - ARG_FORCE, - ARG_ENTRY_TOKEN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "json", required_argument, NULL, ARG_JSON }, - { "raw-description", no_argument, NULL, ARG_RAW_DESCRIPTION }, - { "pcr", required_argument, NULL, ARG_PCR }, - { "nv-index", required_argument, NULL, ARG_NV_INDEX }, - { "components", required_argument, NULL, ARG_COMPONENTS }, - { "location", required_argument, NULL, ARG_LOCATION }, - { "recovery-pin", required_argument, NULL, ARG_RECOVERY_PIN }, - { "pcrlock", required_argument, NULL, ARG_PCRLOCK }, - { "policy", required_argument, NULL, ARG_POLICY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, - { "quiet", no_argument, NULL, 'q' }, - {} - }; - - bool auto_location = true; - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + bool auto_location = true; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_RAW_DESCRIPTION: + OPTION_LONG("raw-description", NULL, + "Show raw firmware record data as description in table"): arg_raw_description = true; break; - case ARG_PCR: { - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_pcr_mask); + OPTION_LONG("pcr", "NR", + "Generate .pcrlock for specified PCR"): + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_pcr_mask); if (r < 0) - return log_error_errno(r, "Failed to parse PCR specification: %s", optarg); - + return log_error_errno(r, "Failed to parse PCR specification: %s", arg); break; - } - case ARG_NV_INDEX: - if (isempty(optarg)) + OPTION_LONG("nv-index", "NUMBER", + "Use the specified NV index, instead of a random one"): + if (isempty(arg)) arg_nv_index = 0; else { uint32_t u; - r = safe_atou32_full(optarg, 16, &u); + r = safe_atou32_full(arg, 16, &u); if (r < 0) - return log_error_errno(r, "Failed to parse --nv-index= argument: %s", optarg); + return log_error_errno(r, "Failed to parse --nv-index= argument: %s", arg); if (u < TPM2_NV_INDEX_FIRST || u > TPM2_NV_INDEX_LAST) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument for --nv-index= outside of valid range 0x%" PRIx32 "…0x%" PRIx32 ": 0x%" PRIx32, @@ -5233,10 +5244,11 @@ static int parse_argv(int argc, char *argv[]) { } break; - case ARG_COMPONENTS: { + OPTION_LONG("components", "PATH", + "Directory to read .pcrlock files from"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -5247,21 +5259,22 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_LOCATION: { + OPTION_LONG("location", "START[:END]", + "Do not process components beyond this component name"): { _cleanup_free_ char *start = NULL, *end = NULL; const char *e; auto_location = false; - if (isempty(optarg)) { + if (isempty(arg)) { arg_location_start = mfree(arg_location_start); arg_location_end = mfree(arg_location_end); break; } - e = strchr(optarg, ':'); + e = strchr(arg, ':'); if (e) { - start = strndup(optarg, e - optarg); + start = strndup(arg, e - arg); if (!start) return log_oom(); @@ -5269,11 +5282,11 @@ static int parse_argv(int argc, char *argv[]) { if (!end) return log_oom(); } else { - start = strdup(optarg); + start = strdup(arg); if (!start) return log_oom(); - end = strdup(optarg); + end = strdup(arg); if (!end) return log_oom(); } @@ -5288,17 +5301,19 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_RECOVERY_PIN: - arg_recovery_pin = recovery_pin_mode_from_string(optarg); + OPTION_LONG("recovery-pin", "MODE", + "Controls whether to show, hide, or ask for a recovery PIN"): + arg_recovery_pin = recovery_pin_mode_from_string(arg); if (arg_recovery_pin < 0) - return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg); + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", arg); break; - case ARG_PCRLOCK: - if (empty_or_dash(optarg)) + OPTION_LONG("pcrlock", "PATH", + ".pcrlock file to write expected PCR measurement to"): + if (empty_or_dash(arg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_pcrlock_path); if (r < 0) return r; } @@ -5306,36 +5321,34 @@ static int parse_argv(int argc, char *argv[]) { arg_pcrlock_auto = false; break; - case ARG_POLICY: - if (empty_or_dash(optarg)) + OPTION_LONG("policy", "PATH", + "JSON file to write policy output to"): + if (empty_or_dash(arg)) arg_policy_path = mfree(arg_policy_path); else { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_policy_path); if (r < 0) return r; } break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Write policy even if it matches existing policy"): arg_force = true; break; - case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + OPTION_LONG("entry-token", "TOKEN", + "Boot entry token to use for this installation " + "(machine-id, os-id, os-image-id, auto, literal:…)"): + r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress unnecessary output"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (auto_location) { @@ -5359,49 +5372,10 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; } + *ret_args = option_parser_get_args(&state); return 1; } -static int pcrlock_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "log", VERB_ANY, 1, VERB_DEFAULT, verb_show_log }, - { "cel", VERB_ANY, 1, 0, verb_show_cel }, - { "list-components", VERB_ANY, 1, 0, verb_list_components }, - { "predict", VERB_ANY, 1, 0, verb_predict }, - { "lock-firmware-code", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-code", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-firmware-config", VERB_ANY, 2, 0, verb_lock_firmware }, - { "unlock-firmware-config", VERB_ANY, 1, 0, verb_unlock_firmware }, - { "lock-secureboot-policy", VERB_ANY, 1, 0, verb_lock_secureboot_policy }, - { "unlock-secureboot-policy", VERB_ANY, 1, 0, verb_unlock_secureboot_policy }, - { "lock-secureboot-authority", VERB_ANY, 1, 0, verb_lock_secureboot_authority }, - { "unlock-secureboot-authority", VERB_ANY, 1, 0, verb_unlock_secureboot_authority }, - { "lock-gpt", VERB_ANY, 2, 0, verb_lock_gpt }, - { "unlock-gpt", VERB_ANY, 1, 0, verb_unlock_gpt }, - { "lock-pe", VERB_ANY, 2, 0, verb_lock_pe }, - { "unlock-pe", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-uki", VERB_ANY, 2, 0, verb_lock_uki }, - { "unlock-uki", VERB_ANY, 1, 0, verb_unlock_simple }, - { "lock-machine-id", VERB_ANY, 1, 0, verb_lock_machine_id }, - { "unlock-machine-id", VERB_ANY, 1, 0, verb_unlock_machine_id }, - { "lock-file-system", VERB_ANY, 2, 0, verb_lock_file_system }, - { "unlock-file-system", VERB_ANY, 2, 0, verb_unlock_file_system }, - { "lock-kernel-cmdline", VERB_ANY, 2, 0, verb_lock_kernel_cmdline }, - { "unlock-kernel-cmdline", VERB_ANY, 1, 0, verb_unlock_kernel_cmdline }, - { "lock-kernel-initrd", VERB_ANY, 2, 0, verb_lock_kernel_initrd }, - { "unlock-kernel-initrd", VERB_ANY, 1, 0, verb_unlock_kernel_initrd }, - { "lock-raw", VERB_ANY, 2, 0, verb_lock_raw }, - { "unlock-raw", VERB_ANY, 1, 0, verb_unlock_simple }, - { "make-policy", VERB_ANY, 1, 0, verb_make_policy }, - { "remove-policy", VERB_ANY, 1, 0, verb_remove_policy }, - { "is-supported", VERB_ANY, 1, 0, verb_is_supported }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { _cleanup_(event_log_freep) EventLog *el = NULL; uint64_t recnum = 0; @@ -5493,7 +5467,8 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -5525,7 +5500,7 @@ static int run(int argc, char *argv[]) { return EXIT_SUCCESS; } - return pcrlock_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); From f5a99402d84cf44a14e6aedbba30333b28e94324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Apr 2026 23:29:42 +0200 Subject: [PATCH 3/3] test: enable check-{help,version}-systemd-pcrlock This is a normal user-facing program, so it should be tested in the usual fashion. --- src/pcrlock/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index ff2b0f0cb2..c5b609ed4a 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -16,6 +16,7 @@ executables += [ libopenssl, tpm2, ], + 'public' : true, }, ]