mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
tpm2-util: fall back to a PCR policy for NvPCR initialization
If the booted UKI carries no PCR public key or no signed policy with the "initrd" reference, initialize NvPCRs in the initrd with a PolicyPCR init policy bound to the current value of PCR 11 instead of not initializing them at all. The resulting NvPCR names depend on PCR 11 and hence change with every UKI update, so this is only useful where PCR 11 can be predicted. Signed-off-by: Paul Meyer <katexochen0@gmail.com>
This commit is contained in:
1 parent
13c417c1fa
commit
7d37482ce4
6 files changed
+338
-136
No files matched your search
@@ -58,6 +58,16 @@ CHANGES WITH 263 in spe:
|
||||
available, for example because they ship new or changed transfer
|
||||
files. systemd-sysupdate-update.service now uses --loop=16.
|
||||
|
||||
Changes in the TPM Subsystem:
|
||||
|
||||
* If the booted UKI carries no PCR public key or no signed PCR policy
|
||||
with the "initrd" policy reference, NvPCRs are now initialized in the
|
||||
initrd with a write policy bound to the current value of PCR 11,
|
||||
instead of not being initialized at all. The names of such NvPCRs,
|
||||
which are measured into PCR 9, change whenever the UKI changes, so
|
||||
this is mostly useful where the value of PCR 11 in the initrd is
|
||||
predictable.
|
||||
|
||||
CHANGES WITH 262:
|
||||
|
||||
Announcements of Future Feature Removals and Incompatible Changes:
|
||||
|
||||
@@ -106,6 +106,20 @@ policy or different attributes that permit initialization during runtime can be
|
||||
detected because the name of the NV index will not match the name that was
|
||||
previously measured to PCR 9.
|
||||
|
||||
If the booted UKI carries no PCR public key, or no signed PCR policy with the
|
||||
`initrd` policy reference for it, the branch for the first write is instead a
|
||||
`TPM2_PolicyPCR` assertion bound to the value of the kernel boot PCR (11) at the
|
||||
time the NvPCR is initialized. This fallback is only used in the initrd, where
|
||||
PCR 11 has a value that can no longer be reached once the initrd has been left.
|
||||
The same considerations as above apply, but the name of the NvPCR then depends
|
||||
on the value of PCR 11 in the initrd, and hence changes whenever the UKI changes.
|
||||
A verifier has to calculate the expected name from the expected value of PCR 11
|
||||
in the initrd (e.g. as calculated by `systemd-measure calculate
|
||||
--phase=enter-initrd`). This is hence only useful where that value can be
|
||||
predicted. Note that on systems that are not booted via a UKI, PCR 11 does not
|
||||
cover the kernel and initrd, and the name of the NvPCR hence does not depend on
|
||||
them.
|
||||
|
||||
## PCR Measurements Made by `systemd-boot` (UEFI)
|
||||
|
||||
### PCR 1, `EV_EVENT_TAG`, SMBIOS information
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
<para><filename>systemd-tpm2-setup.service</filename> and
|
||||
<filename>systemd-tpm2-setup-early.service</filename> are services that generate the Storage Root Key
|
||||
(SRK) and Endorsement Key (EK) if they have not been generated yet, and stores them in the TPM. If
|
||||
NvPCRs (additional PCR registers in TPM NV Indexes) are defined, these are initialized with the
|
||||
anchoring secret.</para>
|
||||
NvPCRs (additional PCR registers in TPM NV Indexes) are defined, these are initialized. NvPCRs can only
|
||||
be initialized from the initrd. Their initial write is authorized by the signed PCR policy with the
|
||||
<literal>initrd</literal> policy reference embedded in the booted UKI, or, if there is none, by a PCR
|
||||
policy bound to the current value of PCR 11.</para>
|
||||
|
||||
<para>The services will store the public key of the SRK key pair in a PEM file in
|
||||
<filename>/run/systemd/tpm2-srk-public-key.pem</filename> and
|
||||
|
||||
@@ -365,8 +365,10 @@ static int extend_nvpcr_now(
|
||||
secret,
|
||||
event,
|
||||
safe);
|
||||
if (IN_SET(r, -ENOBUFS, -ENETDOWN))
|
||||
return r; /* NV space exhausted or NvPCR not initialized; let caller handle gracefully */
|
||||
if (r == -ENOBUFS)
|
||||
return r; /* NV space exhausted; let caller handle gracefully */
|
||||
if (r == -ENETDOWN)
|
||||
return log_error_errno(r, "NvPCR '%s' is not initialized, cannot extend it.", name);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not extend NvPCR: %m");
|
||||
|
||||
@@ -599,12 +601,6 @@ static int run(int argc, char *argv[]) {
|
||||
log_notice_errno(r, "TPM2 cannot be used for measurement (no usable PCR bank, missing device, or missing crypto support), skipping gracefully.");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (r == -ENETDOWN) {
|
||||
/* The NvPCR couldn't be initialized in this boot (e.g. because the booted kernel image
|
||||
* carries no suitable PCR signature), hence there's nothing to extend. */
|
||||
log_notice_errno(r, "NvPCR '%s' is not initialized, skipping gracefully.", arg_nvpcr_name);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (r == -ENOBUFS) {
|
||||
log_notice_errno(r, "TPM NV index space is exhausted, NvPCR '%s' could not be initialized, skipping gracefully.", arg_nvpcr_name);
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
+213
-93
@@ -7997,7 +7997,7 @@ static int tpm2_reuse_or_redefine_nvpcr_nv_index(
|
||||
"Failed to acquire handle to NV index 0x%" PRIx32 ".", nv_index);
|
||||
|
||||
/* Check if the existing index has the same name as the one we're trying to define. We expect
|
||||
* this on any system where this NvPCR was created with the current pubkey on a previous
|
||||
* this on any system where this NvPCR was created with the same init policy on a previous
|
||||
* boot, and it should always be uninitialized (ie, TPMA_NV_WRITTEN unset) at this point. */
|
||||
TPM2B_NAME nv_name_expected;
|
||||
r = tpm2_calculate_nv_index_name(&nv_public_info->nvPublic, &nv_name_expected);
|
||||
@@ -8022,11 +8022,12 @@ static int tpm2_reuse_or_redefine_nvpcr_nv_index(
|
||||
"free sufficient space to allocate the NvPCR.", nv_index);
|
||||
|
||||
/* Check if the existing index looks like it might have been a NvPCR - it might have a
|
||||
* policy that's different because the signing key for the PolicyAuthorize assertion changed,
|
||||
* or it might be an old style NvPCR. In this case, we'll undefine it and create a new one.
|
||||
* This handles updates from old style NvPCRs, and rotation of the authorized policy signing
|
||||
* key. We just check the attributes here - we know that the nvIndex is the same, and the
|
||||
* dataSize for a valid extend index matches the size of nameAlg so we don't need to check that. */
|
||||
* policy that's different because its init policy changed (the signing key for the
|
||||
* PolicyAuthorize assertion was rotated, the PCR value bound by a PolicyPCR init policy changed,
|
||||
* or the init policy switched between the two), or it might be an old style NvPCR. In this case,
|
||||
* we'll undefine it and create a new one. We just check the attributes here - we know that the
|
||||
* nvIndex is the same, and the dataSize for a valid extend index matches the size of nameAlg so
|
||||
* we don't need to check that. */
|
||||
const TPMA_NV old_expected_attrs =
|
||||
TPMA_NV_CLEAR_STCLEAR |
|
||||
TPMA_NV_OWNERWRITE |
|
||||
@@ -8079,6 +8080,11 @@ static int tpm2_reuse_or_redefine_nvpcr_nv_index(
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Flag files marking that the TPM's NV index space was found exhausted in this boot, separately for orderly
|
||||
* and non-orderly NvPCRs. */
|
||||
#define NVPCR_SPACE_EXHAUSTED_FLAG_ORDERLY "/run/systemd/tpm2-nv-space-exhausted-orderly"
|
||||
#define NVPCR_SPACE_EXHAUSTED_FLAG_NON_ORDERLY "/run/systemd/tpm2-nv-space-exhausted-non-orderly"
|
||||
|
||||
static int tpm2_define_nvpcr_nv_index(
|
||||
Tpm2Context *c,
|
||||
const Tpm2Handle *session,
|
||||
@@ -8120,8 +8126,8 @@ static int tpm2_define_nvpcr_nv_index(
|
||||
* file in /run/, with a separate file for orderly and non-orderly NvPCRs, since the two draw on
|
||||
* different TPM resources (RAM-backed vs. NVRAM-backed). */
|
||||
const char *exhausted_flag = orderly ?
|
||||
"/run/systemd/tpm2-nv-space-exhausted-orderly" :
|
||||
"/run/systemd/tpm2-nv-space-exhausted-non-orderly";
|
||||
NVPCR_SPACE_EXHAUSTED_FLAG_ORDERLY :
|
||||
NVPCR_SPACE_EXHAUSTED_FLAG_NON_ORDERLY;
|
||||
|
||||
/* 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. */
|
||||
@@ -9107,6 +9113,14 @@ static int tpm2_nvpcr_read_init_policy(const char *name, TPM2B_DIGEST *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The kind of policy guarding the initializing write to an NvPCR. */
|
||||
typedef enum NvPCRInitPolicyType {
|
||||
NVPCR_INIT_POLICY_SIGNED,
|
||||
NVPCR_INIT_POLICY_PCR,
|
||||
_NVPCR_INIT_POLICY_MAX,
|
||||
_NVPCR_INIT_POLICY_INVALID = -EINVAL,
|
||||
} NvPCRInitPolicyType;
|
||||
|
||||
/* Policy reference for the PolicyAuthorize assertion that guards the first write to an NvPCR. */
|
||||
#define NVPCR_INIT_POLICY_REF "initrd"
|
||||
|
||||
@@ -9116,8 +9130,11 @@ static int tpm2_nvpcr_read_init_policy(const char *name, TPM2B_DIGEST *ret) {
|
||||
/* 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 {
|
||||
NvPCRInitPolicyType type;
|
||||
TPM2B_DIGEST digest;
|
||||
TPML_PCR_SELECTION pcr_selection;
|
||||
|
||||
/* type NVPCR_INIT_POLICY_SIGNED only */
|
||||
TPM2B_PUBLIC public;
|
||||
struct iovec fingerprint;
|
||||
sd_json_variant *signature_json;
|
||||
@@ -9168,27 +9185,40 @@ static int tpm2_nvpcr_open_init_session(
|
||||
|
||||
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;
|
||||
switch (init->type) {
|
||||
case NVPCR_INIT_POLICY_SIGNED:
|
||||
assert(iovec_is_set(&init->fingerprint));
|
||||
assert(init->signature_json);
|
||||
|
||||
/* 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);
|
||||
/* 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);
|
||||
break;
|
||||
|
||||
case NVPCR_INIT_POLICY_PCR:
|
||||
/* Satisfy the PolicyPCR branch with the current PCR values. */
|
||||
r = tpm2_policy_pcr(c, session, &init->pcr_selection, /* ret_policy_digest= */ NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -9351,35 +9381,6 @@ static int nvpcr_extend_bytes(
|
||||
#endif
|
||||
}
|
||||
|
||||
int tpm2_nvpcr_extend_bytes(
|
||||
Tpm2Context *c,
|
||||
const Tpm2Handle *session,
|
||||
const char *name,
|
||||
const struct iovec *data,
|
||||
const struct iovec *secret,
|
||||
Tpm2UserspaceEventType event_type,
|
||||
const char *description) {
|
||||
|
||||
int r;
|
||||
|
||||
r = nvpcr_extend_bytes(c, session, name, data, secret, event_type, description);
|
||||
if (r != -ENETDOWN)
|
||||
return r;
|
||||
|
||||
/* The NvPCR isn't initialized yet, i.e. systemd-tpm2-setup hasn't run.
|
||||
* Initialize it now and extend again. This can only run in the initrd. */
|
||||
if (!in_initrd())
|
||||
return log_debug_errno(r, "NvPCR is not initialized and lazy initialization is only available in the initrd, refusing.");
|
||||
|
||||
r = tpm2_nvpcr_initialize(c, session, name);
|
||||
if (r == -ENOKEY) /* Can't be initialized in this boot, report it as not initialized. */
|
||||
return -ENETDOWN;
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to initialize NvPCR '%s': %m", name);
|
||||
|
||||
return nvpcr_extend_bytes(c, session, name, data, secret, event_type, description);
|
||||
}
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
static int tpm2_context_can_nvindex(Tpm2Context *c) {
|
||||
int r;
|
||||
@@ -9418,6 +9419,47 @@ static int tpm2_context_can_nvindex(Tpm2Context *c) {
|
||||
}
|
||||
#endif
|
||||
|
||||
int tpm2_nvpcr_extend_bytes(
|
||||
Tpm2Context *c,
|
||||
const Tpm2Handle *session,
|
||||
const char *name,
|
||||
const struct iovec *data,
|
||||
const struct iovec *secret,
|
||||
Tpm2UserspaceEventType event_type,
|
||||
const char *description) {
|
||||
|
||||
int r;
|
||||
|
||||
r = nvpcr_extend_bytes(c, session, name, data, secret, event_type, description);
|
||||
if (r != -ENETDOWN)
|
||||
return r;
|
||||
|
||||
/* The NvPCR isn't initialized yet, i.e. systemd-tpm2-setup hasn't run.
|
||||
* Initialize it now and extend again. This can only run in the initrd. */
|
||||
if (!in_initrd()) {
|
||||
/* systemd-tpm2-setup skips NvPCRs if the TPM doesn't support them or ran out of NV index
|
||||
* space, report these cases the same way instead of as a missing initialization. */
|
||||
#if HAVE_OPENSSL
|
||||
if (tpm2_context_can_nvindex(c) == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"TPM2 device does not support NvPCRs, NvPCR '%s' is not initialized.", name);
|
||||
|
||||
if (access(NVPCR_SPACE_EXHAUSTED_FLAG_ORDERLY, F_OK) >= 0 ||
|
||||
access(NVPCR_SPACE_EXHAUSTED_FLAG_NON_ORDERLY, F_OK) >= 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
|
||||
"TPM NV index space was exhausted in this boot, NvPCR '%s' is not initialized.", name);
|
||||
#endif
|
||||
|
||||
return log_debug_errno(r, "NvPCR is not initialized and lazy initialization is only available in the initrd, refusing.");
|
||||
}
|
||||
|
||||
r = tpm2_nvpcr_initialize(c, session, name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to initialize NvPCR '%s': %m", name);
|
||||
|
||||
return nvpcr_extend_bytes(c, session, name, data, secret, event_type, description);
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
@@ -9445,6 +9487,104 @@ static int tpm2_nvpcr_calculate_signed_init_policy(
|
||||
*ret = init_policy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculates the init policy for an NvPCR protected by the current PCR values:
|
||||
* PolicyPCR over 'sel' with the values read from the TPM now. */
|
||||
static int tpm2_nvpcr_calculate_pcr_init_policy(
|
||||
Tpm2Context *c,
|
||||
const TPML_PCR_SELECTION *sel,
|
||||
TPM2B_DIGEST *ret) {
|
||||
|
||||
_cleanup_free_ Tpm2PCRValue *values = NULL;
|
||||
size_t n;
|
||||
TPM2B_DIGEST init_policy;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
assert(sel);
|
||||
assert(ret);
|
||||
|
||||
r = tpm2_pcr_read(c, sel, &values, &n);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (n != 1)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"TPM returned no value for the selected PCR, cannot calculate NvPCR init policy.");
|
||||
|
||||
init_policy = TPM2B_DIGEST_MAKE(NULL, SHA256_DIGEST_SIZE);
|
||||
|
||||
r = tpm2_calculate_policy_pcr(values, n, &init_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
tpm2_log_debug_pcr_value(&values[0], "PCR value for NvPCR init policy");
|
||||
*ret = init_policy;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Loads the signed init policy of NvPCR 'name' from the booted UKI. Returns -ENOKEY if no PCR public key,
|
||||
* signature file, or "initrd" signature for the key is available, in which case 'ret' is left untouched. */
|
||||
static int tpm2_nvpcr_load_signed_init_policy(
|
||||
const char *name,
|
||||
const TPML_PCR_SELECTION *pcr_selection,
|
||||
NvPCRInitPolicy *ret) {
|
||||
|
||||
_cleanup_(nvpcr_init_policy_done) NvPCRInitPolicy p = {
|
||||
.type = NVPCR_INIT_POLICY_SIGNED,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
assert(pcr_selection);
|
||||
assert(ret);
|
||||
|
||||
p.pcr_selection = *pcr_selection;
|
||||
|
||||
/* Load the PCR public key. Its signatures authorize the initializing write to the NvPCR via the
|
||||
* PolicyAuthorize branch of the write policy. */
|
||||
r = tpm2_nvpcr_load_pcr_public_key(/* path= */ NULL, &p.public, &p.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),
|
||||
"No PCR public key available, cannot initialize NvPCR '%s'.", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Load the signed PCR policy, which authorizes the initializing write. */
|
||||
r = tpm2_load_pcr_signature(/* path= */ NULL, &p.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),
|
||||
"No signed PCR policy available, cannot initialize NvPCR '%s'.", name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load PCR signature for NvPCR initialization: %m");
|
||||
|
||||
/* Check that there's a signed policy for the initializing write at all before we allocate anything
|
||||
* on the TPM. This avoids failing systemd-tpm2-setup if there are no policies signed for the "initrd"
|
||||
* 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. */
|
||||
r = find_signature(
|
||||
p.signature_json,
|
||||
&p.pcr_selection,
|
||||
p.fingerprint.iov_base, p.fingerprint.iov_len,
|
||||
NVPCR_INIT_POLICY_REF,
|
||||
/* policy= */ NULL, /* policy_size= */ 0,
|
||||
/* ret_signature= */ NULL, /* ret_signature_size= */ NULL);
|
||||
if (r == -ENOSTR)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
|
||||
"No signed PCR policy with reference '%s' available, cannot initialize NvPCR '%s'.",
|
||||
NVPCR_INIT_POLICY_REF, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = tpm2_nvpcr_calculate_signed_init_policy(&p.public, &p.digest);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_STRUCT(p);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int tpm2_nvpcr_initialize(
|
||||
@@ -9492,9 +9632,9 @@ int tpm2_nvpcr_initialize(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* XXX: As NvPCRs use a write policy with PolicyAuthorize, the name algorithm and signed policy
|
||||
* algorithm must match. Currently, signed policies are restricted to SHA256, so reject anything else
|
||||
* for now. This can only be relaxed when:
|
||||
/* XXX: The init policy of a NvPCR (the signed policy for PolicyAuthorize, the PCR digest for
|
||||
* PolicyPCR) must use the name algorithm of the NV index. Currently, both are restricted to SHA256,
|
||||
* so reject anything else for now. This can only be relaxed when:
|
||||
* - The various tpm2_calculate_policy_* and tpm2_digest_* functions support more than SHA256.
|
||||
* - Signed policies have a new policy algorithm field in the JSON metadata.
|
||||
* - systemd-measure has a new argument to select the policy algorithm.
|
||||
@@ -9517,47 +9657,21 @@ int tpm2_nvpcr_initialize(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash function result too large for TPM, refusing.");
|
||||
|
||||
_cleanup_(nvpcr_init_policy_done) NvPCRInitPolicy init = {};
|
||||
TPML_PCR_SELECTION pcr_selection;
|
||||
|
||||
/* Load the PCR public key. Its signatures authorize the initializing write to the NvPCR via the
|
||||
* PolicyAuthorize branch of the write policy. */
|
||||
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),
|
||||
"No PCR public key available, cannot initialize NvPCR '%s'.", name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
tpm2_tpml_pcr_selection_from_mask(NVPCR_INIT_PCRMASK, TPM2_ALG_SHA256, &pcr_selection);
|
||||
|
||||
/* Load the signed PCR policy, which authorizes the initializing write. */
|
||||
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),
|
||||
"No signed PCR policy available, cannot initialize NvPCR '%s'.", name);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to load PCR signature for NvPCR initialization: %m");
|
||||
|
||||
/* Check that there's a signed policy for the initializing write at all before we allocate anything
|
||||
* on the TPM. This avoids failing systemd-tpm2-setup if there are no policies signed for the "initrd"
|
||||
* 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. */
|
||||
tpm2_tpml_pcr_selection_from_mask(NVPCR_INIT_PCRMASK, TPM2_ALG_SHA256, &init.pcr_selection);
|
||||
r = find_signature(
|
||||
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);
|
||||
if (r == -ENOSTR)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
|
||||
"No signed PCR policy with reference '%s' available, cannot initialize NvPCR '%s'.",
|
||||
NVPCR_INIT_POLICY_REF, name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = tpm2_nvpcr_calculate_signed_init_policy(&init.public, &init.digest);
|
||||
/* Prefer the signed init policy from the booted UKI. Without one, fall back to a PolicyPCR bound to
|
||||
* the current PCR value, which is only reachable in the initrd, so only do that there. */
|
||||
r = tpm2_nvpcr_load_signed_init_policy(name, &pcr_selection, &init);
|
||||
if (r == -ENOKEY && in_initrd()) {
|
||||
log_debug_errno(r, "No signed init policy for NvPCR '%s', falling back to PCR policy.", name);
|
||||
init = (NvPCRInitPolicy) {
|
||||
.type = NVPCR_INIT_POLICY_PCR,
|
||||
.pcr_selection = pcr_selection,
|
||||
};
|
||||
r = tpm2_nvpcr_calculate_pcr_init_policy(c, &pcr_selection, &init.digest);
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -9580,6 +9694,12 @@ int tpm2_nvpcr_initialize(
|
||||
/* Open a policy session to perform the initializing write. */
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
|
||||
r = tpm2_nvpcr_open_init_session(c, &init, &policy_session);
|
||||
if (r == -ENOANO && init.type == NVPCR_INIT_POLICY_PCR)
|
||||
/* The index is bound to the old PCR value, so retrying can't help. The next attempt
|
||||
* redefines it. */
|
||||
return log_debug_errno(r,
|
||||
"PCR %i changed since the init policy of NvPCR '%s' was calculated, refusing initialization.",
|
||||
TPM2_PCR_KERNEL_BOOT, name);
|
||||
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
|
||||
|
||||
@@ -29,6 +29,7 @@ at_exit() {
|
||||
rm -f /var/tmp/nvpcr.raw /run/verity.d/test-70-nvpcr.crt
|
||||
rm -f /run/systemd/nvpcr/test.auth /run/systemd/nvpcr/test2.auth /run/systemd/nvpcr/aaa.auth /run/systemd/nvpcr/zzz.auth /run/systemd/nvpcr/unauth.auth /run/systemd/nvpcr/unauth2.auth
|
||||
rm -f /tmp/test.policy
|
||||
rm -f /run/systemd/tpm2-nv-space-exhausted-orderly
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
@@ -157,11 +158,12 @@ AAA_LINE="$(echo "$SETUP_LOG" | grep -n "Setting up NvPCR 'aaa'" | cut -d: -f1)"
|
||||
ZZZ_LINE="$(echo "$SETUP_LOG" | grep -n "Setting up NvPCR 'zzz'" | cut -d: -f1)"
|
||||
test "$ZZZ_LINE" -lt "$AAA_LINE"
|
||||
|
||||
# Verify that systemd-tpm2-setup skips (rather than fails) NvPCR initialization if the booted kernel
|
||||
# image carries no PCR public key or no signed PCR policy with the "initrd" reference, and that it
|
||||
# doesn't allocate the NV index in that case. systemd-pcrextend must then treat the NvPCR as not
|
||||
# Verify the handling of a booted kernel image that carries no PCR public key or no signed PCR policy
|
||||
# with the "initrd" reference. Outside the initrd, systemd-tpm2-setup skips (rather than fails) NvPCR
|
||||
# initialization and doesn't allocate the NV index, and systemd-pcrextend treats the NvPCR as not
|
||||
# initialized. 'unauth2' sorts after 'unauth', so once 'unauth' couldn't be initialized it must be
|
||||
# skipped without another initialization attempt.
|
||||
# skipped without another initialization attempt. In the initrd, the NvPCRs are initialized with a
|
||||
# PolicyPCR init policy bound to the current value of PCR 11 instead.
|
||||
cat >/run/nvpcr/unauth.nvpcr <<EOF
|
||||
{"name":"unauth","algorithm":"sha256","nvIndex":30474774}
|
||||
EOF
|
||||
@@ -170,59 +172,87 @@ cat >/run/nvpcr/unauth2.nvpcr <<EOF
|
||||
EOF
|
||||
cp /run/systemd/tpm2-pcr-signature.json /tmp/tpm2-pcr-signature.json.valid
|
||||
|
||||
# Takes the expected reason for skipping the initialization, as logged by tpm2_nvpcr_initialize().
|
||||
check_nvpcr_unauth_skipped() {
|
||||
# Reads a hex string from stdin and prints the SHA256 digest of the bytes it encodes.
|
||||
sha256_hex() {
|
||||
tr -d '\n' | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1
|
||||
}
|
||||
|
||||
nv_index_policy() {
|
||||
tpm2_nvreadpublic "${1:?}" | awk '/authorization policy:/{print tolower($3); exit}'
|
||||
}
|
||||
|
||||
# Checks that the NV index carries the write policy of an NvPCR initialized with the PolicyPCR init
|
||||
# policy for the current value of PCR 11, i.e. PolicyOR(PolicyPCR(sha256:11), PolicyNvWritten(YES)).
|
||||
check_nvpcr_pcr_policy() {
|
||||
local nv_index="${1:?}"
|
||||
local zero pcr_policy written_policy
|
||||
|
||||
zero="$(printf '00%.0s' {1..32})"
|
||||
# TPM2_CC_PolicyPCR, TPML_PCR_SELECTION for sha256:11, digest of the PCR value
|
||||
pcr_policy="$(sha256_hex <<<"${zero}0000017F00000001000B03000800$(sha256_hex </sys/class/tpm/tpm0/pcr-sha256/11)")"
|
||||
# TPM2_CC_PolicyNvWritten, YES
|
||||
written_policy="$(sha256_hex <<<"${zero}0000018F01")"
|
||||
# TPM2_CC_PolicyOR, init branch first
|
||||
[[ "$(nv_index_policy "$nv_index")" == "$(sha256_hex <<<"${zero}00000171${pcr_policy}${written_policy}")" ]]
|
||||
}
|
||||
|
||||
# Takes the expected reason for not using a signed init policy, as logged by tpm2_nvpcr_initialize().
|
||||
check_nvpcr_unauth() {
|
||||
local reason="${1:?}"
|
||||
local expected="$reason, cannot initialize NvPCR 'unauth'."
|
||||
local log rc=0
|
||||
local log
|
||||
|
||||
# systemd-tpm2-setup returns EX_CONFIG if the NvPCR initialization was skipped for this reason
|
||||
log="$(SYSTEMD_IN_INITRD=1 "$SD_TPM2SETUP" --early=yes 2>&1)" || rc=$?
|
||||
[[ "$rc" -eq 78 ]]
|
||||
grep -F "$expected" <<<"$log" >/dev/null
|
||||
grep -F "Skipped 2 NvPCR(s) because NvPCR initialization is not authorized in this boot" <<<"$log" >/dev/null
|
||||
grep -F "skipping allocation of NvPCR 'unauth2'." <<<"$log" >/dev/null
|
||||
(! grep -F "cannot initialize NvPCR 'unauth2'" <<<"$log" >/dev/null)
|
||||
test ! -f /run/systemd/nvpcr/unauth.auth
|
||||
test ! -f /run/systemd/nvpcr/unauth2.auth
|
||||
(! tpm2_nvreadpublic 0x01d10216)
|
||||
(! tpm2_nvreadpublic 0x01d10217)
|
||||
|
||||
(! "$SD_PCREXTEND" --nvpcr=unauth foo)
|
||||
"$SD_PCREXTEND" --graceful --nvpcr=unauth foo
|
||||
(! "$SD_PCREXTEND" --graceful --nvpcr=unauth foo)
|
||||
log="$(varlinkctl call /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend.Extend '{"nvpcr":"unauth","text":"foo"}' 2>&1 || :)"
|
||||
grep -F "io.systemd.PCRExtend.NoSuchNvPCR" <<<"$log" >/dev/null
|
||||
|
||||
# In the initrd, systemd-pcrextend attempts to lazily initialize the NvPCR, which must fail in the
|
||||
# same way and be reported as the NvPCR not being initialized.
|
||||
rc=0
|
||||
log="$(SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --nvpcr=unauth foo 2>&1)" || rc=$?
|
||||
[[ "$rc" -ne 0 ]]
|
||||
# In the initrd, systemd-pcrextend lazily initializes the NvPCR with the PolicyPCR init policy, and
|
||||
# anchors it in PCR 9 like any other NvPCR.
|
||||
log="$(SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --nvpcr=unauth foo 2>&1)"
|
||||
grep -F "$expected" <<<"$log" >/dev/null
|
||||
log="$(SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --graceful --nvpcr=unauth foo 2>&1)"
|
||||
grep -F "$expected" <<<"$log" >/dev/null
|
||||
grep -F "NvPCR 'unauth' is not initialized, skipping gracefully." <<<"$log" >/dev/null
|
||||
log="$(SYSTEMD_IN_INITRD=1 varlinkctl call /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend.Extend '{"nvpcr":"unauth","text":"foo"}' 2>&1 || :)"
|
||||
grep -F "$expected" <<<"$log" >/dev/null
|
||||
grep -F "io.systemd.PCRExtend.NoSuchNvPCR" <<<"$log" >/dev/null
|
||||
test ! -f /run/systemd/nvpcr/unauth.auth
|
||||
(! tpm2_nvreadpublic 0x01d10216)
|
||||
grep -F "No signed init policy for NvPCR 'unauth', falling back to PCR policy." <<<"$log" >/dev/null
|
||||
test -f /run/systemd/nvpcr/unauth.auth
|
||||
check_nvpcr_pcr_policy 0x01d10216
|
||||
log="$(jq --seq --slurp -r '[.[] | select(.content.eventType=="nvpcr-init") | .content.string] | last' </run/log/systemd/tpm2-measure.log)"
|
||||
[[ "$log" == "nvpcr-init:unauth:0x1d10216:$(tpm2_nvreadpublic 0x01d10216 | awk '/name:/{print $2; exit}')" ]]
|
||||
# Subsequent extends use the PolicyNvWritten branch, hence work outside the initrd too.
|
||||
"$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
rm -f /run/systemd/nvpcr/unauth.auth
|
||||
tpm2_nvundefine -C o 0x01d10216
|
||||
|
||||
# Same for systemd-tpm2-setup in the initrd, which initializes both NvPCRs.
|
||||
SYSTEMD_IN_INITRD=1 run_tpm2_setup
|
||||
test -f /run/systemd/nvpcr/unauth.auth
|
||||
test -f /run/systemd/nvpcr/unauth2.auth
|
||||
check_nvpcr_pcr_policy 0x01d10216
|
||||
check_nvpcr_pcr_policy 0x01d10217
|
||||
"$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
"$SD_PCREXTEND" --nvpcr=unauth2 foo
|
||||
rm -f /run/systemd/nvpcr/unauth.auth /run/systemd/nvpcr/unauth2.auth
|
||||
tpm2_nvundefine -C o 0x01d10216
|
||||
tpm2_nvundefine -C o 0x01d10217
|
||||
}
|
||||
|
||||
# No PCR public key
|
||||
mv /run/systemd/tpm2-pcr-public-key.pem /tmp/tpm2-pcr-public-key.pem
|
||||
check_nvpcr_unauth_skipped "No PCR public key available"
|
||||
check_nvpcr_unauth "No PCR public key available"
|
||||
mv /tmp/tpm2-pcr-public-key.pem /run/systemd/tpm2-pcr-public-key.pem
|
||||
|
||||
# No PCR signature file
|
||||
rm -f /run/systemd/tpm2-pcr-signature.json
|
||||
check_nvpcr_unauth_skipped "No signed PCR policy available"
|
||||
check_nvpcr_unauth "No signed PCR policy available"
|
||||
|
||||
# PCR signature without a policy with the "initrd" reference
|
||||
"$SD_MEASURE" sign --current --bank sha256 --private-key="/tmp/tpm2-pcr-private-key.pem" --public-key="/run/systemd/tpm2-pcr-public-key.pem" --phase=":" --policyref="foo" >"/run/systemd/tpm2-pcr-signature.json"
|
||||
check_nvpcr_unauth_skipped "No signed PCR policy with reference 'initrd' available"
|
||||
check_nvpcr_unauth "No signed PCR policy with reference 'initrd' available"
|
||||
"$SD_MEASURE" sign --current --bank sha256 --private-key="/tmp/tpm2-pcr-private-key.pem" --public-key="/run/systemd/tpm2-pcr-public-key.pem" --phase=":" >"/run/systemd/tpm2-pcr-signature.json"
|
||||
check_nvpcr_unauth_skipped "No signed PCR policy with reference 'initrd' available"
|
||||
check_nvpcr_unauth "No signed PCR policy with reference 'initrd' available"
|
||||
|
||||
# Outside the initrd, NvPCR initialization must be skipped even with a valid PCR public key and signed
|
||||
# PCR policy, as the policy can no longer be satisfied once the initrd has been left. This happens if
|
||||
@@ -241,11 +271,17 @@ test ! -f /run/systemd/nvpcr/unauth2.auth
|
||||
(! tpm2_nvreadpublic 0x01d10216)
|
||||
(! tpm2_nvreadpublic 0x01d10217)
|
||||
(! SYSTEMD_IN_INITRD=0 "$SD_PCREXTEND" --nvpcr=unauth foo)
|
||||
SYSTEMD_IN_INITRD=0 "$SD_PCREXTEND" --graceful --nvpcr=unauth foo
|
||||
(! SYSTEMD_IN_INITRD=0 "$SD_PCREXTEND" --graceful --nvpcr=unauth foo)
|
||||
VARLINK_LOG="$(SYSTEMD_IN_INITRD=0 varlinkctl call /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend.Extend '{"nvpcr":"unauth","text":"foo"}' 2>&1 || :)"
|
||||
grep -F "io.systemd.PCRExtend.NoSuchNvPCR" <<<"$VARLINK_LOG" >/dev/null
|
||||
test ! -f /run/systemd/nvpcr/unauth.auth
|
||||
(! tpm2_nvreadpublic 0x01d10216)
|
||||
# If the TPM's NV index space ran out in this boot, systemd-tpm2-setup skips the remaining NvPCRs
|
||||
# gracefully, so systemd-pcrextend must do so as well.
|
||||
touch /run/systemd/tpm2-nv-space-exhausted-orderly
|
||||
(! SYSTEMD_IN_INITRD=0 "$SD_PCREXTEND" --nvpcr=unauth foo)
|
||||
SYSTEMD_IN_INITRD=0 "$SD_PCREXTEND" --graceful --nvpcr=unauth foo
|
||||
rm -f /run/systemd/tpm2-nv-space-exhausted-orderly
|
||||
|
||||
# A policy with the "initrd" reference but an invalid signature must still fail
|
||||
jq --arg sig "$(openssl rand -base64 256 | tr -d '\n')" '.sha256[].sig = $sig' </tmp/tpm2-pcr-signature.json.valid >/run/systemd/tpm2-pcr-signature.json
|
||||
@@ -256,10 +292,18 @@ test ! -f /run/systemd/nvpcr/unauth2.auth
|
||||
tpm2_nvundefine -C o 0x01d10216
|
||||
tpm2_nvundefine -C o 0x01d10217
|
||||
|
||||
# Leave behind an NV index initialized with the PolicyPCR init policy in a previous boot (i.e. without
|
||||
# its .auth file), which must be redefined once a signed init policy is available.
|
||||
rm -f /run/systemd/tpm2-pcr-signature.json
|
||||
SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
check_nvpcr_pcr_policy 0x01d10216
|
||||
rm -f /run/systemd/nvpcr/unauth.auth
|
||||
|
||||
# And with the valid signature restored, the NvPCRs get initialized
|
||||
cp /tmp/tpm2-pcr-signature.json.valid /run/systemd/tpm2-pcr-signature.json
|
||||
run_tpm2_setup
|
||||
test -f /run/systemd/nvpcr/unauth.auth
|
||||
(! check_nvpcr_pcr_policy 0x01d10216)
|
||||
test -f /run/systemd/nvpcr/unauth2.auth
|
||||
"$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
"$SD_PCREXTEND" --nvpcr=unauth2 foo
|
||||
@@ -274,6 +318,22 @@ tpm2_nvundefine -C o 0x01d1020a
|
||||
SETUP_LOG="$(rc=0; SYSTEMD_IN_INITRD=1 $SD_TPM2SETUP --early=yes 2>&1 || rc=$?; [[ "$rc" -ne 0 ]] && [[ "$rc" -ne 69 ]] && [[ "$rc" -ne 78 ]])"
|
||||
grep -F "Failed to initialize NvPCR index: Device not a stream" <<<"$SETUP_LOG" >/dev/null
|
||||
|
||||
# The PolicyPCR init policy is bound to the value of PCR 11 at initialization. Once PCR 11 changed, a new
|
||||
# initialization redefines the NV index with a policy for the new value.
|
||||
echo '{"name":"unauth","algorithm":"sha256","nvIndex":30474774}' >/run/nvpcr/unauth.nvpcr
|
||||
rm -f /run/systemd/tpm2-pcr-signature.json
|
||||
SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
check_nvpcr_pcr_policy 0x01d10216
|
||||
OLD_POLICY="$(nv_index_policy 0x01d10216)"
|
||||
"$SD_PCREXTEND" --pcr 11 "foo"
|
||||
rm -f /run/systemd/nvpcr/unauth.auth
|
||||
SYSTEMD_IN_INITRD=1 "$SD_PCREXTEND" --nvpcr=unauth foo
|
||||
check_nvpcr_pcr_policy 0x01d10216
|
||||
[[ "$(nv_index_policy 0x01d10216)" != "$OLD_POLICY" ]]
|
||||
cp /tmp/tpm2-pcr-signature.json.valid /run/systemd/tpm2-pcr-signature.json
|
||||
rm -f /run/nvpcr/unauth.nvpcr /run/systemd/nvpcr/unauth.auth
|
||||
tpm2_nvundefine -C o 0x01d10216
|
||||
|
||||
# Test the --login= mode and the 'login' NvPCR, used in production by systemd-pcrlogin@.service.
|
||||
if [[ -f /usr/lib/nvpcr/login.nvpcr ]]; then
|
||||
login_nvpcr_value() {
|
||||
|
||||
Reference in new issue
Block a user