Skip to content

Commit e846bba

Browse files
chrisccoulsonkatexochen
authored andcommitted
tpm2: Don't fail NvPCR initialization if there is no initial write policy available
Currently systemd-tpm2-setup will fail if NvPCR initialization doesn't succeed, either because the currently booted UKI carries no PCR public key, or it carries no appropriate signed policy (one with the "initrd" policy reference). In addition to this, systemd-pcrextend will subsequently fail because it also fails to initialize the NvPCR it is requested to extend. This makes systemd-tpm2-setup return EX_CONFIG in these scenarios, with this marked as a successful exit status in the service unit file. In addition to this, systemd-pcrextend is updated to only attempt NvPCR initialization from the initrd (the only place it will succceed), and when called with --graceful, to not exit with an error code if the target NvPCR is uninitialized, and can't be initialized because of a missing PCR public key or appropriate signed policy. When called via Varlink, it will return the existing "NoSuchNvPCR" error. Note that I also removed some NvPCR initialization specific exit status codes from systemd-tpm2-setup.service.in, as these can only be returned via systemd-tpm2-setup-early.service. This should address the issues in #43848.
1 parent 16d9cb1 commit e846bba

8 files changed

Lines changed: 253 additions & 34 deletions

File tree

‎catalog/systemd.catalog.in‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,32 @@ The Trusted Platform Module's (TPM) NV index support is too limited to properly
10401040
implement NV index backed additional PCRs. NvPCRs will not be allocated or
10411041
initialized, and will not be available on the system.
10421042

1043+
-- 7a427613a56946b6bfa9aee1dfc553cd
1044+
Subject: Initialization of NvPCR not authorized in this boot
1045+
Defined-By: systemd
1046+
Support: %SUPPORT_URL%
1047+
Documentation: man:systemd-measure(1) man:ukify(1) man:systemd-tpm2-setup.service(8)
1048+
1049+
An additional NV index backed Platform Configuration Register (PCR) could not be
1050+
initialized, because the authorization required for its initializing write is
1051+
not available. The initializing write to an NvPCR must be authorized by a signed
1052+
PCR policy with the "initrd" policy reference, which is only possible from the
1053+
initrd. This happens if the booted unified kernel image (UKI) does not carry a
1054+
PCR public key (in its .pcrpkey section), if it does not carry a signed PCR
1055+
policy (in its .pcrsig section) for that key with the "initrd" policy
1056+
reference, or if initialization was attempted after the system transitioned
1057+
from the initrd to the host, for example because
1058+
systemd-tpm2-setup-early.service is not included in the initrd.
1059+
1060+
This is not an error: the affected NvPCRs will not be available during this
1061+
boot, and measurements to them will be skipped.
1062+
1063+
To make NvPCRs available, sign the UKI's PCR policy for the initrd boot phase
1064+
with the "initrd" policy reference, for example via the --policyref=initrd and
1065+
--phase=enter-initrd options of systemd-measure, or the corresponding options
1066+
of ukify, and make sure systemd-tpm2-setup-early.service is included in the
1067+
initrd.
1068+
10431069
-- eac6d394c925493deac2ba99f3c2bb11
10441070
Subject: An optional dependency for @FEATURE@ is not installed
10451071
Defined-By: systemd

‎src/pcrextend/pcrextend.c‎

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@ static int extend_nvpcr_now(
365365
secret,
366366
event,
367367
safe);
368-
if (r == -ENOBUFS)
369-
return r; /* NV space exhausted; let caller handle gracefully */
368+
if (IN_SET(r, -ENOBUFS, -ENETDOWN))
369+
return r; /* NV space exhausted or NvPCR not initialized; let caller handle gracefully */
370370
if (r < 0)
371371
return log_error_errno(r, "Could not extend NvPCR: %m");
372372

@@ -451,7 +451,7 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
451451

452452
if (p.nvpcr) {
453453
r = extend_nvpcr_now(p.nvpcr, extend_iovec, &p.secret, p.event_type);
454-
if (IN_SET(r, -ENOENT, -ENODEV))
454+
if (IN_SET(r, -ENOENT, -ENODEV, -ENETDOWN))
455455
return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
456456
if (r == -ENOBUFS)
457457
return sd_varlink_error(link, "io.systemd.PCRExtend.NvPCRSpaceExhausted", NULL);
@@ -590,17 +590,25 @@ static int run(int argc, char *argv[]) {
590590
r = extend_nvpcr_now(arg_nvpcr_name, &IOVEC_MAKE(word, strlen(word)), NULL, event);
591591
else
592592
r = extend_pcr_now(arg_pcr_mask, &IOVEC_MAKE(word, strlen(word)), NULL, event);
593-
/* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto,
594-
* no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip
595-
* those rather than fail and block boot. Genuine faults keep their own errno and are never
596-
* suppressed. */
597-
if (arg_graceful && r == -EOPNOTSUPP) {
598-
log_notice_errno(r, "TPM2 cannot be used for measurement (no usable PCR bank, missing device, or missing crypto support), skipping gracefully.");
599-
return EXIT_SUCCESS;
600-
}
601-
if (arg_graceful && r == -ENOBUFS) {
602-
log_notice_errno(r, "TPM NV index space is exhausted, NvPCR '%s' could not be initialized, skipping gracefully.", arg_nvpcr_name);
603-
return EXIT_SUCCESS;
593+
if (arg_graceful) {
594+
/* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto,
595+
* no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip
596+
* those rather than fail and block boot. Genuine faults keep their own errno and are never
597+
* suppressed. */
598+
if (r == -EOPNOTSUPP) {
599+
log_notice_errno(r, "TPM2 cannot be used for measurement (no usable PCR bank, missing device, or missing crypto support), skipping gracefully.");
600+
return EXIT_SUCCESS;
601+
}
602+
if (r == -ENETDOWN) {
603+
/* The NvPCR couldn't be initialized in this boot (e.g. because the booted kernel image
604+
* carries no suitable PCR signature), hence there's nothing to extend. */
605+
log_notice_errno(r, "NvPCR '%s' is not initialized, skipping gracefully.", arg_nvpcr_name);
606+
return EXIT_SUCCESS;
607+
}
608+
if (r == -ENOBUFS) {
609+
log_notice_errno(r, "TPM NV index space is exhausted, NvPCR '%s' could not be initialized, skipping gracefully.", arg_nvpcr_name);
610+
return EXIT_SUCCESS;
611+
}
604612
}
605613
if (r < 0)
606614
return r;

‎src/shared/tpm2-util.c‎

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4846,8 +4846,13 @@ static int find_signature(
48464846
const char *k;
48474847
int r;
48484848

4849+
assert(pcr_selection);
4850+
assert(policy || !ret_signature);
4851+
assert(!ret_signature || ret_signature_size);
4852+
48494853
/* Searches for a signature blob in the specified JSON object. Search keys are PCR bank, PCR mask,
4850-
* public key, and policy digest. */
4854+
* public key, policy reference and policy digest. If 'policy' is NULL, matches any policy digest.
4855+
* If 'ret_signature' is NULL, only checks whether a matching entry exists. */
48514856

48524857
if (!sd_json_variant_is_object(v))
48534858
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object.");
@@ -4917,18 +4922,23 @@ static int find_signature(
49174922
if (!polj)
49184923
continue;
49194924

4920-
r = sd_json_variant_unhex(polj, &polj_data, &polj_size);
4921-
if (r < 0)
4922-
return log_debug_errno(r, "Failed to decode policy hash JSON data: %m");
4925+
if (policy) {
4926+
r = sd_json_variant_unhex(polj, &polj_data, &polj_size);
4927+
if (r < 0)
4928+
return log_debug_errno(r, "Failed to decode policy hash JSON data: %m");
49234929

4924-
if (memcmp_nn(policy, policy_size, polj_data, polj_size) != 0)
4925-
continue;
4930+
if (memcmp_nn(policy, policy_size, polj_data, polj_size) != 0)
4931+
continue;
4932+
}
49264933

49274934
/* This entry matches all our expectations, now return the signature included in it */
49284935
sigj = sd_json_variant_by_key(i, "sig");
49294936
if (!sigj)
49304937
continue;
49314938

4939+
if (!ret_signature)
4940+
return 0;
4941+
49324942
return sd_json_variant_unbase64(sigj, ret_signature, ret_signature_size);
49334943
}
49344944

@@ -9331,8 +9341,13 @@ int tpm2_nvpcr_extend_bytes(
93319341
return r;
93329342

93339343
/* The NvPCR isn't initialized yet, i.e. systemd-tpm2-setup hasn't run.
9334-
* Initialize it now and extend again. */
9344+
* Initialize it now and extend again. This can only run in the initrd. */
9345+
if (!in_initrd())
9346+
return log_debug_errno(r, "NvPCR is not initialized and lazy initialization is only available in the initrd, refusing.");
9347+
93359348
r = tpm2_nvpcr_initialize(c, session, name);
9349+
if (r == -ENOKEY) /* Can't be initialized in this boot, report it as not initialized. */
9350+
return -ENETDOWN;
93369351
if (r < 0)
93379352
return log_debug_errno(r, "Failed to initialize NvPCR '%s': %m", name);
93389353

@@ -9424,6 +9439,9 @@ int tpm2_nvpcr_initialize(
94249439
return 0;
94259440
}
94269441

9442+
if (!in_initrd())
9443+
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY), "NvPCR '%s' is not initialized yet and initialization is only available in the initrd, refusing.", name);
9444+
94279445
r = dlopen_libcrypto(LOG_DEBUG);
94289446
if (r < 0)
94299447
return r;
@@ -9457,6 +9475,41 @@ int tpm2_nvpcr_initialize(
94579475
TPM2B_PUBLIC public;
94589476
_cleanup_(iovec_done) struct iovec fingerprint = {};
94599477
r = tpm2_nvpcr_load_pcr_public_key(/* path= */ NULL, &public, &fingerprint);
9478+
if (r == -ENOENT)
9479+
/* Avoid failing systemd-tpm2-setup if there is no PCR public key attached to the UKI. */
9480+
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
9481+
"No PCR public key available, cannot initialize NvPCR '%s'.", name);
9482+
if (r < 0)
9483+
return r;
9484+
9485+
/* Load the signed PCR policy, which authorizes the initializing write. */
9486+
_cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL;
9487+
r = tpm2_load_pcr_signature(/* path= */ NULL, &signature_json);
9488+
if (r == -ENOENT)
9489+
/* Avoid failing systemd-tpm2-setup if there are no PCR signatures attached to the UKI. */
9490+
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
9491+
"No signed PCR policy available, cannot initialize NvPCR '%s'.", name);
9492+
if (r < 0)
9493+
return log_debug_errno(r, "Failed to load PCR signature for NvPCR initialization: %m");
9494+
9495+
/* Check that there's a signed policy for the initializing write at all before we allocate anything
9496+
* on the TPM. This avoids failing systemd-tpm2-setup if there are no policies signed for the "initrd"
9497+
* policy reference. We don't check the policy digest here: if there is a matching policy but it doesn't
9498+
* match the current PCR state, or its signature is invalid, then that's a real error which we
9499+
* want to report below. */
9500+
TPML_PCR_SELECTION pcr_selection;
9501+
tpm2_tpml_pcr_selection_from_mask(NVPCR_PUBKEY_PCRMASK, TPM2_ALG_SHA256, &pcr_selection);
9502+
r = find_signature(
9503+
signature_json,
9504+
&pcr_selection,
9505+
fingerprint.iov_base, fingerprint.iov_len,
9506+
NVPCR_INIT_POLICY_REF,
9507+
/* policy= */ NULL, /* policy_size= */ 0,
9508+
/* ret_signature= */ NULL, /* ret_signature_size= */ NULL);
9509+
if (r == -ENOSTR)
9510+
return log_debug_errno(SYNTHETIC_ERRNO(ENOKEY),
9511+
"No signed PCR policy with reference '%s' available, cannot initialize NvPCR '%s'.",
9512+
NVPCR_INIT_POLICY_REF, name);
94609513
if (r < 0)
94619514
return r;
94629515

@@ -9477,12 +9530,6 @@ int tpm2_nvpcr_initialize(
94779530

94789531
log_debug("Successfully acquired handle to NV index 0x%" PRIx32 ".", p.nv_index);
94799532

9480-
/* Load the signed PCR policy, which authorizes the initializing write. */
9481-
_cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL;
9482-
r = tpm2_load_pcr_signature(/* path= */ NULL, &signature_json);
9483-
if (r < 0)
9484-
return log_debug_errno(r, "Failed to load PCR signature for NvPCR initialization: %m");
9485-
94869533
bool reset_marker;
94879534
for (unsigned i = RETRY_NVPCR_INIT_MAX;; i--) {
94889535
/* Open a policy session to perform the initializing write. */

‎src/systemd/sd-messages.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,9 @@ _SD_BEGIN_DECLARATIONS;
314314
#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED SD_ID128_MAKE(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed)
315315
#define SD_MESSAGE_TPM_NVPCR_UNSUPPORTED_STR SD_ID128_MAKE_STR(8f,07,a5,b8,14,ca,47,62,b8,9f,cc,30,82,e4,8a,ed)
316316

317+
#define SD_MESSAGE_TPM_NVPCR_UNAUTHORIZED SD_ID128_MAKE(7a,42,76,13,a5,69,46,b6,bf,a9,ae,e1,df,c5,53,cd)
318+
#define SD_MESSAGE_TPM_NVPCR_UNAUTHORIZED_STR SD_ID128_MAKE_STR(7a,42,76,13,a5,69,46,b6,bf,a9,ae,e1,df,c5,53,cd)
319+
317320
#define SD_MESSAGE_MISSING_DEPENDENCY SD_ID128_MAKE(ea,c6,d3,94,c9,25,49,3d,ea,c2,ba,99,f3,c2,bb,11)
318321
#define SD_MESSAGE_MISSING_DEPENDENCY_STR SD_ID128_MAKE_STR(ea,c6,d3,94,c9,25,49,3d,ea,c2,ba,99,f3,c2,bb,11)
319322

‎src/tpm2-setup/tpm2-setup.c‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ typedef struct SetupNvPCRContext {
417417
Tpm2Context *tpm2_context;
418418
size_t n_already, n_initialized, n_failed, n_skipped;
419419
bool nv_space_exhausted; /* Set once the TPM ran out of NV index space, so we skip the rest. */
420+
bool nvpcr_unauthorized; /* Set if NvPCR initialization is not authorized in this boot, so we skip the rest. */
420421
Set *done;
421422
} SetupNvPCRContext;
422423

@@ -448,6 +449,12 @@ static int setup_nvpcr_one(
448449
return 0;
449450
}
450451

452+
if (c->nvpcr_unauthorized) {
453+
log_debug("NvPCR initialization is not authorized in this boot, skipping allocation of NvPCR '%s'.", name);
454+
c->n_skipped++;
455+
return 0;
456+
}
457+
451458
r = tpm2_nvpcr_initialize(c->tpm2_context, /* session= */ NULL, name);
452459
if (r == -EOPNOTSUPP) {
453460
c->n_failed++;
@@ -465,6 +472,17 @@ static int setup_nvpcr_one(
465472
LOG_MESSAGE("The TPM's NV index space is exhausted, skipping allocation of NvPCR '%s' and any less important ones: %m", name),
466473
LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVINDEX_EXHAUSTED_STR));
467474
}
475+
if (r == -ENOKEY) {
476+
/* NvPCR initialization is not authorized in this boot, because the booted UKI carries no
477+
* PCR public key or no signed PCR policy for NvPCR initialization, or because we're not
478+
* running in the initrd. This is not an error, the NvPCR simply won't be available in
479+
* this boot. */
480+
c->nvpcr_unauthorized = true;
481+
c->n_skipped++;
482+
return log_struct_errno(LOG_NOTICE, r,
483+
LOG_MESSAGE("NvPCR initialization is not authorized in this boot (no PCR public key, no signed PCR policy for NvPCR initialization, or not running in the initrd), unable to allocate NvPCR '%s' and any others: %m", name),
484+
LOG_MESSAGE_ID(SD_MESSAGE_TPM_NVPCR_UNAUTHORIZED_STR));
485+
}
468486
if (r < 0) {
469487
c->n_failed++;
470488
return log_error_errno(r, "Failed to initialize NvPCR index: %m");
@@ -564,6 +582,9 @@ static int setup_nvpcr(void) {
564582
if (c.nv_space_exhausted)
565583
log_notice("Skipped %zu lowest-priority NvPCR(s) because the TPM's NV index space is exhausted, proceeding anyway.", c.n_skipped);
566584

585+
if (c.nvpcr_unauthorized)
586+
log_notice("Skipped %zu NvPCR(s) because NvPCR initialization is not authorized in this boot, proceeding anyway.", c.n_skipped);
587+
567588
if (c.n_failed > 0)
568589
log_warning("%zu NvPCRs failed to initialize, proceeding anyway.", c.n_failed);
569590

@@ -583,6 +604,8 @@ static int setup_nvpcr(void) {
583604
return EX_UNAVAILABLE; /* e.g. no NvPCR support in TPM */
584605
if (ret == -ENOBUFS)
585606
return EX_CANTCREAT; /* NV index space on TPM exhausted */
607+
if (ret == -ENOKEY)
608+
return EX_CONFIG; /* NvPCR initialization not authorized in this boot */
586609

587610
return ret;
588611
}

0 commit comments

Comments
 (0)