From: Kai Lüke Date: Mon, 20 Apr 2026 06:55:57 +0000 (+0900) Subject: cryptsetup/cryptenroll: Iterate over TPM tokens when they don't match X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9213de2e067703debe34c91bedece359cc4cc95c;p=thirdparty%2Fsystemd.git cryptsetup/cryptenroll: Iterate over TPM tokens when they don't match When we enroll two UKIs with different PCR pub keys into one LUKS slot/ token each, then we can encounter the wrong one and should not give up but continue iterating instead of requiring the passphrase. Similarly, a pcrlock token might be for another UKI and we should continue the search. Same for a token that is meant for another TPM (e.g., external storage). While this is mainly about cryptsetup's automatic unlocking from the initrd, it also matters for usage in the system, e.g., for other storage and when cryptenroll should unlock using the TPM. There are two code paths, one is the libcryptsetup plugin and the other is the fallback when that's not available. To let the libcryptsetup loop continue to iterate, remap the above error conditions to EPERM. For the fallback path check all of them (no remapping) and continue iteration. For better log output, include the token ID to be able understand which token fails. --- diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index f31ed2e7eab..8c64daa3c88 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -260,7 +260,10 @@ int load_volume_key_tpm2( &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed"); - if (r != -EPERM) + /* Stop unless we should keep iterating to next token because the tried one + * does not match boot state. For now without -EUCLEAN because currently the + * only error it reports won't be solved by moving to another token. */ + if (!ERRNO_IS_NEG_TPM2_TOKEN_MISMATCH(r)) break; token++; /* try a different token next time */ diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index d8d233fabb6..20876cc71b6 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -23,13 +23,22 @@ _public_ const char *cryptsetup_token_version(void) { return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" PROJECT_VERSION_FULL " (" GIT_VERSION ")"; } -static int log_debug_open_error(struct crypt_device *cd, int r) { +static int log_debug_open_error(struct crypt_device *cd, int token, int r) { if (r == -EAGAIN) return crypt_log_debug_errno(cd, r, "TPM2 device not found."); - if (r == -ENOSTR) - return crypt_log_debug_errno(cd, r, "No matching TPM2 token data found."); + if (r == -ENOSTR) { + /* Remap to -EPERM so libcryptsetup's CRYPT_ANY_TOKEN loop keeps iterating. */ + (void) crypt_log_debug_errno(cd, r, "Token %d: no matching TPM2 token data found.", token); + return -EPERM; + } + if (IN_SET(r, -EREMCHG, -EREMOTE)) { + /* Remap as above. Note: For now without -EUCLEAN because currently the only error it + * reports won't be solved by moving to another token. */ + (void) crypt_log_debug_errno(cd, r, "Token %d: TPM policy does not match current system state, skipping.", token); + return -EPERM; + } - return crypt_log_debug_errno(cd, r, TOKEN_NAME " open failed: %m."); + return crypt_log_debug_errno(cd, r, "Token %d: open failed: %m.", token); } _public_ int cryptsetup_token_open_pin( @@ -78,8 +87,11 @@ _public_ int cryptsetup_token_open_pin( params = *(systemd_tpm2_plugin_params *)usrptr; r = sd_json_parse(json, SD_JSON_PARSE_MUST_BE_OBJECT, &v, /* reterr_line= */ NULL, /* reterr_column= */ NULL); - if (r < 0) - return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); + if (r < 0) { + /* Remap to -EPERM so libcryptsetup keeps iterating past a broken token. */ + (void) crypt_log_debug_errno(cd, r, "Token %d: failed to parse JSON data: %m", token); + return -EPERM; + } struct iovec *blobs = NULL, *policy_hash = NULL; size_t n_blobs = 0, n_policy_hash = 0; @@ -104,10 +116,13 @@ _public_ int cryptsetup_token_open_pin( &pcrlock_nv, &flags); if (r < 0) - return log_debug_open_error(cd, r); + return log_debug_open_error(cd, token, r); - if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask) - return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask); + if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask) { + /* Remap to -EPERM so libcryptsetup keeps iterating to the next token. */ + crypt_log_debug(cd, "Token %d: PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", token, hash_pcr_mask, params.search_pcr_mask); + return -EPERM; + } r = acquire_luks2_key( params.device, @@ -130,12 +145,12 @@ _public_ int cryptsetup_token_open_pin( flags, &decrypted_key); if (r < 0) - return log_debug_open_error(cd, r); + return log_debug_open_error(cd, token, r); /* Before using this key as passphrase we base64 encode it, for compat with homed */ base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded); if (base64_encoded_size < 0) - return log_debug_open_error(cd, base64_encoded_size); + return log_debug_open_error(cd, token, base64_encoded_size); /* free'd automatically by libcryptsetup */ *ret_password = TAKE_PTR(base64_encoded); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 5f02e64da88..36058b249d0 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -2181,7 +2181,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking."); - if (r != -EPERM) + /* Stop unless we should keep iterating to next token because the tried one + * does not match boot state. For now without -EUCLEAN because currently the + * only error it reports won't be solved by moving to another token. */ + if (!ERRNO_IS_NEG_TPM2_TOKEN_MISMATCH(r)) break; token++; /* try a different token next time */ diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 88098acac8c..7fca284d699 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -49,6 +49,14 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { int dlopen_tpm2(int log_level); +/* tpm2_unseal() returns a bunch of different errors for various flavours of PCR issues, let's group them. + * Kept outside the HAVE_TPM2 guard as callers switch on these errnos even in builds without TPM2. */ +#define ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r) IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM) + +/* Errors that mean the tried TPM2 token does not match the boot state, be it due to wrong PCR state, a + * different PCR signing key/policy, or even a different TPM. The caller should keep trying other tokens. */ +#define ERRNO_IS_NEG_TPM2_TOKEN_MISMATCH(r) IN_SET(r, -EREMCHG, -ENOANO, -EPERM, -ENOSTR, -EREMOTE) + #if HAVE_TPM2 #ifndef SYSTEMD_CFLAGS_MARKER_TPM2 # error "missing tpm2_cflags in meson dependency." @@ -378,9 +386,6 @@ int tpm2_get_or_create_ek(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLI 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, const char *pubkey_policy_ref, 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);