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"