diff --git a/man/systemd-pcrlock.xml b/man/systemd-pcrlock.xml index 521f3a8bde4..dbb59f2e12f 100644 --- a/man/systemd-pcrlock.xml +++ b/man/systemd-pcrlock.xml @@ -443,6 +443,14 @@ The following options are understood: + + + + Show full hash value instead of abbreviating to 7 characters. + + + + @@ -593,6 +601,15 @@ + + + + Takes a boolean. Defaults to false. Honoured by make-policy and predict. If + true all specified PCRs must be included in the policy otherwise the command returns failure. + + + + diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 33b35ac4142..d290538aad1 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -91,9 +91,12 @@ static RecoveryPinMode arg_recovery_pin = RECOVERY_PIN_HIDE; static char *arg_policy_path = NULL; static bool arg_force = false; static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; +static bool arg_strict = false; static char *arg_entry_token = NULL; static bool arg_varlink = false; static bool arg_quiet = false; +/* abbreviate to 7 chars, just like git */ +static size_t arg_abbreviate_hash = 7; STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep); @@ -252,6 +255,9 @@ struct EventLog { /* PCRs mask indicating all PCRs touched by unrecognized components */ uint32_t missing_component_pcrs; + + /* PCRs mask indicating component found for the pcr */ + uint32_t has_component_pcrs; }; static EventLogRecordBank *event_log_record_bank_free(EventLogRecordBank *bank) { @@ -824,6 +830,16 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { goto invalid; } + /* device path could be empty. Don't mark that as invalid but leave as don't know. + * Happens eg with shim https://github.com/rhboot/shim/issues/642 */ + if (load->lengthOfDevicePath == 0) { + rec->description = strdup("File: "); + if (!rec->description) + return log_oom(); + + return 1; + } + const packed_EFI_DEVICE_PATH *dp = (const packed_EFI_DEVICE_PATH*) load->devicePath; size_t left = load->lengthOfDevicePath; @@ -1935,7 +1951,7 @@ static int event_log_validate_fully_recognized(EventLog *el) { continue; if (rec->n_mapped == 0) { - log_notice("Event log record %zu (PCR %" PRIu32 ", \"%s\") not matching any component.", + log_info("Event log record %zu (PCR %" PRIu32 ", \"%s\") not matching any component.", (size_t) (rr - el->records), rec->pcr, strna(rec->description)); fully_recognized = false; break; @@ -2046,6 +2062,8 @@ static int event_log_map_components(EventLog *el) { continue; } + el->has_component_pcrs |= event_log_component_variant_pcrs(*ii); + r = event_log_match_component_variant(el, 0, i, 0, n_matching + n_empty == 0); if (r < 0) return r; @@ -2065,7 +2083,7 @@ static int event_log_map_components(EventLog *el) { else if (arg_location_end && strcmp(c->id, arg_location_end) > 0) { log_info("Didn't find component '%s' in event log, but irrelevant for location window, ignoring.", c->id); } else { - log_notice("Couldn't find component '%s' in event log.", c->id); + log_info("Couldn't find component '%s' in event log.", c->id); el->n_missing_components++; el->missing_component_pcrs |= event_log_component_pcrs(c); @@ -2084,9 +2102,9 @@ static int event_log_map_components(EventLog *el) { } if (n_skipped > 0) - log_notice("Skipped %u components (%s).", n_skipped, skipped_ids); + log_info("Skipped %u components (%s).", n_skipped, skipped_ids); if (el->n_missing_components > 0) - log_notice("Unable to recognize %zu components in event log.", el->n_missing_components); + log_debug("Unable to recognize %zu components in event log.", el->n_missing_components); return event_log_validate_fully_recognized(el); } @@ -2098,6 +2116,18 @@ static const char *ansi_true_color(uint8_t r, uint8_t g, uint8_t b, char ret[sta return ret; } +/* red and green colors usually have good/bad meaning. So exclude color ranges that look too red- or + * greenish. See https://en.wikipedia.org/wiki/Hue */ +static double map_pcr_to_hue(uint32_t pcr) { + double exclude_red_range = 60.0; + double exclude_green_range = 60.0; + double h = (360.0 - exclude_red_range - exclude_green_range) / (TPM2_PCRS_MAX - 1) * pcr; + h += exclude_red_range / 2; + if (h > 120 - exclude_green_range / 2) + h += exclude_green_range; + return MIN(h, 360 - exclude_red_range / 2); +} + static char *color_for_pcr(EventLog *el, uint32_t pcr) { char color[ANSI_TRUE_COLOR_MAX]; uint8_t r, g, b; @@ -2108,7 +2138,7 @@ static char *color_for_pcr(EventLog *el, uint32_t pcr) { if (el->registers[pcr].color) return el->registers[pcr].color; - hsv_to_rgb(360.0 / (TPM2_PCRS_MAX - 1) * pcr, 100, 90, &r, &g, &b); + hsv_to_rgb(map_pcr_to_hue(pcr), 100, 90, &r, &g, &b); ansi_true_color(r, g, b, color); el->registers[pcr].color = strdup(color); @@ -2211,6 +2241,10 @@ static int show_log_table(EventLog *el, sd_json_variant **ret_variant) { FOREACH_ARRAY(rr, el->records, el->n_records) { EventLogRecord *record = *rr; + if (EVENT_LOG_RECORD_IS_PCR(record) && + arg_pcr_mask != 0 && !FLAGS_SET(arg_pcr_mask, UINT32_C(1) << record->pcr)) + continue; + if (EVENT_LOG_RECORD_IS_PCR(record)) r = table_add_many(table, TABLE_UINT32, record->pcr, @@ -2262,6 +2296,9 @@ static int show_log_table(EventLog *el, sd_json_variant **ret_variant) { if (!hex) return log_oom(); + if (!sd_json_format_enabled(arg_json_format_flags)) + strshorten(hex, arg_abbreviate_hash); + r = table_add_cell(table, NULL, TABLE_STRING, hex); } else r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); @@ -2357,20 +2394,32 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { (void) table_set_json_field_name(table, 7, "noMissingComponents"); for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) { + if (arg_pcr_mask != 0 && !FLAGS_SET(arg_pcr_mask, UINT32_C(1) << pcr)) + continue; + /* Check if the PCR hash value matches the event log data */ bool hash_match = event_log_pcr_checks_out(el, el->registers + pcr); /* Whether all records in this PCR have a matching component */ bool fully_recognized = el->registers[pcr].fully_recognized; + bool seen = el->registers[pcr].n_measurements > 0; + /* Whether any unmatched components touch this PCR */ bool missing_components = BIT_SET(el->missing_component_pcrs, pcr); + bool has_components = BIT_SET(el->has_component_pcrs, pcr); - const char *emoji = glyph( - !hash_match ? GLYPH_DEPRESSED_SMILEY : - !fully_recognized ? GLYPH_UNHAPPY_SMILEY : - missing_components ? GLYPH_SLIGHTLY_HAPPY_SMILEY : - GLYPH_HAPPY_SMILEY); + const char *emoji = ""; + if (seen || has_components) { + if (!hash_match) + emoji = glyph(GLYPH_DEPRESSED_SMILEY); + else if (!fully_recognized) + emoji = glyph(GLYPH_UNHAPPY_SMILEY); + else if (!missing_components) + emoji = glyph(GLYPH_HAPPY_SMILEY); + else + emoji = glyph(GLYPH_SLIGHTLY_HAPPY_SMILEY); + } r = table_add_many(table, TABLE_UINT32, pcr, @@ -2388,13 +2437,19 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { if (r < 0) return table_log_add_error(r); - r = table_add_many(table, - TABLE_BOOLEAN_CHECKMARK, hash_match, - TABLE_SET_COLOR, ansi_highlight_green_red(hash_match), - TABLE_BOOLEAN_CHECKMARK, fully_recognized, - TABLE_SET_COLOR, ansi_highlight_green_red(fully_recognized), - TABLE_BOOLEAN_CHECKMARK, !missing_components, - TABLE_SET_COLOR, ansi_highlight_green_red(!missing_components)); + if (sd_json_format_enabled(arg_json_format_flags)) + r = table_add_many(table, + TABLE_BOOLEAN_CHECKMARK, hash_match, + TABLE_BOOLEAN_CHECKMARK, fully_recognized, + TABLE_BOOLEAN_CHECKMARK, !missing_components); + else + r = table_add_many(table, + TABLE_STRING, seen ? glyph_check_mark(hash_match) : " ", + TABLE_SET_COLOR, ansi_highlight_green_red(hash_match), + TABLE_STRING, seen ? glyph_check_mark(fully_recognized) : " ", + TABLE_SET_COLOR, ansi_highlight_green_red(fully_recognized), + TABLE_STRING, has_components ? glyph_check_mark(!missing_components) : " ", + TABLE_SET_COLOR, ansi_highlight_green_red(!missing_components)); if (r < 0) return table_log_add_error(r); @@ -2410,6 +2465,9 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { if (!hex) return log_oom(); + if (!sd_json_format_enabled(arg_json_format_flags)) + strshorten(hex, arg_abbreviate_hash); + r = table_add_many(table, TABLE_STRING, hex, TABLE_SET_COLOR, color); @@ -2429,6 +2487,9 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { if (!hex) return log_oom(); + if (!sd_json_format_enabled(arg_json_format_flags)) + strshorten(hex, arg_abbreviate_hash); + color = !hash_match ? ansi_highlight_red() : is_unset_pcr(el->registers[pcr].banks[i].observed.buffer, el->registers[pcr].banks[i].observed.size) ? ansi_grey() : NULL; @@ -2456,7 +2517,7 @@ static int show_pcr_table(EventLog *el, sd_json_variant **ret_variant) { printf("\n" "%sLegend: H → PCR hash value matches event log%s\n" "%s R → All event log records for this PCR have a matching component%s\n" - "%s C → No components that couldn't be matched with log records affect this PCR%s\n", + "%s C → Component exists and found in event log%s\n", ansi_grey(), ansi_normal(), /* less on small screens automatically resets the color after long lines, hence we set it anew for each line */ ansi_grey(), ansi_normal(), ansi_grey(), ansi_normal()); @@ -2735,6 +2796,9 @@ static int verb_list_components(int argc, char *argv[], uintptr_t _data, void *u FOREACH_ARRAY(c, el->components, el->n_components) { + if (arg_pcr_mask != 0 && (arg_pcr_mask & event_log_component_pcrs(*c)) == 0) + continue; + if (!sd_json_format_enabled(arg_json_format_flags)) { _cleanup_free_ char *marker = NULL; @@ -2998,7 +3062,7 @@ static int write_pcrlock(sd_json_variant *array, const char *default_pcrlock_pat return log_error_errno(r, "Failed to output JSON object: %m"); if (p) - log_info("%s written.", p); + log_debug("%s written.", p); return 0; } @@ -3025,6 +3089,7 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { _cleanup_free_ char *dropped = NULL, *kept = NULL; + bool dropped_relevant_pcr = false; assert(el); assert(pcrs); @@ -3059,7 +3124,7 @@ static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { goto drop; } - 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_debug("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))); if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) return log_oom(); @@ -3067,6 +3132,9 @@ static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { continue; drop: + if (arg_strict) + dropped_relevant_pcr = true; + *pcrs &= ~(UINT32_C(1) << pcr); if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0) @@ -3074,7 +3142,7 @@ static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { } if (dropped) - log_notice("PCRs dropped from protection mask: %s", dropped); + log_full(dropped_relevant_pcr ? LOG_ERR : LOG_NOTICE, "PCRs dropped from protection mask: %s", dropped); else log_debug("No PCRs dropped from protection mask."); @@ -3083,7 +3151,7 @@ static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) { else log_notice("No PCRs kept in protection mask."); - return 0; + return dropped_relevant_pcr ? -ENOEXEC : 0; } static int pcr_prediction_add_result( @@ -3604,7 +3672,7 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { if (r < 0) return r; - log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); + log_debug("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1)); _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); @@ -5322,6 +5390,11 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; + OPTION_LONG("full", NULL, + "Print full hash in measurement log"): + arg_abbreviate_hash = SIZE_MAX; + break; + OPTION_LONG("raw-description", NULL, "Show raw firmware record data as description in table"): arg_raw_description = true; @@ -5455,6 +5528,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; + OPTION_LONG("strict", "BOOL", + "Require all PCRs configured via --pcr= included in policy"): + r = parse_boolean_argument("--strict", opts.arg, &arg_strict); + if (r < 0) + return r; + break; + OPTION('q', "quiet", NULL, "Suppress unnecessary output"): arg_quiet = true; break; diff --git a/test/units/TEST-70-TPM2.pcrlock.sh b/test/units/TEST-70-TPM2.pcrlock.sh index ae809be6712..25f1a2537ed 100755 --- a/test/units/TEST-70-TPM2.pcrlock.sh +++ b/test/units/TEST-70-TPM2.pcrlock.sh @@ -117,10 +117,12 @@ if [[ -n "$SD_STUB" ]]; then [[ "$uki_stdin" == "$uki_pipe" ]] fi -PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query -# Repeat immediately (this call will have to reuse the nvindex, rather than create it) -"$SD_PCRLOCK" make-policy --pcr="$PCRS" -"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force +if "$SD_PCRLOCK" is-supported; then + PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=query + # Repeat immediately (this call will have to reuse the nvindex, rather than create it) + "$SD_PCRLOCK" make-policy --pcr="$PCRS" + "$SD_PCRLOCK" make-policy --pcr="$PCRS" --force +fi img="/tmp/pcrlock.img" truncate -s 20M "$img"