diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 14bd26de27..f7e788dbc5 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -7594,34 +7594,24 @@ int tpm2_write_policy_nv_index( } #if HAVE_OPENSSL -/* Calculates the write policy of a NvPCR, which is TPM2_PolicyAuthorize OR TPM2_PolicyNvWritten(true), - * where the authorized policy is bound to the supplied public key and policy ref. Returns the overall write - * policy, and optionally, the PolicyAuthorize branch digest (which the extend path needs later on in order - * to reconstruct the PolicyOR). */ +/* Calculates the write policy of a NvPCR, which is 'init_policy' OR TPM2_PolicyNvWritten(true). + * Returns the overall write policy. */ static int tpm2_nvpcr_calculate_write_policy( - const TPM2B_PUBLIC *public, - const char *pubkey_policy_ref, - TPM2B_DIGEST *ret_authorize_policy, + const TPM2B_DIGEST *init_policy, TPM2B_DIGEST *ret_write_policy) { int r; - assert(public); + assert(init_policy); + assert(init_policy->size == SHA256_DIGEST_SIZE); assert(ret_write_policy); - TPM2B_NONCE policy_ref; - tpm2_make_policy_ref(pubkey_policy_ref, &policy_ref); - - /* Branch order matters here and must match the reconstruction in tpm2_nvpcr_open_write_session. */ + /* Branch order matters here and must match the reconstruction in tpm2_nvpcr_policy_or_written(). */ TPM2B_DIGEST branches[2] = { - TPM2B_DIGEST_MAKE(NULL, SHA256_DIGEST_SIZE), /* PolicyAuthorize branch */ + *init_policy, /* Init branch */ TPM2B_DIGEST_MAKE(NULL, SHA256_DIGEST_SIZE), /* PolicyNvWritten branch */ }; - r = tpm2_calculate_policy_authorize(public, &policy_ref, &branches[0]); - if (r < 0) - return r; - r = tpm2_calculate_policy_nv_written(/* written_set= */ true, &branches[1]); if (r < 0) return r; @@ -7631,8 +7621,6 @@ static int tpm2_nvpcr_calculate_write_policy( if (r < 0) return r; - if (ret_authorize_policy) - *ret_authorize_policy = branches[0]; *ret_write_policy = write_policy; return 0; } @@ -7895,26 +7883,24 @@ static int tpm2_define_nvpcr_nv_index( const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, - const TPM2B_PUBLIC *public, - const char *pubkey_policy_ref, + const TPM2B_DIGEST *init_policy, bool orderly, - TPM2B_DIGEST *ret_authorize_policy, Tpm2Handle **ret_nv_handle) { TSS2_RC rc; int r; assert(c); - assert(public); + assert(init_policy); /* Allocates an nvindex to use as a "fake" PCR. We call these "NvPCR" in our codebase. The index is * created to require a policy for writing, so that the first write (which sets TPMA_NV_WRITTEN) - * requires a signed PolicyAuthorize authorization, while all subsequent writes can be performed using - * a PolicyNvWritten(true) branch without any further authorization. The intention here is that a - * NvPCR can only be defined and initialized for as long as the authorized policy can be satisfied - * (ie, during an early boot phase). Once the authorized policy can no longer be satisfied (ie, after - * the early boot has ended), it is no longer possible to redefine and initialize the same NvPCR in - * order to spoof its measurements. */ + * requires 'init_policy' to be satisfied, while all subsequent writes can be performed using a + * PolicyNvWritten(true) branch without any further authorization. The intention here is that a + * NvPCR can only be defined and initialized for as long as 'init_policy' can be satisfied (ie, + * during an early boot phase). Once it can no longer be satisfied (ie, after the early boot has + * ended), it is no longer possible to redefine and initialize the same NvPCR in order to spoof its + * measurements. */ log_debug("Allocating NvPCR index 0x%" PRIx32 ".", nv_index); @@ -7936,11 +7922,10 @@ static int tpm2_define_nvpcr_nv_index( "/run/systemd/tpm2-nv-space-exhausted-orderly" : "/run/systemd/tpm2-nv-space-exhausted-non-orderly"; - /* Calculate the write policy, which is TPM2_PolicyAuthorize() OR TPM2_PolicyNvWritten(true). The - * PolicyAuthorize() branch is used for the first write, but we need to retain the digest of that so - * that we can reconstruct the digests used for the TPM2_PolicyOR() assertion on subsequent writes. */ - TPM2B_DIGEST authorize_policy, write_policy; - r = tpm2_nvpcr_calculate_write_policy(public, pubkey_policy_ref, &authorize_policy, &write_policy); + /* Calculate the write policy, which is init_policy OR TPM2_PolicyNvWritten(true). The init_policy + * branch is used for the first write and can only be satisfied during early boot. */ + TPM2B_DIGEST write_policy; + r = tpm2_nvpcr_calculate_write_policy(init_policy, &write_policy); if (r < 0) return r; @@ -7973,8 +7958,6 @@ static int tpm2_define_nvpcr_nv_index( log_debug("NV index 0x%" PRIx32 " successfully %s for NvPCR.", nv_index, r == 0 ? "reused" : "reallocated"); - if (ret_authorize_policy) - *ret_authorize_policy = authorize_policy; if (ret_nv_handle) *ret_nv_handle = TAKE_PTR(handle); @@ -8012,8 +7995,6 @@ static int tpm2_define_nvpcr_nv_index( if (rc == TSS2_RC_SUCCESS) { log_debug("NV index 0x%" PRIx32 " successfully allocated for NvPCR.", nv_index); - if (ret_authorize_policy) - *ret_authorize_policy = authorize_policy; if (ret_nv_handle) *ret_nv_handle = TAKE_PTR(new_handle); @@ -8041,8 +8022,6 @@ static int tpm2_define_nvpcr_nv_index( log_debug("NV index 0x%" PRIx32 " successfully %s for NvPCR.", nv_index, r == 0 ? "reused" : "reallocated"); - if (ret_authorize_policy) - *ret_authorize_policy = authorize_policy; if (ret_nv_handle) *ret_nv_handle = TAKE_PTR(handle); @@ -8898,9 +8877,9 @@ static int tpm2_nvpcr_load_pcr_public_key( return 0; } -/* Reads the stored PolicyAuthorize branch digest of a previously initialized NvPCR. This is written by +/* Reads the stored init policy digest of a previously initialized NvPCR. This is written by * tpm2_nvpcr_initialize() and is needed to reconstruct the TPM2_PolicyOR for runtime extends. */ -static int tpm2_nvpcr_read_authorize_policy(const char *name, TPM2B_DIGEST *ret) { +static int tpm2_nvpcr_read_init_policy(const char *name, TPM2B_DIGEST *ret) { int r; assert(name); @@ -8913,82 +8892,137 @@ static int tpm2_nvpcr_read_authorize_policy(const char *name, TPM2B_DIGEST *ret) if (r == -ENOENT) return log_debug_errno(SYNTHETIC_ERRNO(ENETDOWN), "NvPCR '%s' not initialized yet, refusing.", name); if (r < 0) - return log_debug_errno(r, "Failed to read NvPCR authorize policy '%s': %m", fname); + return log_debug_errno(r, "Failed to read NvPCR init policy '%s': %m", fname); _cleanup_free_ void *d = NULL; size_t d_size; r = unhexmem(strstrip(h), &d, &d_size); if (r < 0) - return log_debug_errno(r, "Failed to decode NvPCR authorize policy from '%s': %m", fname); + return log_debug_errno(r, "Failed to decode NvPCR init policy from '%s': %m", fname); if (d_size != SHA256_DIGEST_SIZE) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR authorize policy in '%s' has unexpected size.", fname); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "NvPCR init policy in '%s' has unexpected size.", fname); *ret = TPM2B_DIGEST_MAKE(d, d_size); return 0; } -/* Opens and executes a policy session that satisfies an NvPCR's write policy, leaving it ready for a - * subsequent extend. If 'signature_json' is provided, the PolicyAuthorize branch is used (for the - * initializing write that sets TPMA_NV_WRITTEN); otherwise the PolicyNvWritten(true) branch is used (for - * all later writes). */ -static int tpm2_nvpcr_open_write_session( +/* Policy reference for the PolicyAuthorize assertion that guards the first write to an NvPCR. */ +#define NVPCR_INIT_POLICY_REF "initrd" + +/* PCR mask for the policy that guards the first write to an NvPCR. */ +#define NVPCR_INIT_PCRMASK (UINT32_C(1) << TPM2_PCR_KERNEL_BOOT) + +/* The init policy of an NvPCR, i.e. the branch of its write policy that guards the initializing write, + * together with everything needed to satisfy it. */ +typedef struct NvPCRInitPolicy { + TPM2B_DIGEST digest; + TPML_PCR_SELECTION pcr_selection; + TPM2B_PUBLIC public; + struct iovec fingerprint; + sd_json_variant *signature_json; +} NvPCRInitPolicy; + +static void nvpcr_init_policy_done(NvPCRInitPolicy *p) { + assert(p); + + iovec_done(&p->fingerprint); + p->signature_json = sd_json_variant_unref(p->signature_json); +} + +/* Completes the write policy of an NvPCR in 'session' by submitting PolicyOR('init_policy', + * PolicyNvWritten(true)). 'session' must already satisfy one of the two branches. */ +static int tpm2_nvpcr_policy_or_written( Tpm2Context *c, - const TPM2B_PUBLIC *public, - const char *pubkey_policy_ref, - uint32_t pubkey_pcr_mask, - const struct iovec *fingerprint, - sd_json_variant *signature_json, - const TPM2B_DIGEST *authorize_policy, - Tpm2Handle **ret_session) { + const Tpm2Handle *session, + const TPM2B_DIGEST *init_policy) { int r; assert(c); - assert(authorize_policy); - assert(ret_session); - - _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; - r = tpm2_make_policy_session(c, /* primary= */ NULL, /* encryption_session= */ NULL, &session); - if (r < 0) - return r; - - if (signature_json) { - assert(public); - assert(fingerprint); - - /* Initializing write: satisfy the PolicyAuthorize branch using the signed PCR policy. */ - TPML_PCR_SELECTION pcr_selection; - tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, TPM2_ALG_SHA256, &pcr_selection); - - r = tpm2_policy_authorize( - c, - session, - &pcr_selection, - public, - pubkey_policy_ref, - fingerprint->iov_base, fingerprint->iov_len, - signature_json, - /* ret_policy_digest= */ NULL); - if (r < 0) - return r; - } else { - /* Subsequent write: the NvPCR is already written, so the PolicyNvWritten(true) branch can be - * satisfied without any authorization. */ - r = tpm2_policy_nv_written(c, session, /* written_set= */ true, /* ret_policy_digest= */ NULL); - if (r < 0) - return r; - } + assert(session); + assert(init_policy); /* Branch order must match tpm2_nvpcr_calculate_write_policy(). */ TPM2B_DIGEST branches[2] = { - *authorize_policy, + *init_policy, /* Init branch */ TPM2B_DIGEST_MAKE(NULL, SHA256_DIGEST_SIZE), /* PolicyNvWritten branch */ }; r = tpm2_calculate_policy_nv_written(/* written_set= */ true, &branches[1]); if (r < 0) return r; - r = tpm2_policy_or(c, session, branches, ELEMENTSOF(branches), /* ret_policy_digest= */ NULL); + return tpm2_policy_or(c, session, branches, ELEMENTSOF(branches), /* ret_policy_digest= */ NULL); +} + +/* Opens a policy session that satisfies the init branch of an NvPCR's write policy, for the initializing + * write that sets TPMA_NV_WRITTEN. */ +static int tpm2_nvpcr_open_init_session( + Tpm2Context *c, + const NvPCRInitPolicy *init, + Tpm2Handle **ret_session) { + + _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; + TPML_PCR_SELECTION pcr_selection; + int r; + + assert(c); + assert(init); + assert(iovec_is_set(&init->fingerprint)); + assert(init->signature_json); + assert(ret_session); + + r = tpm2_make_policy_session(c, /* primary= */ NULL, /* encryption_session= */ NULL, &session); + if (r < 0) + return r; + + /* tpm2_policy_authorize() takes a non-const selection. */ + pcr_selection = init->pcr_selection; + + /* Satisfy the PolicyAuthorize branch using the signed PCR policy. */ + r = tpm2_policy_authorize( + c, + session, + &pcr_selection, + &init->public, + NVPCR_INIT_POLICY_REF, + init->fingerprint.iov_base, init->fingerprint.iov_len, + init->signature_json, + /* ret_policy_digest= */ NULL); + if (r < 0) + return r; + + r = tpm2_nvpcr_policy_or_written(c, session, &init->digest); + if (r < 0) + return r; + + *ret_session = TAKE_PTR(session); + return 0; +} + +/* Opens a policy session that satisfies the PolicyNvWritten(true) branch of an NvPCR's write policy, for all + * writes after the initializing one. 'init_policy' is the digest of the init branch, needed for the + * PolicyOR. */ +static int tpm2_nvpcr_open_extend_session( + Tpm2Context *c, + const TPM2B_DIGEST *init_policy, + Tpm2Handle **ret_session) { + + _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; + int r; + + assert(c); + assert(init_policy); + assert(ret_session); + + r = tpm2_make_policy_session(c, /* primary= */ NULL, /* encryption_session= */ NULL, &session); + if (r < 0) + return r; + + r = tpm2_policy_nv_written(c, session, /* written_set= */ true, /* ret_policy_digest= */ NULL); + if (r < 0) + return r; + + r = tpm2_nvpcr_policy_or_written(c, session, init_policy); if (r < 0) return r; @@ -9028,10 +9062,10 @@ static int nvpcr_extend_bytes( * and our measurement and change either */ log_fd = tpm2_userspace_log_open(); - /* Read the stored PolicyAuthorize branch digest. Its presence also tells us this NvPCR has been + /* Read the stored init policy digest. Its presence also tells us this NvPCR has been * initialized; we need it to reconstruct the TPM2_PolicyOR below. */ - TPM2B_DIGEST authorize_policy; - r = tpm2_nvpcr_read_authorize_policy(name, &authorize_policy); + TPM2B_DIGEST init_policy; + r = tpm2_nvpcr_read_init_policy(name, &init_policy); if (r < 0) return r; @@ -9078,15 +9112,7 @@ static int nvpcr_extend_bytes( /* Open a policy session that satisfies the write policy via the PolicyNvWritten(true) branch * (the index was already written during initialization). */ _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = tpm2_nvpcr_open_write_session( - c, - /* public= */ NULL, - /* pubkey_policy_ref= */ NULL, - /* pubkey_pcr_mask= */ 0, - /* fingerprint= */ NULL, - /* signature_json= */ NULL, - &authorize_policy, - &policy_session); + r = tpm2_nvpcr_open_extend_session(c, &init_policy, &policy_session); if (r < 0) return r; @@ -9191,15 +9217,35 @@ static int tpm2_context_can_nvindex(Tpm2Context *c) { } #endif -/* Policy reference for the PolicyAuthorize assertion that guards the first write to an NvPCR. */ -#define NVPCR_INIT_POLICY_REF "initrd" - -/* PCR mask for the policy that guards the first write to an NvPCR, via the signed policy and PolicyAuthorize. */ -#define NVPCR_PUBKEY_PCRMASK (UINT32_C(1) << TPM2_PCR_KERNEL_BOOT) - /* The maximum number of times to try initializing a NvPCR before failing if PCR values change under our feet. */ #define RETRY_NVPCR_INIT_MAX 30u +#if HAVE_OPENSSL +/* Calculate the init policy for an NvPCR protected by a signed PCR policy: + * PolicyAuthorize is bound to 'public' and the "initrd" policy reference. */ +static int tpm2_nvpcr_calculate_signed_init_policy( + const TPM2B_PUBLIC *public, + TPM2B_DIGEST *ret) { + + TPM2B_NONCE policy_ref; + TPM2B_DIGEST init_policy; + int r; + + assert(public); + assert(ret); + + tpm2_make_policy_ref(NVPCR_INIT_POLICY_REF, &policy_ref); + + init_policy = TPM2B_DIGEST_MAKE(NULL, SHA256_DIGEST_SIZE); + r = tpm2_calculate_policy_authorize(public, &policy_ref, &init_policy); + if (r < 0) + return r; + + *ret = init_policy; + return 0; +} +#endif + int tpm2_nvpcr_initialize( Tpm2Context *c, const Tpm2Handle *session, @@ -9269,11 +9315,11 @@ int tpm2_nvpcr_initialize( if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash function result too large for TPM, refusing."); + _cleanup_(nvpcr_init_policy_done) NvPCRInitPolicy init = {}; + /* Load the PCR public key. Its signatures authorize the initializing write to the NvPCR via the * PolicyAuthorize branch of the write policy. */ - TPM2B_PUBLIC public; - _cleanup_(iovec_done) struct iovec fingerprint = {}; - r = tpm2_nvpcr_load_pcr_public_key(/* path= */ NULL, &public, &fingerprint); + r = tpm2_nvpcr_load_pcr_public_key(/* path= */ NULL, &init.public, &init.fingerprint); if (r == -ENOENT) /* Avoid failing systemd-tpm2-setup if there is no PCR public key attached to the UKI. */ return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY), @@ -9282,8 +9328,7 @@ int tpm2_nvpcr_initialize( return r; /* Load the signed PCR policy, which authorizes the initializing write. */ - _cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL; - r = tpm2_load_pcr_signature(/* path= */ NULL, &signature_json); + r = tpm2_load_pcr_signature(/* path= */ NULL, &init.signature_json); if (r == -ENOENT) /* Avoid failing systemd-tpm2-setup if there are no PCR signatures attached to the UKI. */ return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY), @@ -9296,12 +9341,11 @@ int tpm2_nvpcr_initialize( * policy reference. We don't check the policy digest here: if there is a matching policy but it doesn't * match the current PCR state, or its signature is invalid, then that's a real error which we * want to report below. */ - TPML_PCR_SELECTION pcr_selection; - tpm2_tpml_pcr_selection_from_mask(NVPCR_PUBKEY_PCRMASK, TPM2_ALG_SHA256, &pcr_selection); + tpm2_tpml_pcr_selection_from_mask(NVPCR_INIT_PCRMASK, TPM2_ALG_SHA256, &init.pcr_selection); r = find_signature( - signature_json, - &pcr_selection, - fingerprint.iov_base, fingerprint.iov_len, + init.signature_json, + &init.pcr_selection, + init.fingerprint.iov_base, init.fingerprint.iov_len, NVPCR_INIT_POLICY_REF, /* policy= */ NULL, /* policy_size= */ 0, /* ret_signature= */ NULL, /* ret_signature_size= */ NULL); @@ -9312,17 +9356,18 @@ int tpm2_nvpcr_initialize( if (r < 0) return r; - TPM2B_DIGEST authorize_policy; + r = tpm2_nvpcr_calculate_signed_init_policy(&init.public, &init.digest); + if (r < 0) + return r; + _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; r = tpm2_define_nvpcr_nv_index( c, session, p.nv_index, p.algorithm, - &public, - NVPCR_INIT_POLICY_REF, + &init.digest, p.orderly, - &authorize_policy, &nv_handle); if (r < 0) return r; @@ -9333,15 +9378,7 @@ int tpm2_nvpcr_initialize( for (unsigned i = RETRY_NVPCR_INIT_MAX;; i--) { /* Open a policy session to perform the initializing write. */ _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; - r = tpm2_nvpcr_open_write_session( - c, - &public, - NVPCR_INIT_POLICY_REF, - NVPCR_PUBKEY_PCRMASK, - &fingerprint, - signature_json, - &authorize_policy, - &policy_session); + r = tpm2_nvpcr_open_init_session(c, &init, &policy_session); if (r == -EUCLEAN) { /* A PCR was extended while we submitted the policy, so this session is unusable. * Same situation as below, just observed while building the policy rather than @@ -9379,9 +9416,9 @@ int tpm2_nvpcr_initialize( break; } - /* Persist the PolicyAuthorize branch digest. The extend path needs it to reconstruct the + /* Persist the init policy digest. The extend path needs it to reconstruct the * TPM2_PolicyOR, and its presence doubles as an indication that this NvPCR is initialized. */ - _cleanup_free_ char *h = hexmem(authorize_policy.buffer, authorize_policy.size); + _cleanup_free_ char *h = hexmem(init.digest.buffer, init.digest.size); if (!h) return log_oom_debug();