pcrlock: error out if requested PCR was dropped (#43260)

- --strict=/--full moved to the OPTION_LONG() parser framework that
replaced getopt in the meantime
- the is-supported commit is dropped, main grew that verb independently.
That also gets rid of the early tpm2_support() gate that broke CI back
then, and TEST-70-TPM2 runs with a software TPM these days anyway
- special_glyph() → glyph() renames
- the log table PCR filter needs an EVENT_LOG_RECORD_IS_PCR() guard now
that the event log has NV-index records
This commit is contained in:
Yu Watanabe
2026-08-05 13:17:21 +09:00
committed by GitHub
3 changed files with 126 additions and 27 deletions

View File

@@ -443,6 +443,14 @@
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--full</option></term>
<listitem><para>Show full hash value instead of abbreviating to 7 characters.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--raw-description</option></term>
@@ -593,6 +601,15 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--strict=</option></term>
<listitem><para>Takes a boolean. Defaults to false. Honoured by <command>make-policy</command> and <command>predict</command>. If
true all specified PCRs must be included in the policy otherwise the command returns failure.</para>
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />

View File

@@ -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: <unspecified>");
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;

View File

@@ -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"