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.");
+static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) {
+ _cleanup_free_ char *dropped = NULL, *kept = NULL;
- if (argc >= 2) {
- f = fopen(argv[1], "re");
- if (!f)
- return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
- }
+ assert(el);
+ assert(pcrs);
- r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records);
- if (r < 0)
- return r;
+ /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three
+ * reasons:
+ *
+ * 1. The PCR value doesn't match the event log
+ * 2. The event log for the PCR contains measurements we don't know responsible components for
+ * 3. The event log for the PCR does not contain measurements for components we know
+ *
+ * This function checks for the three conditions and drops the PCR from the mask.
+ */
- return write_pcrlock(records, NULL);
-}
+ for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
-static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return unlink_pcrlock(NULL);
-}
+ if (!BIT_SET(*pcrs, pcr))
+ continue;
-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 },
- };
+ if (!event_log_pcr_checks_out(el, el->registers + pcr)) {
+ log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+ goto drop;
+ }
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
- int r;
+ if (!el->registers[pcr].fully_recognized) {
+ log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+ goto drop;
+ }
- /* Generates expected records from the current SecureBoot state, as readable in the EFI variables
- * right now. */
+ if (BIT_SET(el->missing_component_pcrs, pcr)) {
+ log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+ goto drop;
+ }
- FOREACH_ELEMENT(vv, variables) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL;
+ log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
- _cleanup_free_ char *name = NULL;
- if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0)
+ if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 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;
- }
+ continue;
- _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;
+ drop:
+ *pcrs &= ~(UINT32_C(1) << pcr);
- size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size;
- _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size);
- if (!vdata)
+ if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
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;
+ if (dropped)
+ log_notice("PCRs dropped from protection mask: %s", dropped);
+ else
+ log_debug("No PCRs dropped from protection mask.");
- r = sd_json_variant_append_array(&array, record);
- if (r < 0)
- return log_error_errno(r, "Failed to append to JSON array: %m");
- }
+ if (kept)
+ log_notice("PCRs in protection mask: %s", kept);
+ else
+ log_notice("No PCRs kept in protection mask.");
- return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH);
+ return 0;
}
-static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH);
-}
+static int pcr_prediction_add_result(
+ Tpm2PCRPrediction *context,
+ Tpm2PCRPredictionResult *result,
+ uint32_t pcr,
+ const char *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;
+ _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL;
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;
+ assert(context);
+ assert(result);
- if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG)
- return false;
+ copy = newdup(Tpm2PCRPredictionResult, result, 1);
+ if (!copy)
+ return log_oom();
- r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name);
- if (r == -EBADMSG)
- return false;
+ r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy);
+ if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */
+ return 0;
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to insert result into set: %m");
- if (!sd_id128_equal(found_uuid, uuid))
- return false;
+ log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path));
- return streq(found_name, name);
+ TAKE_PTR(copy);
+ return 0;
}
-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;
+static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) {
+ const char *name;
- if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
- return false;
+ name = tpm2_hash_alg_to_string(alg);
+ if (!name)
+ return NULL;
- return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY;
+ return EVP_get_digestbyname(name);
}
-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);
+static int event_log_component_variant_calculate(
+ Tpm2PCRPredictionResult *result,
+ EventLogComponentVariant *variant,
+ uint32_t pcr) {
- /* 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. */
+ assert(result);
+ assert(variant);
- FOREACH_ARRAY(rr, el->records, el->n_records) {
+ FOREACH_ARRAY(rr, variant->records, variant->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'
+ if (!EVENT_LOG_RECORD_IS_PCR(rec))
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)
+ if (rec->pcr != pcr)
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.");
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
+ EventLogRecordBank *b;
- /* Check for duplicates */
- if (records[found])
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description);
+ if (result->hash[i].size <= 0) /* already invalidated */
+ continue;
- /* 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);
+ b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]);
+ if (!b) {
+ /* Can't calculate, hence invalidate */
+ result->hash[i] = (TPM2B_DIGEST) {};
+ continue;
+ }
- records[found] = rec;
- }
+ md_ctx = EVP_MD_CTX_new();
+ if (!md_ctx)
+ return log_oom();
- /* 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;
+ const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i]));
- /* 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. */
+ int sz = EVP_MD_size(md);
+ assert(sz > 0);
+ assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer));
- if (!is_efi_secure_boot()) {
- log_info("SecureBoot disabled, not generating authority .pcrlock file.");
- return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
- }
+ assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]));
- el = event_log_new();
- if (!el)
- return log_oom();
+ assert(result->hash[i].size == (size_t) sz);
+ assert(b->hash.size == (size_t) sz);
- r = event_log_add_algorithms_from_environment(el);
- if (r < 0)
- return r;
+ if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest.");
- r = event_log_load(el);
- if (r < 0)
- return r;
+ if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value.");
- r = event_log_read_pcrs(el);
- if (r < 0)
- return r;
+ if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value.");
- r = event_log_calculate_pcrs(el);
- if (r < 0)
- return r;
+ unsigned l = (unsigned) sz;
+ if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest.");
- /* Before we base anything on the event log records, let's check that the event log state checks
- * out. */
+ assert(l == (unsigned) sz);
+ }
+ }
- r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY);
- if (r < 0)
- return r;
+ return 0;
+}
- r = event_log_validate_record_hashes(el);
- if (r < 0)
- return r;
+static int event_log_predict_pcrs(
+ EventLog *el,
+ Tpm2PCRPrediction *context,
+ Tpm2PCRPredictionResult *parent_result,
+ size_t component_index,
+ uint32_t pcr,
+ const char *path) {
- r = event_log_ensure_secureboot_consistency(el);
- if (r < 0)
- return r;
+ EventLogComponent *component;
+ int count = 0, r;
- FOREACH_ARRAY(rr, el->records, el->n_records) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
- EventLogRecord *rec = *rr;
+ assert(el);
+ assert(context);
+ assert(parent_result);
- if (!event_log_record_is_secureboot_authority(rec))
- continue;
+ /* Check if we reached the end of the components, generate a result, and backtrack */
+ if (component_index >= el->n_components ||
+ (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) {
+ r = pcr_prediction_add_result(context, parent_result, pcr, path);
+ if (r < 0)
+ return r;
- log_debug("Locking down authority '%s'.", strna(rec->description));
+ return 1;
+ }
- 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");
- }
+ component = ASSERT_PTR(el->components[component_index]);
- r = sd_json_variant_append_arraybo(
- &array,
- SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr),
- SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
+ /* Check if we are just about to process a component after start, if so record a result and continue. */
+ if (arg_location_start && strcmp(component->id, arg_location_start) > 0) {
+ r = pcr_prediction_add_result(context, parent_result, pcr, path);
if (r < 0)
- return log_error_errno(r, "Failed to build record array: %m");
+ return r;
}
- 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;
+ FOREACH_ARRAY(ii, component->variants, component->n_variants) {
+ _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
+ EventLogComponentVariant *variant = *ii;
+ _cleanup_free_ char *subpath = NULL;
- 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");
+ /* Operate on a copy of the result */
- 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");
+ if (path)
+ subpath = strjoin(path, ":", component->id);
+ else
+ subpath = strdup(component->id);
+ if (!subpath)
+ return log_oom();
- 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.");
+ if (!streq(component->id, variant->id))
+ if (!strextend(&subpath, "@", variant->id))
+ return log_oom();
- /* 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 */
+ result = newdup(Tpm2PCRPredictionResult, parent_result, 1);
+ if (!result)
+ return log_oom();
- if (!gpt_header_has_signature(p))
- continue;
+ r = event_log_component_variant_calculate(
+ result,
+ variant,
+ pcr);
+ if (r < 0)
+ return r;
- if (found != 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
- "Disk has partition table for multiple sector sizes, refusing.");
+ r = event_log_predict_pcrs(
+ el,
+ context,
+ result,
+ component_index + 1, /* Next component */
+ pcr,
+ subpath);
+ if (r < 0)
+ return r;
- found = sz;
+ count += r;
}
- if (found == 0)
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
- "Disk does not have GPT partition table, refusing.");
+ return count;
+}
- p = (const GptHeader*) (h + found);
+static ssize_t event_log_calculate_component_combinations(EventLog *el) {
+ ssize_t count = 1;
+ assert(el);
- if (le32toh(p->header_size) > found)
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
- "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size));
+ FOREACH_ARRAY(cc, el->components, el->n_components) {
+ EventLogComponent *c = *cc;
- start = le64toh(p->partition_entry_lba);
- if (start > UINT64_MAX / found)
- return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
- "Partition table start offset overflow, refusing.");
+ assert(c->n_variants > 0);
- 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.");
+ /* Overflow check */
+ if (c->n_variants > (size_t) (SSIZE_MAX/count))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations.");
+ count *= c->n_variants;
+ }
- 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.");
+ return count;
+}
- member_bufsz = ROUND_UP(member_bufsz, found);
+static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) {
+ int r;
- _cleanup_free_ void *members = malloc(member_bufsz);
- if (!members)
- return log_oom();
+ assert(context);
- 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.");
+ pager_open(arg_pager_flags);
- 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();
+ if (sd_json_format_enabled(arg_json_format_flags)) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
- void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
- void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t);
+ r = tpm2_pcr_prediction_to_json(
+ context,
+ tpm2_hash_algorithms[i],
+ &aj);
+ if (r < 0)
+ return r;
- for (uint64_t i = 0; i < n_members; i++) {
- const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i));
+ if (sd_json_variant_elements(aj) == 0)
+ continue;
- if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid)))
- continue;
+ r = sd_json_variant_set_field(
+ &j,
+ tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]),
+ aj);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add prediction bank to object: %m");
+ }
- qq = mempcpy(qq, entry, member_size);
- unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1);
+ if (!j) {
+ r = sd_json_variant_new_object(&j, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocated empty object: %m");
+ }
+
+ sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
+ return 0;
}
- vdata_size = (uint8_t*) qq - (uint8_t*) vdata;
+ for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+ Tpm2PCRPredictionResult *result;
+ if (!BIT_SET(context->pcrs, pcr))
+ continue;
- r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record);
- if (r < 0)
- return r;
+ if (ordered_set_isempty(context->results[pcr])) {
+ printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr));
+ continue;
+ }
- r = sd_json_variant_new_array(&array, &record, 1);
- if (r < 0)
- return log_error_errno(r, "Failed to append to JSON array: %m");
+ printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal());
- return write_pcrlock(array, PCRLOCK_GPT_PATH);
-}
+ ORDERED_SET_FOREACH(result, context->results[pcr]) {
-static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return unlink_pcrlock(PCRLOCK_GPT_PATH);
-}
+ _cleanup_free_ char *aa = NULL, *h = NULL;
+ const char *a;
-static bool event_log_record_is_separator(const EventLogRecord *rec) {
- assert(rec);
+ TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg);
+ if (!hash)
+ continue;
- /* Recognizes EV_SEPARATOR events */
+ a = ASSERT_PTR(tpm2_hash_alg_to_string(alg));
+ aa = strdup(a);
+ if (!aa)
+ return log_oom();
- if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
- return false;
+ ascii_strlower(aa);
- if (rec->firmware_event_type != EV_SEPARATOR)
- return false;
+ h = hexmem(hash->buffer, hash->size);
+ if (!h)
+ return log_oom();
- return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */
+ printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h);
+ }
+ }
+
+ return 0;
}
-static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) {
- _cleanup_free_ char *d = NULL;
- int r;
+static int tpm2_pcr_prediction_run(
+ EventLog *el,
+ Tpm2PCRPrediction *context) {
- assert(rec);
+ int r;
- /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */
+ assert(el);
+ assert(context);
- if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
- return false;
+ for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+ _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
- if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE)
- return false;
+ if (!BIT_SET(context->pcrs, pcr))
+ continue;
- if (rec->firmware_event_type != EV_EFI_ACTION)
- return false;
+ result = new0(Tpm2PCRPredictionResult, 1);
+ if (!result)
+ return log_oom();
- if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */
- return false;
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
+ event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i);
- r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d);
- if (r < 0)
- return r;
+ r = event_log_predict_pcrs(
+ el,
+ context,
+ result,
+ /* component_index= */ 0,
+ pcr,
+ /* path= */ NULL);
+ if (r < 0)
+ return r;
+ }
- return streq(d, "Calling EFI Application from Boot Option");
+ return 0;
}
-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;
+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,
+ };
+ _cleanup_(event_log_freep) EventLog *el = NULL;
+ ssize_t count;
+ int r;
- if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE))
- return;
+ r = event_log_load_and_process(&el);
+ if (r < 0)
+ return r;
- log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output.");
- arg_json_format_flags |= SD_JSON_FORMAT_SSE;
-}
+ count = event_log_calculate_component_combinations(el);
+ if (count < 0)
+ return count;
-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;
+ log_info("%zi combinations of components.", count);
- enable_json_sse();
+ r = event_log_reduce_to_safe_pcrs(el, &context.pcrs);
+ if (r < 0)
+ return r;
- /* 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 */
+ r = tpm2_pcr_prediction_run(el, &context);
+ if (r < 0)
+ return r;
- separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */
+ return event_log_show_predictions(&context, el->primary_algorithm);
+}
- 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 */
+static int remove_policy_file(const char *path) {
+ assert(path);
- separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */
+ if (unlink(path) < 0) {
+ if (errno == ENOENT)
+ return 0;
- default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
- default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
+ return log_error_errno(errno, "Failed to remove policy file '%s': %m", path);
}
- el = event_log_new();
- if (!el)
- return log_oom();
+ log_info("Removed policy file '%s'.", path);
+ return 1;
+}
- r = event_log_add_algorithms_from_environment(el);
- if (r < 0)
- return r;
+static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) {
+ int r;
- r = event_log_load(el);
+ _cleanup_free_ char *path = NULL;
+ r = get_global_boot_credentials_path(&path);
if (r < 0)
return r;
+ if (r == 0) {
+ if (ret_path)
+ *ret_path = NULL;
+ if (ret_credential_name)
+ *ret_credential_name = NULL;
+ return 0; /* not found! */
+ }
- r = event_log_read_pcrs(el);
+ sd_id128_t machine_id;
+ r = sd_id128_get_machine(&machine_id);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to read machine ID: %m");
- r = event_log_calculate_pcrs(el);
+ r = boot_entry_token_ensure(
+ /* root= */ NULL,
+ /* conf_root= */ NULL,
+ machine_id,
+ /* machine_id_is_random= */ false,
+ &arg_entry_token_type,
+ &arg_entry_token);
if (r < 0)
return r;
- r = event_log_validate_record_hashes(el);
- if (r < 0)
- return r;
+ _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred");
+ if (!fn)
+ return log_oom();
- /* 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. */
+ if (!filename_is_valid(fn))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn);
- r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask);
- if (r < 0)
- return r;
+ _cleanup_free_ char *joined = NULL;
+ if (ret_path) {
+ joined = path_join(path, fn);
+ if (!joined)
+ return log_oom();
+ }
- // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries,
- // and exactly once
+ _cleanup_free_ char *cn = NULL;
+ if (ret_credential_name) {
+ /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from
+ * the embedded credential name. */
+ cn = strjoin("pcrlock.", arg_entry_token);
+ if (!cn)
+ return log_oom();
- 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;
+ ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want
+ * to run into case change incompatibilities */
+ }
- if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
- continue;
+ if (ret_path)
+ *ret_path = TAKE_PTR(joined);
- if (!FLAGS_SET(always_mask, bit) &&
- !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit)))
- continue;
+ if (ret_credential_name)
+ *ret_credential_name = TAKE_PTR(cn);
- /* 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;
- }
+ return 1; /* found! */
+}
- /* 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");
- }
+static int write_boot_policy_file(const char *json_text) {
+ _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL;
+ int r;
- 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");
- }
+ assert(json_text);
- r = write_pcrlock(array_early, default_pcrlock_early_path);
+ r = determine_boot_policy_file(&boot_policy_file, &credential_name);
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;
+ if (r == 0) {
+ log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file.");
+ return 0;
}
- r = unlink_pcrlock(default_pcrlock_early_path);
+ _cleanup_(iovec_done) struct iovec encoded = {};
+ r = encrypt_credential_and_warn(
+ CRED_AES256_GCM_BY_NULL,
+ credential_name,
+ now(CLOCK_REALTIME),
+ /* not_after= */ USEC_INFINITY,
+ /* tpm2_device= */ NULL,
+ /* tpm2_hash_pcr_mask= */ 0,
+ /* tpm2_pubkey_path= */ NULL,
+ /* tpm2_pubkey_pcr_mask= */ 0,
+ UID_INVALID,
+ &IOVEC_MAKE_STRING(json_text),
+ CREDENTIAL_ALLOW_NULL,
+ &encoded);
if (r < 0)
- return r;
-
- if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */
- return 0;
+ return log_error_errno(r, "Failed to encode policy as credential: %m");
- r = unlink_pcrlock(default_pcrlock_late_path);
+ r = write_base64_file_at(
+ AT_FDCWD,
+ boot_policy_file,
+ &encoded,
+ WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file);
- return 0;
+ log_info("Written new boot policy to '%s'.", boot_policy_file);
+ return 1;
}
-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;
+static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
int r;
- r = pcrextend_machine_id_word(&word);
+ /* Here's how this all works: after predicting all possible PCR values for next boot (with
+ * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR
+ * expressions. This is then stored in an NV index. When a component of the boot process is changed a
+ * new prediction is made and the NV index updated (which automatically invalidates any older
+ * policies).
+ *
+ * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
+ * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any
+ * policies matching the current NV index contents.
+ *
+ * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we
+ * either generate locally or which the user can provide us with) which can also be used for
+ * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it
+ * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need
+ * the policy to pass.
+ *
+ * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR
+ * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks
+ * this data must be also copied to the ESP so that it is available to the initrd. The data is not
+ * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index
+ * to be useful. */
+
+ usec_t start_usec = now(CLOCK_MONOTONIC);
+
+ _cleanup_(event_log_freep) EventLog *el = NULL;
+ r = event_log_load_and_process(&el);
if (r < 0)
return r;
- r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+ _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = {
+ arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
+ };
+ r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs);
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");
+ if (!force && new_prediction.pcrs == 0)
+ log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding.");
- return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH);
-}
+ usec_t predict_start_usec = now(CLOCK_MONOTONIC);
-static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH);
-}
+ r = tpm2_pcr_prediction_run(el, &new_prediction);
+ if (r < 0)
+ return r;
-static int pcrlock_file_system_path(const char *normalized_path, char **ret) {
- _cleanup_free_ char *s = NULL;
+ log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1));
- assert(normalized_path);
- assert(ret);
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL;
+ r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json);
+ if (r < 0)
+ return r;
- 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;
+ if (DEBUG_LOGGING)
+ (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL);
- assert(normalized_path[0] == '/');
- assert(normalized_path[1] != '/');
+ /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when
+ * --policy= is unspecified but --pcrlock is specified. */
+ if (!arg_policy_path && arg_pcrlock_path) {
+ log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead.");
- escaped = unit_name_escape(normalized_path + 1);
- if (!escaped)
+ arg_policy_path = strdup(arg_pcrlock_path);
+ if (!arg_policy_path)
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] = "/";
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {};
+ r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy);
+ if (r < 0)
+ return r;
- r = get_block_device("/", &a);
- if (r < 0)
- return log_error_errno(r, "Failed to get device of root file system: %m");
+ bool have_old_policy = r > 0;
- r = get_block_device("/var", &b);
- if (r < 0)
- return log_error_errno(r, "Failed to get device of /var/ file system: %m");
+ /* When we update the policy the old serializations for NV, SRK, PIN remain the same */
+ _cleanup_(iovec_done) struct iovec
+ nv_blob = TAKE_STRUCT(old_policy.nv_handle),
+ nv_public_blob = TAKE_STRUCT(old_policy.nv_public),
+ srk_blob = TAKE_STRUCT(old_policy.srk_handle),
+ pin_public = TAKE_STRUCT(old_policy.pin_public),
+ pin_private = TAKE_STRUCT(old_policy.pin_private);
- /* if backing device is distinct, then measure /var/ too */
- if (a != b)
- paths[1] = "/var";
+ if (have_old_policy) {
+ if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index);
- enable_json_sse();
+ if (!force &&
+ old_policy.algorithm == el->primary_algorithm &&
+ tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) {
+ log_info("Prediction is identical to current policy, skipping update.");
+ return 0; /* NOP */
+ }
}
- 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;
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+ if (r < 0)
+ return r;
- r = pcrextend_file_system_word(*p, &word, &normalized_path);
- if (r < 0)
- return r;
+ if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing.");
+ if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256))
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing.");
- r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
- if (r < 0)
- return r;
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
- r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+ r = tpm2_deserialize(
+ tc,
+ &srk_blob,
+ &srk_handle);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deserialize SRK TR: %m");
+ if (r == 0) {
+ r = tpm2_get_or_create_srk(
+ tc,
+ /* session= */ NULL,
+ /* ret_public= */ NULL,
+ /* ret_name= */ NULL,
+ /* ret_qname= */ NULL,
+ &srk_handle);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to install SRK: %m");
+ }
- r = sd_json_variant_new_array(&array, &record, 1);
- if (r < 0)
- return log_error_errno(r, "Failed to create record array: %m");
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
+ r = tpm2_make_encryption_session(
+ tc,
+ srk_handle,
+ /* bind_key= */ &TPM2_HANDLE_NONE,
+ &encryption_session);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate encryption session: %m");
- r = write_pcrlock(array, pcrlock_file);
+ /* Acquire a recovery PIN, either from the user, or create a randomized one */
+ _cleanup_(erase_and_freep) char *pin = NULL;
+ if (recovery_pin_mode == RECOVERY_PIN_QUERY) {
+ r = getenv_steal_erase("PIN", &pin);
if (r < 0)
- return r;
- }
-
- return 0;
-}
+ return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+ if (r == 0) {
+ _cleanup_strv_free_erase_ char **l = NULL;
-static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
- const char* paths[3] = {};
- int r;
+ AskPasswordRequest req = {
+ .tty_fd = -EBADF,
+ .message = "Recovery PIN",
+ .id = "pcrlock-recovery-pin",
+ .credential = "pcrlock.recovery-pin",
+ .until = USEC_INFINITY,
+ .hup_fd = -EBADF,
+ };
- if (argc > 1)
- paths[0] = argv[1];
- else {
- paths[0] = "/";
- paths[1] = "/var";
- }
+ r = ask_password_auto(
+ &req,
+ /* flags= */ 0,
+ &l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query for recovery PIN: %m");
- STRV_FOREACH(p, paths) {
- _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL;
+ if (strv_length(l) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only.");
- r = chase(*p, NULL, 0, &normalized_path, NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]);
+ pin = TAKE_PTR(l[0]);
+ l = mfree(l);
+ }
- r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
+ } else if (!have_old_policy) {
+ r = make_recovery_key(&pin);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m");
- r = unlink_pcrlock(pcrlock_file);
- if (r < 0)
- return r;
+ if (recovery_pin_mode == RECOVERY_PIN_SHOW)
+ printf("%s Selected recovery PIN is: %s%s%s\n",
+ glyph(GLYPH_LOCK_AND_KEY),
+ ansi_highlight_cyan(),
+ pin,
+ ansi_normal());
}
- 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
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+ TPM2_HANDLE nv_index = 0;
- 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]);
- }
+ r = tpm2_deserialize(tc, &nv_blob, &nv_handle);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deserialize NV index TR: %m");
+ if (r > 0)
+ nv_index = old_policy.nv_index;
- if (arg_pcr_mask == 0)
- arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;
+ TPM2B_AUTH auth = {};
+ CLEANUP_ERASE(auth);
- for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
+ if (pin) {
+ r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
+ if (r < 0)
+ return log_error_errno(r, "Failed to hash PIN: %m");
+ } else {
+ assert(iovec_is_set(&pin_public));
+ assert(iovec_is_set(&pin_private));
- if (!BIT_SET(arg_pcr_mask, i))
- continue;
+ log_debug("Retrieving PIN from sealed data.");
- 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;
+ usec_t pin_start_usec = now(CLOCK_MONOTONIC);
- assert_se(a = tpm2_hash_alg_to_string(*pa));
- assert_se(md = EVP_get_digestbyname(a));
+ _cleanup_(iovec_done_erase) struct iovec secret = {};
+ for (unsigned attempt = 0;; attempt++) {
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
- r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size);
+ r = tpm2_make_policy_session(
+ tc,
+ srk_handle,
+ encryption_session,
+ &policy_session);
if (r < 0)
- return log_error_errno(r, "Failed to hash PE binary: %m");
+ return log_error_errno(r, "Failed to allocate policy session: %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)));
+ r = tpm2_policy_super_pcr(
+ tc,
+ policy_session,
+ &old_policy.prediction,
+ old_policy.algorithm);
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 r;
- return write_pcrlock(array, NULL);
-}
+ r = tpm2_policy_authorize_nv(
+ tc,
+ policy_session,
+ nv_handle,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m");
-typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS];
+ r = tpm2_unseal_data(
+ tc,
+ &pin_public,
+ &pin_private,
+ srk_handle,
+ policy_session,
+ encryption_session,
+ &secret);
+ if (r < 0 && (r != -ESTALE || attempt >= 16))
+ return log_error_errno(r, "Failed to unseal PIN: %m");
+ if (r == 0)
+ break;
-static void section_hashes_array_done(SectionHashArray *array) {
- assert(array);
+ log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1);
+ }
- for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++)
- free((*array)[i]);
-}
+ if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer))
+ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large.");
-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;
+ auth = (TPM2B_AUTH) {
+ .size = secret.iov_len,
+ };
- if (arg_pcr_mask != 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down.");
+ memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len);
- 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]);
+ log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
}
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
- _cleanup_free_ void *peh = NULL;
- const EVP_MD *md;
- const char *a;
+ /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL;
+ r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle);
+ if (r < 0)
+ return r;
- assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
- assert_se(md = EVP_get_digestbyname(a));
+ TPM2B_NV_PUBLIC nv_public = {};
+ usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
- r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i);
+ if (!iovec_is_set(&nv_blob)) {
+ _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL;
+ r = tpm2_get_name(
+ tc,
+ pin_handle,
+ &pin_name);
if (r < 0)
- return log_error_errno(r, "Failed to hash PE binary: %m");
+ return log_error_errno(r, "Failed to get name of PIN from TPM2: %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])));
+ TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+ r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
if (r < 0)
- return log_error_errno(r, "Failed to build JSON digest object: %m");
+ return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
- r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i);
+ log_debug("Allocating NV index to write PCR policy to...");
+ r = tpm2_define_policy_nv_index(
+ tc,
+ encryption_session,
+ arg_nv_index,
+ &recovery_policy_digest,
+ &nv_index,
+ &nv_handle,
+ &nv_public);
+ if (r == -EEXIST)
+ return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index);
if (r < 0)
- return log_error_errno(r, "Failed to UKI hash PE binary: %m");
+ return log_error_errno(r, "Failed to allocate NV index: %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));
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
+ r = tpm2_make_policy_session(
+ tc,
+ srk_handle,
+ encryption_session,
+ &policy_session);
if (r < 0)
- return log_error_errno(r, "Failed to append record object: %m");
+ return log_error_errno(r, "Failed to allocate policy session: %m");
- for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL;
+ r = tpm2_policy_signed_hmac_sha256(
+ tc,
+ policy_session,
+ pin_handle,
+ &IOVEC_MAKE(auth.buffer, auth.size),
+ /* ret_policy_digest= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to submit authentication value policy: %m");
- if (!unified_section_measure(section))
- continue;
+ log_debug("Calculating new PCR policy to write...");
+ TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
- const char *a;
- void *hash;
+ usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC);
- hash = section_hashes[i * _UNIFIED_SECTION_MAX + section];
- if (!hash)
- continue;
+ r = tpm2_calculate_policy_super_pcr(
+ &new_prediction,
+ el->primary_algorithm,
+ &new_super_pcr_policy_digest);
+ if (r < 0)
+ return log_error_errno(r, "Failed to calculate super PCR policy: %m");
- assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+ log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1));
- 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");
- }
+ log_debug("Writing new PCR policy to NV index...");
+ r = tpm2_write_policy_nv_index(
+ tc,
+ policy_session,
+ nv_index,
+ nv_handle,
+ &new_super_pcr_policy_digest);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write to NV index: %m");
- if (!section_digests)
- continue;
+ log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1));
- /* 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);
+ assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private));
+ if (!iovec_is_set(&pin_public)) {
+ TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+
+ r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m");
- r = sd_json_variant_append_array(&array, record);
+ struct iovec data = {
+ .iov_base = auth.buffer,
+ .iov_len = auth.size,
+ };
+
+ usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC);
+
+ log_debug("Sealing PIN to NV index policy...");
+ r = tpm2_seal_data(
+ tc,
+ &data,
+ srk_handle,
+ encryption_session,
+ &authnv_policy_digest,
+ &pin_public,
+ &pin_private);
if (r < 0)
- return log_error_errno(r, "Failed to append JSON record array: %m");
+ return log_error_errno(r, "Failed to seal PIN to NV auth policy: %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));
+ log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1));
+ }
+
+ if (!iovec_is_set(&nv_blob)) {
+ r = tpm2_serialize(tc, nv_handle, &nv_blob);
if (r < 0)
- return log_error_errno(r, "Failed to append record object: %m");
+ return log_error_errno(r, "Failed to serialize NV index TR: %m");
}
- return write_pcrlock(array, NULL);
-}
+ if (!iovec_is_set(&srk_blob)) {
+ r = tpm2_serialize(tc, srk_handle, &srk_blob);
+ if (r < 0)
+ return log_error_errno(r, "Failed to serialize SRK index TR: %m");
+ }
-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 (!iovec_is_set(&nv_public_blob)) {
+ r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len);
+ if (r < 0)
+ return log_error_errno(r, "Failed to marshal NV public area: %m");
+ }
- 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);
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL;
+ r = sd_json_buildo(
+ &new_configuration_json,
+ SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)),
+ SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json),
+ SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index),
+ JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob),
+ JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob),
+ JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob),
+ JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public),
+ JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private));
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();
+ return log_error_errno(r, "Failed to generate JSON: %m");
- r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record);
+ _cleanup_free_ char *text = NULL;
+ r = sd_json_variant_format(new_configuration_json, 0, &text);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to format new configuration to JSON: %m");
- r = sd_json_variant_new_array(&array, &record, 1);
+ const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json");
+ r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
if (r < 0)
- return log_error_errno(r, "Failed to create record array: %m");
+ return log_error_errno(r, "Failed to write new configuration to '%s': %m", path);
- r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH);
- if (r < 0)
- return r;
+ if (!arg_policy_path && !in_initrd()) {
+ r = remove_policy_file("/run/systemd/pcrlock.json");
+ if (r < 0)
+ return r;
+ }
- return 0;
-}
+ log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
-static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH);
-}
+ (void) write_boot_policy_file(text);
-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;
+ log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
- if (argc >= 2) {
- f = fopen(argv[1], "re");
- if (!f)
- return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
- }
+ return 1; /* installed new policy */
+}
- r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records);
- if (r < 0)
- return r;
+static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
- r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH);
+ r = make_policy(arg_force, arg_recovery_pin);
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;
-
- assert(el);
- assert(pcrs);
+static int undefine_policy_nv_index(
+ uint32_t nv_index,
+ const struct iovec *nv_blob,
+ const struct iovec *srk_blob) {
+ int r;
- /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three
- * reasons:
- *
- * 1. The PCR value doesn't match the event log
- * 2. The event log for the PCR contains measurements we don't know responsible components for
- * 3. The event log for the PCR does not contain measurements for components we know
- *
- * This function checks for the three conditions and drops the PCR from the mask.
- */
+ assert(nv_blob);
+ assert(srk_blob);
- for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+ if (r < 0)
+ return r;
- if (!BIT_SET(*pcrs, pcr))
- continue;
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
+ r = tpm2_deserialize(
+ tc,
+ srk_blob,
+ &srk_handle);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deserialize SRK TR: %m");
- if (!event_log_pcr_checks_out(el, el->registers + pcr)) {
- log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
- goto drop;
- }
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+ r = tpm2_deserialize(
+ tc,
+ nv_blob,
+ &nv_handle);
+ if (r < 0)
+ return log_error_errno(r, "Failed to deserialize NV TR: %m");
- if (!el->registers[pcr].fully_recognized) {
- log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
- goto drop;
- }
+ _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
+ r = tpm2_make_encryption_session(
+ tc,
+ srk_handle,
+ /* bind_key= */ &TPM2_HANDLE_NONE,
+ &encryption_session);
+ if (r < 0)
+ return r;
- if (BIT_SET(el->missing_component_pcrs, pcr)) {
- log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
- goto drop;
- }
+ r = tpm2_undefine_nv_index(
+ tc,
+ encryption_session,
+ nv_index,
+ nv_handle);
+ if (r < 0)
+ return r;
- log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+ log_info("Removed NV index 0x%x", nv_index);
+ return 0;
+}
- if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
- return log_oom();
+static int remove_policy(void) {
+ int ret = 0, r;
- continue;
+ _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
+ r = tpm2_pcrlock_policy_load(arg_policy_path, &policy);
+ if (r == 0) {
+ log_info("No policy found.");
+ return 0;
+ }
- drop:
- *pcrs &= ~(UINT32_C(1) << pcr);
+ if (r < 0)
+ log_notice("Failed to load old policy file, assuming it is corrupted, removing.");
+ else {
+ r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle);
+ if (r < 0)
+ log_notice("Failed to remove NV index, assuming data out of date, removing policy file.");
- if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
- return log_oom();
+ RET_GATHER(ret, r);
}
- if (dropped)
- log_notice("PCRs dropped from protection mask: %s", dropped);
- else
- log_debug("No PCRs dropped from protection mask.");
+ if (arg_policy_path)
+ RET_GATHER(ret, remove_policy_file(arg_policy_path));
+ else {
+ RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json"));
+ RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json"));
+ }
- if (kept)
- log_notice("PCRs in protection mask: %s", kept);
- else
- log_notice("No PCRs kept in protection mask.");
+ _cleanup_free_ char *boot_policy_file = NULL;
+ r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL);
+ if (r == 0)
+ log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file.");
+ else if (r > 0) {
+ RET_GATHER(ret, remove_policy_file(boot_policy_file));
+ } else
+ RET_GATHER(ret, r);
- return 0;
+ return ret;
}
-static int pcr_prediction_add_result(
- Tpm2PCRPrediction *context,
- Tpm2PCRPredictionResult *result,
- uint32_t pcr,
- const char *path) {
+static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ return remove_policy();
+}
- _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL;
+static int test_tpm2_support_pcrlock(Tpm2Support *ret) {
int r;
- assert(context);
- assert(result);
+ assert(ret);
- copy = newdup(Tpm2PCRPredictionResult, result, 1);
- if (!copy)
- return log_oom();
+ /* First check basic support */
+ Tpm2Support s = tpm2_support();
- r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy);
- if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */
- return 0;
- if (r < 0)
- return log_error_errno(r, "Failed to insert result into set: %m");
+ /* If basic support is available, let's also check the things we need for systemd-pcrlock */
+ if (s == TPM2_SUPPORT_FULL) {
+ _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+ r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+ if (r < 0)
+ return r;
- log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path));
+ /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */
+ SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV));
- TAKE_PTR(copy);
+ log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV)));
+
+ /* We also strictly need SHA-256 to work */
+ SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256));
+
+ log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256)));
+ }
+
+ *ret = s;
return 0;
}
-static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) {
- const char *name;
+static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
- name = tpm2_hash_alg_to_string(alg);
- if (!name)
- return NULL;
+ Tpm2Support s;
+ r = test_tpm2_support_pcrlock(&s);
+ if (r < 0)
+ return r;
- return EVP_get_digestbyname(name);
-}
+ if (!arg_quiet) {
+ if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK))
+ printf("%syes%s\n", ansi_green(), ansi_normal());
+ else if (FLAGS_SET(s, TPM2_SUPPORT_FULL))
+ printf("%sobsolete%s\n", ansi_red(), ansi_normal());
+ else if (s == TPM2_SUPPORT_NONE)
+ printf("%sno%s\n", ansi_red(), ansi_normal());
+ else
+ printf("%spartial%s\n", ansi_yellow(), ansi_normal());
+ }
-static int event_log_component_variant_calculate(
- Tpm2PCRPredictionResult *result,
- EventLogComponentVariant *variant,
- uint32_t pcr) {
+ assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */
- assert(result);
- assert(variant);
+ return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK);
+}
- FOREACH_ARRAY(rr, variant->records, variant->n_records) {
- EventLogRecord *rec = *rr;
+static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) {
+ _cleanup_free_ char *d = NULL;
+ int r;
- if (!EVENT_LOG_RECORD_IS_PCR(rec))
- continue;
+ assert(rec);
- if (rec->pcr != pcr)
- continue;
+ /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
- _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
- EventLogRecordBank *b;
+ if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+ return false;
- if (result->hash[i].size <= 0) /* already invalidated */
- continue;
+ if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE)
+ return false;
- b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]);
- if (!b) {
- /* Can't calculate, hence invalidate */
- result->hash[i] = (TPM2B_DIGEST) {};
- continue;
- }
+ if (rec->firmware_event_type != EV_EFI_ACTION)
+ return false;
- md_ctx = EVP_MD_CTX_new();
- if (!md_ctx)
- return log_oom();
+ if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */
+ return false;
- const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i]));
+ r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d);
+ if (r < 0)
+ return r;
- int sz = EVP_MD_size(md);
- assert(sz > 0);
- assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer));
+ return streq(d, "Calling EFI Application from Boot Option");
+}
- assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]));
+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 */
- assert(result->hash[i].size == (size_t) sz);
- assert(b->hash.size == (size_t) sz);
+ if (!arg_pcrlock_path && arg_pcrlock_auto)
+ return;
- if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest.");
+ if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE))
+ return;
- if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value.");
+ log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output.");
+ arg_json_format_flags |= SD_JSON_FORMAT_SSE;
+}
- if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value.");
+static bool event_log_record_is_separator(const EventLogRecord *rec) {
+ assert(rec);
- unsigned l = (unsigned) sz;
- if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest.");
+ /* Recognizes EV_SEPARATOR events */
- assert(l == (unsigned) sz);
- }
- }
+ if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+ return false;
- return 0;
+ 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_predict_pcrs(
- EventLog *el,
- Tpm2PCRPrediction *context,
- Tpm2PCRPredictionResult *parent_result,
- size_t component_index,
- uint32_t pcr,
- const char *path) {
+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;
- EventLogComponent *component;
- int count = 0, r;
+ enable_json_sse();
- assert(el);
- assert(context);
- assert(parent_result);
+ /* 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 */
- /* Check if we reached the end of the components, generate a result, and backtrack */
- if (component_index >= el->n_components ||
- (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) {
- r = pcr_prediction_add_result(context, parent_result, pcr, path);
- if (r < 0)
- return r;
+ separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE; /* → 4 */
- return 1;
- }
+ 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 */
- component = ASSERT_PTR(el->components[component_index]);
+ separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */
- /* Check if we are just about to process a component after start, if so record a result and continue. */
- if (arg_location_start && strcmp(component->id, arg_location_start) > 0) {
- r = pcr_prediction_add_result(context, parent_result, pcr, path);
- if (r < 0)
- return r;
+ default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
+ default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
}
- FOREACH_ARRAY(ii, component->variants, component->n_variants) {
- _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
- EventLogComponentVariant *variant = *ii;
- _cleanup_free_ char *subpath = NULL;
+ el = event_log_new();
+ if (!el)
+ return log_oom();
- /* Operate on a copy of the result */
+ r = event_log_add_algorithms_from_environment(el);
+ if (r < 0)
+ return r;
- if (path)
- subpath = strjoin(path, ":", component->id);
- else
- subpath = strdup(component->id);
- if (!subpath)
- return log_oom();
+ r = event_log_load(el);
+ if (r < 0)
+ return r;
- if (!streq(component->id, variant->id))
- if (!strextend(&subpath, "@", variant->id))
- return log_oom();
+ r = event_log_read_pcrs(el);
+ if (r < 0)
+ return r;
- result = newdup(Tpm2PCRPredictionResult, parent_result, 1);
- if (!result)
- return log_oom();
+ r = event_log_calculate_pcrs(el);
+ if (r < 0)
+ return r;
- r = event_log_component_variant_calculate(
- result,
- variant,
- pcr);
- if (r < 0)
- return r;
-
- r = event_log_predict_pcrs(
- el,
- context,
- result,
- component_index + 1, /* Next component */
- pcr,
- subpath);
- if (r < 0)
- return r;
-
- count += r;
- }
-
- return count;
-}
-
-static ssize_t event_log_calculate_component_combinations(EventLog *el) {
- ssize_t count = 1;
- assert(el);
-
- FOREACH_ARRAY(cc, el->components, el->n_components) {
- EventLogComponent *c = *cc;
-
- assert(c->n_variants > 0);
-
- /* Overflow check */
- if (c->n_variants > (size_t) (SSIZE_MAX/count))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations.");
- count *= c->n_variants;
- }
-
- return count;
-}
+ r = event_log_validate_record_hashes(el);
+ if (r < 0)
+ return r;
-static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) {
- int 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. */
- assert(context);
+ r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask);
+ if (r < 0)
+ return r;
- pager_open(arg_pager_flags);
+ // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries,
+ // and exactly once
- if (sd_json_format_enabled(arg_json_format_flags)) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+ 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;
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
+ if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+ continue;
- r = tpm2_pcr_prediction_to_json(
- context,
- tpm2_hash_algorithms[i],
- &aj);
- if (r < 0)
- return r;
+ if (!FLAGS_SET(always_mask, bit) &&
+ !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit)))
+ continue;
- if (sd_json_variant_elements(aj) == 0)
- 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;
+ }
- r = sd_json_variant_set_field(
- &j,
- tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]),
- aj);
- if (r < 0)
- return log_error_errno(r, "Failed to add prediction bank to object: %m");
+ /* 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;
}
- if (!j) {
- r = sd_json_variant_new_object(&j, NULL, 0);
+ 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 allocated empty object: %m");
+ return log_error_errno(r, "Failed to build digests array: %m");
}
- sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
- return 0;
+ 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");
}
- for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
- Tpm2PCRPredictionResult *result;
- if (!BIT_SET(context->pcrs, pcr))
- continue;
-
- if (ordered_set_isempty(context->results[pcr])) {
- printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr));
- continue;
- }
-
- printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal());
-
- ORDERED_SET_FOREACH(result, context->results[pcr]) {
+ r = write_pcrlock(array_early, default_pcrlock_early_path);
+ if (r < 0)
+ return r;
- _cleanup_free_ char *aa = NULL, *h = NULL;
- const char *a;
+ return write_pcrlock(array_late, default_pcrlock_late_path);
+}
- TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg);
- if (!hash)
- continue;
+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;
- a = ASSERT_PTR(tpm2_hash_alg_to_string(alg));
- aa = strdup(a);
- if (!aa)
- return log_oom();
+ 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;
+ }
- ascii_strlower(aa);
+ r = unlink_pcrlock(default_pcrlock_early_path);
+ if (r < 0)
+ return r;
- h = hexmem(hash->buffer, hash->size);
- if (!h)
- return log_oom();
+ if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */
+ return 0;
- printf(" %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h);
- }
- }
+ r = unlink_pcrlock(default_pcrlock_late_path);
+ if (r < 0)
+ return r;
return 0;
}
-static int tpm2_pcr_prediction_run(
- EventLog *el,
- Tpm2PCRPrediction *context) {
+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;
- assert(el);
- assert(context);
+ /* Generates expected records from the current SecureBoot state, as readable in the EFI variables
+ * right now. */
- for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
- _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
+ FOREACH_ELEMENT(vv, variables) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL;
- if (!BIT_SET(context->pcrs, pcr))
- continue;
+ _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();
- result = new0(Tpm2PCRPredictionResult, 1);
- if (!result)
+ _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;
- for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
- event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i);
+ 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();
- r = event_log_predict_pcrs(
- el,
- context,
- result,
- /* component_index= */ 0,
- pcr,
- /* path= */ NULL);
+ *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 0;
+ return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH);
}
-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,
- };
- _cleanup_(event_log_freep) EventLog *el = NULL;
- ssize_t count;
+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;
- r = event_log_load_and_process(&el);
- if (r < 0)
- return r;
+ assert(rec);
+ assert(name);
- count = event_log_calculate_component_combinations(el);
- if (count < 0)
- return count;
+ if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+ return false;
- log_info("%zi combinations of components.", count);
+ if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
+ return false;
- r = event_log_reduce_to_safe_pcrs(el, &context.pcrs);
- if (r < 0)
- return r;
+ if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
+ return false;
- r = tpm2_pcr_prediction_run(el, &context);
+ 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;
- return event_log_show_predictions(&context, el->primary_algorithm);
-}
-
-static int remove_policy_file(const char *path) {
- assert(path);
+ if (!sd_id128_equal(found_uuid, uuid))
+ return false;
- if (unlink(path) < 0) {
- if (errno == ENOENT)
- return 0;
+ return streq(found_name, name);
+}
- return log_error_errno(errno, "Failed to remove policy file '%s': %m", path);
- }
+static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) {
+ assert(rec);
- log_info("Removed policy file '%s'.", path);
- return 1;
-}
+ if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+ return false;
-static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) {
- int r;
+ if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
+ return false;
- _cleanup_free_ char *path = NULL;
- r = get_global_boot_credentials_path(&path);
- if (r < 0)
- return r;
- if (r == 0) {
- if (ret_path)
- *ret_path = NULL;
- if (ret_credential_name)
- *ret_credential_name = NULL;
- return 0; /* not found! */
- }
+ if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
+ return false;
- sd_id128_t machine_id;
- r = sd_id128_get_machine(&machine_id);
- if (r < 0)
- return log_error_errno(r, "Failed to read machine ID: %m");
+ return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY;
+}
- r = boot_entry_token_ensure(
- /* root= */ NULL,
- /* conf_root= */ NULL,
- machine_id,
- /* machine_id_is_random= */ false,
- &arg_entry_token_type,
- &arg_entry_token);
- if (r < 0)
- return r;
+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
+ };
- _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred");
- if (!fn)
- return log_oom();
+ EventLogRecord *records[ELEMENTSOF(table)] = {};
+ EventLogRecord *first_authority = NULL;
- if (!filename_is_valid(fn))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn);
+ assert(el);
- _cleanup_free_ char *joined = NULL;
- if (ret_path) {
- joined = path_join(path, fn);
- if (!joined)
- return log_oom();
- }
+ /* 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. */
- _cleanup_free_ char *cn = NULL;
- if (ret_credential_name) {
- /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from
- * the embedded credential name. */
- cn = strjoin("pcrlock.", arg_entry_token);
- if (!cn)
- return log_oom();
+ FOREACH_ARRAY(rr, el->records, el->n_records) {
+ EventLogRecord *rec = *rr;
+ size_t found = SIZE_MAX;
- ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want
- * to run into case change incompatibilities */
- }
+ if (event_log_record_is_secureboot_authority(rec)) {
+ if (first_authority)
+ continue;
- if (ret_path)
- *ret_path = TAKE_PTR(joined);
+ first_authority = rec;
+ // FIXME: also check that each authority record's data is also listed in 'db'
+ continue;
+ }
- if (ret_credential_name)
- *ret_credential_name = TAKE_PTR(cn);
+ 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;
- return 1; /* found! */
-}
+ /* Require the authority records always come *after* database measurements */
+ if (first_authority)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing.");
-static int write_boot_policy_file(const char *json_text) {
- _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL;
- int r;
+ /* Check for duplicates */
+ if (records[found])
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description);
- assert(json_text);
+ /* 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);
- r = determine_boot_policy_file(&boot_policy_file, &credential_name);
- if (r < 0)
- return r;
- if (r == 0) {
- log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file.");
- return 0;
+ records[found] = rec;
}
- _cleanup_(iovec_done) struct iovec encoded = {};
- r = encrypt_credential_and_warn(
- CRED_AES256_GCM_BY_NULL,
- credential_name,
- now(CLOCK_REALTIME),
- /* not_after= */ USEC_INFINITY,
- /* tpm2_device= */ NULL,
- /* tpm2_hash_pcr_mask= */ 0,
- /* tpm2_pubkey_path= */ NULL,
- /* tpm2_pubkey_pcr_mask= */ 0,
- UID_INVALID,
- &IOVEC_MAKE_STRING(json_text),
- CREDENTIAL_ALLOW_NULL,
- &encoded);
- if (r < 0)
- return log_error_errno(r, "Failed to encode policy as credential: %m");
-
- r = write_base64_file_at(
- AT_FDCWD,
- boot_policy_file,
- &encoded,
- WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
- if (r < 0)
- return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file);
+ /* 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);
- log_info("Written new boot policy to '%s'.", boot_policy_file);
- return 1;
+ /* At this point we know that all required variables have been measured, in the right order. */
+ return 0;
}
-static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
+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;
- /* Here's how this all works: after predicting all possible PCR values for next boot (with
- * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR
- * expressions. This is then stored in an NV index. When a component of the boot process is changed a
- * new prediction is made and the NV index updated (which automatically invalidates any older
- * policies).
- *
- * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
- * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any
- * policies matching the current NV index contents.
- *
- * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we
- * either generate locally or which the user can provide us with) which can also be used for
- * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it
- * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need
- * the policy to pass.
- *
- * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR
- * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks
- * this data must be also copied to the ESP so that it is available to the initrd. The data is not
- * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index
- * to be useful. */
+ /* 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. */
- usec_t start_usec = now(CLOCK_MONOTONIC);
+ if (!is_efi_secure_boot()) {
+ log_info("SecureBoot disabled, not generating authority .pcrlock file.");
+ return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
+ }
- _cleanup_(event_log_freep) EventLog *el = NULL;
- r = event_log_load_and_process(&el);
+ el = event_log_new();
+ if (!el)
+ return log_oom();
+
+ r = event_log_add_algorithms_from_environment(el);
if (r < 0)
return r;
- _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = {
- arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
- };
- r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs);
+ r = event_log_load(el);
if (r < 0)
return r;
- if (!force && new_prediction.pcrs == 0)
- log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding.");
-
- usec_t predict_start_usec = now(CLOCK_MONOTONIC);
+ r = event_log_read_pcrs(el);
+ if (r < 0)
+ return r;
- r = tpm2_pcr_prediction_run(el, &new_prediction);
+ r = event_log_calculate_pcrs(el);
if (r < 0)
return r;
- log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1));
+ /* Before we base anything on the event log records, let's check that the event log state checks
+ * out. */
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL;
- r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json);
+ r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY);
if (r < 0)
return r;
- if (DEBUG_LOGGING)
- (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL);
+ r = event_log_validate_record_hashes(el);
+ if (r < 0)
+ return r;
- /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when
- * --policy= is unspecified but --pcrlock is specified. */
- if (!arg_policy_path && arg_pcrlock_path) {
- log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead.");
+ r = event_log_ensure_secureboot_consistency(el);
+ if (r < 0)
+ return r;
- arg_policy_path = strdup(arg_pcrlock_path);
- if (!arg_policy_path)
- return log_oom();
+ 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");
}
- _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {};
- r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy);
+ 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 r;
+ return log_error_errno(r, "Failed to determine root block device: %m");
- bool have_old_policy = r > 0;
+ 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");
- /* When we update the policy the old serializations for NV, SRK, PIN remain the same */
- _cleanup_(iovec_done) struct iovec
- nv_blob = TAKE_STRUCT(old_policy.nv_handle),
- nv_public_blob = TAKE_STRUCT(old_policy.nv_public),
- srk_blob = TAKE_STRUCT(old_policy.srk_handle),
- pin_public = TAKE_STRUCT(old_policy.pin_public),
- pin_private = TAKE_STRUCT(old_policy.pin_private);
+ 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.");
- if (have_old_policy) {
- if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index);
+ /* 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 (!force &&
- old_policy.algorithm == el->primary_algorithm &&
- tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) {
- log_info("Prediction is identical to current policy, skipping update.");
- return 0; /* NOP */
- }
+ 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;
}
- _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
- r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+ 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;
- if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV))
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing.");
- if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256))
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing.");
+ r = sd_json_variant_new_array(&array, &record, 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append to JSON array: %m");
- _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
+ return write_pcrlock(array, PCRLOCK_GPT_PATH);
+}
- r = tpm2_deserialize(
- tc,
- &srk_blob,
- &srk_handle);
- if (r < 0)
- return log_error_errno(r, "Failed to deserialize SRK TR: %m");
- if (r == 0) {
- r = tpm2_get_or_create_srk(
- tc,
- /* session= */ NULL,
- /* ret_public= */ NULL,
- /* ret_name= */ NULL,
- /* ret_qname= */ NULL,
- &srk_handle);
- if (r < 0)
- return log_error_errno(r, "Failed to install SRK: %m");
+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]);
}
- _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
- r = tpm2_make_encryption_session(
- tc,
- srk_handle,
- /* bind_key= */ &TPM2_HANDLE_NONE,
- &encryption_session);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate encryption session: %m");
+ if (arg_pcr_mask == 0)
+ arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;
- /* Acquire a recovery PIN, either from the user, or create a randomized one */
- _cleanup_(erase_and_freep) char *pin = NULL;
- if (recovery_pin_mode == RECOVERY_PIN_QUERY) {
- r = getenv_steal_erase("PIN", &pin);
- if (r < 0)
- return log_error_errno(r, "Failed to acquire PIN from environment: %m");
- if (r == 0) {
- _cleanup_strv_free_erase_ char **l = NULL;
+ for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
- AskPasswordRequest req = {
- .tty_fd = -EBADF,
- .message = "Recovery PIN",
- .id = "pcrlock-recovery-pin",
- .credential = "pcrlock.recovery-pin",
- .until = USEC_INFINITY,
- .hup_fd = -EBADF,
- };
+ if (!BIT_SET(arg_pcr_mask, i))
+ continue;
- r = ask_password_auto(
- &req,
- /* flags= */ 0,
- &l);
- if (r < 0)
- return log_error_errno(r, "Failed to query for recovery PIN: %m");
+ 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;
- if (strv_length(l) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only.");
+ assert_se(a = tpm2_hash_alg_to_string(*pa));
+ assert_se(md = EVP_get_digestbyname(a));
- pin = TAKE_PTR(l[0]);
- l = mfree(l);
+ 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");
}
- } else if (!have_old_policy) {
- r = make_recovery_key(&pin);
+ 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 generate a randomized recovery PIN: %m");
+ return log_error_errno(r, "Failed to append record object: %m");
+ }
- if (recovery_pin_mode == RECOVERY_PIN_SHOW)
- printf("%s Selected recovery PIN is: %s%s%s\n",
- glyph(GLYPH_LOCK_AND_KEY),
- ansi_highlight_cyan(),
- pin,
- ansi_normal());
+ 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]);
}
- _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
- TPM2_HANDLE nv_index = 0;
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ _cleanup_free_ void *peh = NULL;
+ const EVP_MD *md;
+ const char *a;
- r = tpm2_deserialize(tc, &nv_blob, &nv_handle);
- if (r < 0)
- return log_error_errno(r, "Failed to deserialize NV index TR: %m");
- if (r > 0)
- nv_index = old_policy.nv_index;
+ assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+ assert_se(md = EVP_get_digestbyname(a));
- TPM2B_AUTH auth = {};
- CLEANUP_ERASE(auth);
+ 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");
- if (pin) {
- r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
+ 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 hash PIN: %m");
- } else {
- assert(iovec_is_set(&pin_public));
- assert(iovec_is_set(&pin_private));
+ return log_error_errno(r, "Failed to build JSON digest object: %m");
- log_debug("Retrieving PIN from sealed data.");
+ 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");
+ }
- usec_t pin_start_usec = now(CLOCK_MONOTONIC);
+ 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");
- _cleanup_(iovec_done_erase) struct iovec secret = {};
- for (unsigned attempt = 0;; attempt++) {
- _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
+ for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL;
- r = tpm2_make_policy_session(
- tc,
- srk_handle,
- encryption_session,
- &policy_session);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate policy session: %m");
+ if (!unified_section_measure(section))
+ continue;
- r = tpm2_policy_super_pcr(
- tc,
- policy_session,
- &old_policy.prediction,
- old_policy.algorithm);
- if (r < 0)
- return r;
+ for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+ const char *a;
+ void *hash;
- r = tpm2_policy_authorize_nv(
- tc,
- policy_session,
- nv_handle,
- NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m");
+ hash = section_hashes[i * _UNIFIED_SECTION_MAX + section];
+ if (!hash)
+ continue;
- r = tpm2_unseal_data(
- tc,
- &pin_public,
- &pin_private,
- srk_handle,
- policy_session,
- encryption_session,
- &secret);
- if (r < 0 && (r != -ESTALE || attempt >= 16))
- return log_error_errno(r, "Failed to unseal PIN: %m");
- if (r == 0)
- break;
+ assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
- log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1);
+ 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 (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer))
- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large.");
+ if (!section_digests)
+ continue;
- auth = (TPM2B_AUTH) {
- .size = secret.iov_len,
- };
+ /* 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;
- memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len);
+ r = sd_json_variant_append_array(&array, record);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append JSON record array: %m");
- log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
+ /* 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");
}
- /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */
- _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL;
- r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle);
+ 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;
- TPM2B_NV_PUBLIC nv_public = {};
- usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
+ r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+ if (r < 0)
+ return r;
- if (!iovec_is_set(&nv_blob)) {
- _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL;
- r = tpm2_get_name(
- tc,
- pin_handle,
- &pin_name);
- if (r < 0)
- return log_error_errno(r, "Failed to get name of PIN from TPM2: %m");
+ r = sd_json_variant_new_array(&array, &record, 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create record array: %m");
- TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
- r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
- if (r < 0)
- return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
+ return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH);
+}
- log_debug("Allocating NV index to write PCR policy to...");
- r = tpm2_define_policy_nv_index(
- tc,
- encryption_session,
- arg_nv_index,
- &recovery_policy_digest,
- &nv_index,
- &nv_handle,
- &nv_public);
- if (r == -EEXIST)
- return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate NV index: %m");
- }
+static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH);
+}
- _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
- r = tpm2_make_policy_session(
- tc,
- srk_handle,
- encryption_session,
- &policy_session);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate policy session: %m");
+static int pcrlock_file_system_path(const char *normalized_path, char **ret) {
+ _cleanup_free_ char *s = NULL;
- r = tpm2_policy_signed_hmac_sha256(
- tc,
- policy_session,
- pin_handle,
- &IOVEC_MAKE(auth.buffer, auth.size),
- /* ret_policy_digest= */ NULL);
- if (r < 0)
- return log_error_errno(r, "Failed to submit authentication value policy: %m");
+ assert(normalized_path);
+ assert(ret);
- log_debug("Calculating new PCR policy to write...");
- TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+ 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;
- usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC);
+ assert(normalized_path[0] == '/');
+ assert(normalized_path[1] != '/');
- r = tpm2_calculate_policy_super_pcr(
- &new_prediction,
- el->primary_algorithm,
- &new_super_pcr_policy_digest);
- if (r < 0)
- return log_error_errno(r, "Failed to calculate super PCR policy: %m");
+ escaped = unit_name_escape(normalized_path + 1);
+ if (!escaped)
+ return log_oom();
- log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1));
+ s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock");
+ }
+ if (!s)
+ return log_oom();
- log_debug("Writing new PCR policy to NV index...");
- r = tpm2_write_policy_nv_index(
- tc,
- policy_session,
- nv_index,
- nv_handle,
- &new_super_pcr_policy_digest);
- if (r < 0)
- return log_error_errno(r, "Failed to write to NV index: %m");
+ *ret = TAKE_PTR(s);
+ return 0;
+}
- log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1));
+static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ const char* paths[3] = {};
+ int r;
- assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private));
- if (!iovec_is_set(&pin_public)) {
- TPM2B_DIGEST authnv_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+ if (argc > 1)
+ paths[0] = argv[1];
+ else {
+ dev_t a, b;
+ paths[0] = "/";
- r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest);
+ r = get_block_device("/", &a);
if (r < 0)
- return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m");
-
- struct iovec data = {
- .iov_base = auth.buffer,
- .iov_len = auth.size,
- };
-
- usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC);
+ return log_error_errno(r, "Failed to get device of root file system: %m");
- log_debug("Sealing PIN to NV index policy...");
- r = tpm2_seal_data(
- tc,
- &data,
- srk_handle,
- encryption_session,
- &authnv_policy_digest,
- &pin_public,
- &pin_private);
+ r = get_block_device("/var", &b);
if (r < 0)
- return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m");
+ 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";
- log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1));
+ enable_json_sse();
}
- if (!iovec_is_set(&nv_blob)) {
- r = tpm2_serialize(tc, nv_handle, &nv_blob);
- if (r < 0)
- return log_error_errno(r, "Failed to serialize NV index TR: %m");
- }
+ 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;
- if (!iovec_is_set(&srk_blob)) {
- r = tpm2_serialize(tc, srk_handle, &srk_blob);
+ r = pcrextend_file_system_word(*p, &word, &normalized_path);
if (r < 0)
- return log_error_errno(r, "Failed to serialize SRK index TR: %m");
- }
+ return r;
- if (!iovec_is_set(&nv_public_blob)) {
- r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len);
+ r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
if (r < 0)
- return log_error_errno(r, "Failed to marshal NV public area: %m");
- }
-
- _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL;
- r = sd_json_buildo(
- &new_configuration_json,
- SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)),
- SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json),
- SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index),
- JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob),
- JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob),
- JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob),
- JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public),
- JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private));
- if (r < 0)
- return log_error_errno(r, "Failed to generate JSON: %m");
+ return r;
- _cleanup_free_ char *text = NULL;
- r = sd_json_variant_format(new_configuration_json, 0, &text);
- if (r < 0)
- return log_error_errno(r, "Failed to format new configuration to JSON: %m");
+ r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+ if (r < 0)
+ return r;
- const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json");
- r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
- if (r < 0)
- return log_error_errno(r, "Failed to write new configuration to '%s': %m", path);
+ r = sd_json_variant_new_array(&array, &record, 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create record array: %m");
- if (!arg_policy_path && !in_initrd()) {
- r = remove_policy_file("/run/systemd/pcrlock.json");
+ r = write_pcrlock(array, pcrlock_file);
if (r < 0)
return r;
}
- log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
+ return 0;
+}
- (void) write_boot_policy_file(text);
+static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ const char* paths[3] = {};
+ int r;
- log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
+ if (argc > 1)
+ paths[0] = argv[1];
+ else {
+ paths[0] = "/";
+ paths[1] = "/var";
+ }
- return 1; /* installed new policy */
-}
+ STRV_FOREACH(p, paths) {
+ _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL;
-static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
- int r;
+ 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 = make_policy(arg_force, arg_recovery_pin);
- if (r < 0)
- return r;
+ 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 undefine_policy_nv_index(
- uint32_t nv_index,
- const struct iovec *nv_blob,
- const struct iovec *srk_blob) {
+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;
- assert(nv_blob);
- assert(srk_blob);
-
- _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
- r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+ 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 r;
+ return log_error_errno(r, "Failed to read cmdline: %m");
- _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
- r = tpm2_deserialize(
- tc,
- srk_blob,
- &srk_handle);
- if (r < 0)
- return log_error_errno(r, "Failed to deserialize SRK TR: %m");
+ delete_trailing_chars(cmdline, "\n");
- _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
- r = tpm2_deserialize(
- tc,
- nv_blob,
- &nv_handle);
- if (r < 0)
- return log_error_errno(r, "Failed to deserialize NV TR: %m");
+ _cleanup_free_ char16_t *u = NULL;
+ u = utf8_to_utf16(cmdline, SIZE_MAX);
+ if (!u)
+ return log_oom();
- _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
- r = tpm2_make_encryption_session(
- tc,
- srk_handle,
- /* bind_key= */ &TPM2_HANDLE_NONE,
- &encryption_session);
+ r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record);
if (r < 0)
return r;
- r = tpm2_undefine_nv_index(
- tc,
- encryption_session,
- nv_index,
- nv_handle);
+ 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;
- log_info("Removed NV index 0x%x", nv_index);
return 0;
}
-static int remove_policy(void) {
- int ret = 0, r;
-
- _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
- r = tpm2_pcrlock_policy_load(arg_policy_path, &policy);
- if (r == 0) {
- log_info("No policy found.");
- return 0;
- }
+static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH);
+}
- if (r < 0)
- log_notice("Failed to load old policy file, assuming it is corrupted, removing.");
- else {
- r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle);
- if (r < 0)
- log_notice("Failed to remove NV index, assuming data out of date, removing policy 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;
+ uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD;
+ int r;
- RET_GATHER(ret, r);
+ if (argc >= 2) {
+ f = fopen(argv[1], "re");
+ if (!f)
+ return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
}
- if (arg_policy_path)
- RET_GATHER(ret, remove_policy_file(arg_policy_path));
- else {
- RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json"));
- RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json"));
- }
+ r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records);
+ if (r < 0)
+ return r;
- _cleanup_free_ char *boot_policy_file = NULL;
- r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL);
- if (r == 0)
- log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file.");
- else if (r > 0) {
- RET_GATHER(ret, remove_policy_file(boot_policy_file));
- } else
- RET_GATHER(ret, r);
+ r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH);
+ if (r < 0)
+ return r;
- return ret;
+ return 0;
}
-static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
- return remove_policy();
+static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH);
}
-static int test_tpm2_support_pcrlock(Tpm2Support *ret) {
+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;
- assert(ret);
-
- /* First check basic support */
- Tpm2Support s = tpm2_support();
-
- /* If basic support is available, let's also check the things we need for systemd-pcrlock */
- if (s == TPM2_SUPPORT_FULL) {
- _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
- r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
- if (r < 0)
- return r;
-
- /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */
- SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV));
-
- log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV)));
-
- /* We also strictly need SHA-256 to work */
- SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256));
+ if (arg_pcr_mask == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing.");
- log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256)));
+ if (argc >= 2) {
+ f = fopen(argv[1], "re");
+ if (!f)
+ return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
}
- *ret = s;
- return 0;
-}
-
-static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) {
- int r;
-
- Tpm2Support s;
- r = test_tpm2_support_pcrlock(&s);
+ r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records);
if (r < 0)
return r;
- if (!arg_quiet) {
- if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK))
- printf("%syes%s\n", ansi_green(), ansi_normal());
- else if (FLAGS_SET(s, TPM2_SUPPORT_FULL))
- printf("%sobsolete%s\n", ansi_red(), ansi_normal());
- else if (s == TPM2_SUPPORT_NONE)
- printf("%sno%s\n", ansi_red(), ansi_normal());
- else
- printf("%spartial%s\n", ansi_yellow(), ansi_normal());
- }
-
- assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */
-
- return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK);
+ return write_pcrlock(records, NULL);
}
static int help(void) {