pcrlock: add "is-supported" verb that checks if the local TPM supports the commands we need for pcrlock

systemd-pcrlock requires support for the PolicyAuthorizeNV command,
which is not implemented in the first TPM2 releases. We also strictly
require SHA-256 support. Hence add a tool for checking for both of
these.

This is a tighter version of "systemd-analyze has-tpm2", that checks for
the precise feature that systemd-pcrlock needs, on top of basic TPM2
functionality.

Fixes: #37607
This commit is contained in:
Lennart Poettering
2025-06-02 11:20:52 +02:00
parent d1c36f11d5
commit ccd2bf48b2
4 changed files with 107 additions and 5 deletions

View File

@@ -183,6 +183,28 @@
</listitem>
</varlistentry>
<varlistentry>
<term><command>is-supported</command></term>
<listitem><para>Checks if the local TPM2 supports all functionality for
<command>systemd-pcrlock</command> to work correctly. This does similar tests as
<command>systemd-analyze has-tpm2</command>, but also checks for supported of the TPM2 operations
requires by <command>systemd-pcrlock</command>. Outputs one of <literal>no</literal>,
<literal>partial</literal> (in case some parts of TPM2 support are avaialable in hardware, firmware,
OS, but not complete), <literal>obsolete</literal> (if TPM2 support is available in hardware,
firmware and OS, but the operations required for <command>systemd-pcrlock</command> are missing),
<literal>yes</literal>. Returns an exit status of zero if full support is available, otherwise
non-zero.</para>
<para>If combined with <option>--quiet</option>, suppresses the output of the string.</para>
<para>Currently, this checks for support for the PolicAuthorizeNV TPM2 command, as well as for
support of the SHA-256 hash algorithm.</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<varlistentry>
<term><command>lock-firmware-code</command></term>
<term><command>unlock-firmware-code</command></term>
@@ -562,6 +584,15 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--quiet</option></term>
<listitem><para>If specified suppresses output when invoked for
<command>is-supported</command>.</para>
<xi:include href="version-info.xml" xpointer="v258"/></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

@@ -85,6 +85,7 @@ static bool arg_force = false;
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
static char *arg_entry_token = NULL;
static bool arg_varlink = false;
static bool arg_quiet = false;
STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep);
@@ -4938,6 +4939,60 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) {
return remove_policy();
}
static int test_tpm2_support_pcrlock(Tpm2Support *ret) {
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));
log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256)));
}
*ret = s;
return 0;
}
static int verb_is_supported(int argc, char *argv[], void *userdata) {
int r;
Tpm2Support s;
r = test_tpm2_support_pcrlock(&s);
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_PCRLOCK <= 255); /* make sure this is safe to use as process exit status */
return ~s & TPM2_SUPPORT_API_PCRLOCK;
}
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
@@ -4955,6 +5010,7 @@ static int help(int argc, char *argv[], void *userdata) {
" predict Predict PCR values\n"
" make-policy Predict PCR values and generate TPM2 policy from it\n"
" remove-policy Remove TPM2 policy\n"
" is-supported Tests if TPM2 supports necessary features\n"
"\n%3$sProtections:%4$s\n"
" lock-firmware-code Generate a .pcrlock file from current firmware code\n"
" unlock-firmware-code Remove .pcrlock file for firmware code\n"
@@ -4997,6 +5053,7 @@ static int help(int argc, char *argv[], void *userdata) {
" --force Write policy even if it matches existing policy\n"
" --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
" Boot entry token to use for this installation\n"
" -q --quiet Suppress unnecessary output\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@@ -5040,6 +5097,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "policy", required_argument, NULL, ARG_POLICY },
{ "force", no_argument, NULL, ARG_FORCE },
{ "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
{ "quiet", no_argument, NULL, 'q' },
{}
};
@@ -5049,7 +5107,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0)
switch (c) {
case 'h':
@@ -5193,6 +5251,10 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case 'q':
arg_quiet = true;
break;
case '?':
return -EINVAL;
@@ -5257,6 +5319,7 @@ static int pcrlock_main(int argc, char *argv[]) {
{ "unlock-raw", VERB_ANY, 1, 0, verb_unlock_simple },
{ "make-policy", VERB_ANY, 1, 0, verb_make_policy },
{ "remove-policy", VERB_ANY, 1, 0, verb_remove_policy },
{ "is-supported", VERB_ANY, 1, 0, verb_is_supported },
{}
};

View File

@@ -450,11 +450,18 @@ typedef enum Tpm2Support {
TPM2_SUPPORT_LIBRARIES = 1 << 4, /* we can dlopen the tpm2 libraries */
TPM2_SUPPORT_API = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES,
/* Flags below are not returned by systemd-analyze has-tpm2 as exit status. */
TPM2_SUPPORT_LIBTSS2_ESYS = 1 << 5, /* we can dlopen libtss2-esys.so.0 */
TPM2_SUPPORT_LIBTSS2_RC = 1 << 6, /* we can dlopen libtss2-rc.so.0 */
TPM2_SUPPORT_LIBTSS2_MU = 1 << 7, /* we can dlopen libtss2-mu.so.0 */
/* Flags below are used by pcrlock, to indicate hardware specific features. It's not used by systemd-analyze has-tpm2. */
TPM2_SUPPORT_AUTHORIZE_NV = 1 << 5, /* chip supports PolicyAuthorizeNV */
TPM2_SUPPORT_SHA256 = 1 << 6, /* chip supports SHA-256 */
TPM2_SUPPORT_API_PCRLOCK = TPM2_SUPPORT_API | TPM2_SUPPORT_AUTHORIZE_NV | TPM2_SUPPORT_SHA256,
/* Flags below are not returned by systemd-analyze has-tpm2 nor by systemd-pcrlock as exit status. */
TPM2_SUPPORT_LIBTSS2_ESYS = 1 << 7, /* we can dlopen libtss2-esys.so.0 */
TPM2_SUPPORT_LIBTSS2_RC = 1 << 8, /* we can dlopen libtss2-rc.so.0 */
TPM2_SUPPORT_LIBTSS2_MU = 1 << 9, /* we can dlopen libtss2-mu.so.0 */
TPM2_SUPPORT_LIBTSS2_ALL = TPM2_SUPPORT_LIBTSS2_ESYS|TPM2_SUPPORT_LIBTSS2_RC|TPM2_SUPPORT_LIBTSS2_MU,
/* Combined flags for generic (i.e. not tool-specific) support */
TPM2_SUPPORT_FULL = TPM2_SUPPORT_API|TPM2_SUPPORT_LIBTSS2_ALL,
} Tpm2Support;

View File

@@ -52,6 +52,7 @@ set -e
test $ret -eq 1
SYSTEMD_COLORS=256 "$SD_PCRLOCK"
"$SD_PCRLOCK" is-supported
"$SD_PCRLOCK" cel --no-pager --json=pretty
"$SD_PCRLOCK" log --pcr="$PCRS"
"$SD_PCRLOCK" log --json=pretty --pcr="$PCRS"