tpm2: various smaller fixes, mostly about better error messages and support checking (#37690)

Fixes: #35187
Fixes: #36743
Fixes: #37607
This commit is contained in:
Lennart Poettering
2025-06-03 18:26:36 +02:00
committed by GitHub
11 changed files with 183 additions and 21 deletions
+4
View File
@@ -173,6 +173,10 @@
specified, defaults to the four banks <literal>sha1</literal>, <literal>sha256</literal>,
<literal>sha384</literal>, <literal>sha512</literal>.</para>
<para>Note that some operating systems disable support for SHA1-based signatures, in which case this
operation will fail. Please consult your OS' documentation for details on how to override the OS
security policy around this.</para>
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>
+31
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" />
@@ -111,6 +111,8 @@ int acquire_luks2_key(
ret_decrypted_key);
if (r == -EREMOTE)
return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
+3 -1
View File
@@ -952,8 +952,10 @@ static int build_policy_digest(bool sign) {
size_t ss = 0;
if (privkey) {
r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss);
if (r == -EADDRNOTAVAIL)
return log_error_errno(r, "Hash algorithm '%s' not available while signing. (Maybe OS security policy disables this algorithm?)", EVP_MD_name(p->md));
if (r < 0)
return log_error_errno(r, "Failed to sign PCR policy: %m");
return log_error_errno(r, "Failed to sign PCR policy with hash algorithm '%s': %m", EVP_MD_name(p->md));
}
_cleanup_free_ void *pubkey_fp = NULL;
+67 -2
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);
@@ -4510,6 +4511,8 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
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.");
_cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
@@ -4631,7 +4634,7 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
&old_policy.prediction,
old_policy.algorithm);
if (r < 0)
return log_error_errno(r, "Failed to submit super PCR policy: %m");
return r;
r = tpm2_policy_authorize_nv(
tc,
@@ -4938,6 +4941,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 +5012,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 +5055,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 +5099,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 +5109,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 +5253,10 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case 'q':
arg_quiet = true;
break;
case '?':
return -EINVAL;
@@ -5257,6 +5321,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 },
{}
};
+2
View File
@@ -1366,6 +1366,8 @@ int decrypt_credential_and_warn(
&tpm2_key);
if (r == -EREMOTE)
return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
#else
+5 -1
View File
@@ -173,6 +173,8 @@ int acquire_tpm2_key(
ret_decrypted_key);
if (r == -EREMOTE)
return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
@@ -223,12 +225,14 @@ int acquire_tpm2_key(
ret_decrypted_key);
if (r == -EREMOTE)
return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0) {
log_error_errno(r, "Failed to unseal secret using TPM2: %m");
/* We get this error in case there is an authentication policy mismatch. This should
* not happen, but this avoids confusing behavior, just in case. */
if (!IN_SET(r, -EPERM, -ENOLCK))
if (r != -ENOLCK)
continue;
}
+9 -2
View File
@@ -1082,6 +1082,8 @@ int digest_and_sign(
const void *data, size_t size,
void **ret, size_t *ret_size) {
int r;
assert(privkey);
assert(ret);
assert(ret_size);
@@ -1099,8 +1101,13 @@ int digest_and_sign(
if (!mdctx)
return log_openssl_errors("Failed to create new EVP_MD_CTX");
if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1)
return log_openssl_errors("Failed to initialize signature context");
if (EVP_DigestSignInit(mdctx, NULL, md, NULL, privkey) != 1) {
/* Distro security policies often disable support for SHA-1. Let's return a recognizable
* error for that case. */
bool invalid_digest = ERR_GET_REASON(ERR_peek_last_error()) == EVP_R_INVALID_DIGEST;
r = log_openssl_errors("Failed to initialize signature context");
return invalid_digest ? -EADDRNOTAVAIL : r;
}
/* Determine signature size */
size_t ss;
+45 -11
View File
@@ -2117,7 +2117,7 @@ int tpm2_create_primary(
/* creationHash= */ NULL,
/* creationTicket= */ NULL);
if (rc == TPM2_RC_BAD_AUTH)
return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to enroll SRK into TPM.");
return log_debug_errno(SYNTHETIC_ERRNO(EDEADLK), "Authorization failure while attempting to create primary key.");
if (rc != TSS2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to generate primary key in TPM: %s",
@@ -3661,6 +3661,11 @@ int tpm2_policy_authorize_nv(
ESYS_TR_PASSWORD,
ESYS_TR_NONE,
ESYS_TR_NONE);
if ((rc & ~(TPM2_RC_N_MASK|TPM2_RC_P)) == TPM2_RC_VALUE) /* Return a recognizable error if the policy
* in the NV index does not match what we
* just put together */
return log_debug_errno(SYNTHETIC_ERRNO(EREMCHG),
"Submitted policy does not match policy stored in PolicyAuthorizeNV.");
if (rc != TSS2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to add AuthorizeNV policy to TPM: %s",
@@ -3705,8 +3710,11 @@ int tpm2_policy_or(
ESYS_TR_NONE,
ESYS_TR_NONE,
&hash_list);
if ((rc & ~(TPM2_RC_N_MASK|TPM2_RC_P)) == TPM2_RC_VALUE) /* Return a recognizable error if none of the OR branches matched */
return log_debug_errno(SYNTHETIC_ERRNO(ENOANO),
"None of the PolicyOR branches matched the current policy state.");
if (rc != TSS2_RC_SUCCESS)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to add OR policy to TPM: %s",
sym_Tss2_RC_Decode(rc));
@@ -5500,6 +5508,20 @@ int tpm2_unseal(Tpm2Context *c,
const struct iovec *srk,
struct iovec *ret_secret) {
/* Returns the following errors:
*
* -EREMOTE → blob is from a different TPM
* -EDEADLK → couldn't create primary key because authorization failure
* -ENOLCK → TPM is in dictionary lockout mode
* -EREMCHG → submitted policy doesn't match NV index stored policy (in case of PolicyAuthorizeNV)
* -ENOANO → none of the PolicyOR branches of a policy matched current state
* -EUCLEAN → PCR state doesn't match expectations
* -EPERM → stored policy does not match TPM state
* -ENOTRECOVERABLE → all other kinds of TPM errors
*
* Of these all four of EREMCHG, ENOANO, EUCLEAN, EPERM can all mean that PCR state is not matching
* expectations. */
TSS2_RC rc;
int r;
@@ -6649,14 +6671,11 @@ int tpm2_calculate_policy_super_pcr(
/* First we look for all PCRs that have exactly one allowed hash value, and generate a single PolicyPCR policy from them */
_cleanup_free_ Tpm2PCRValue *single_values = NULL;
size_t n_single_values = 0;
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
if (!BIT_SET(prediction->pcrs, pcr))
continue;
BIT_FOREACH(pcr, prediction->pcrs) {
if (ordered_set_size(prediction->results[pcr]) != 1)
continue;
log_debug("Including PCR %" PRIu32 " in single value PolicyPCR expression", pcr);
log_debug("Including PCR %i in single value PolicyPCR expression", pcr);
Tpm2PCRPredictionResult *banks = ASSERT_PTR(ordered_set_first(prediction->results[pcr]));
@@ -6780,8 +6799,17 @@ int tpm2_policy_super_pcr(
session,
&pcr_selection,
&current_policy_digest);
if (r == -EUCLEAN) {
_cleanup_free_ char *j = NULL;
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++)
if (single_value_pcrs & (UINT32_C(1) << pcr))
(void) strextendf_with_separator(&j, ", ", "%" PRIu32, pcr);
return log_error_errno(r, "Combined value for PCR(s) %s encoded in policy does not match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", strna(j));
}
if (r < 0)
return r;
return log_error_errno(r, "Failed to submit PCR policy to TPM: %m");
previous_policy_digest = *current_policy_digest;
}
@@ -6810,8 +6838,10 @@ int tpm2_policy_super_pcr(
session,
&pcr_selection,
&current_policy_digest);
if (r == -EUCLEAN)
return log_error_errno(r, "Value for PCR %" PRIu32 " encoded in policy does not match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", pcr);
if (r < 0)
return r;
return log_error_errno(r, "Failed to submit PCR policy to TPM: %m");
_cleanup_free_ TPM2B_DIGEST *branches = NULL;
branches = new0(TPM2B_DIGEST, n_branches);
@@ -6836,7 +6866,7 @@ int tpm2_policy_super_pcr(
/* n_pcr_values= */ 1,
&pcr_policy_digest);
if (r < 0)
return r;
return log_error_errno(r, "Failed to calculate PolicyPCR: %m");
branches[i++] = pcr_policy_digest;
}
@@ -6850,8 +6880,10 @@ int tpm2_policy_super_pcr(
branches,
n_branches,
&current_policy_digest);
if (r == -ENOANO)
return log_error_errno(r, "None of the alternative values for PCR %" PRIu32 " encoded in policy match the current TPM state. Either the system has been tempered with or the provided policy is incorrect.", pcr);
if (r < 0)
return r;
return log_error_errno(r, "Failed to submit OR policy to TPM: %m");
previous_policy_digest = *current_policy_digest;
}
@@ -7810,6 +7842,8 @@ int verb_has_tpm2_generic(bool quiet) {
print_field(" ", "libtss2-mu.so.0", FLAGS_SET(s, TPM2_SUPPORT_LIBTSS2_MU));
}
assert_cc(TPM2_SUPPORT_API <= 255); /* make sure this is safe to use as process exit status */
/* Return inverted bit flags. So that TPM2_SUPPORT_FULL becomes EXIT_SUCCESS and the other values
* become some reasonable values 1…7. i.e. the flags we return here tell what is missing rather than
* what is there, acknowledging the fact that for process exit statuses it is customary to return
+14 -4
View File
@@ -283,6 +283,9 @@ int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBL
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk);
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec known_policy_hash[], size_t n_known_policy_hash, const struct iovec *srk, struct iovec *ret_secret);
/* tpm2_unseal() returns a bunch of different errors for various flavours of PCR issues, let's group them */
#define ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r) IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM)
#if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret);
@@ -450,11 +453,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;
+1
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"