tpm2: optionally disable TPMA_NV_ORDERLY for NvPCRs (#42848)
This commit is contained in:
@@ -938,16 +938,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
- drop nss-myhostname in favour of nss-resolve?
|
||||
|
||||
- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that
|
||||
it pushes the thing into TPM RAM, but a TPM usually has very little of that,
|
||||
less than NVRAM. hence setting the flag amplifies space issues. Unsetting the
|
||||
flag increases wear issues on the NVRAM, however, but this should be limited
|
||||
for the product uuid nvpcr, since its only changed once per boot. this needs
|
||||
to be configurable by nvpcr however, as other nvpcrs are different,
|
||||
i.e. verity one receives many writes during system uptime quite
|
||||
possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs
|
||||
possibly up to 100ms supposedly)
|
||||
|
||||
- **EFI:**
|
||||
- honor timezone efi variables for default timezone selection (if there are any?)
|
||||
|
||||
|
||||
@@ -74,6 +74,18 @@ fields are:
|
||||
least important ones are skipped gracefully rather than the allocation failing
|
||||
arbitrarily. Ties are broken by name. Priority does not affect the NV index,
|
||||
the algorithm, or anything measured into the NvPCR.
|
||||
* `orderly` — a boolean, defaulting to `true`. It controls whether the NV index
|
||||
is allocated with the `TPMA_NV_ORDERLY` attribute set, which selects whether
|
||||
the TPM keeps the NV index in RAM or in persistent memory (NVRAM). On
|
||||
physical TPMs RAM is typically much more constrained than persistent memory,
|
||||
but persistent memory is subject to wear. We hence prefer `TPMA_NV_ORDERLY`
|
||||
disabled (i.e. NVRAM) for NvPCRs that are written only once each boot — which
|
||||
translates into a conservative number of write cycles over the lifetime of a
|
||||
TPM — but enabled (i.e. RAM) for NvPCRs we expect to be written many times
|
||||
during runtime, so that we minimize wear. This reflects real-life experience
|
||||
where the RAM in TPMs is so constrained that allocating many NvPCRs in TPM
|
||||
RAM simply doesn't work. For now, only the `hardware` NvPCR (which is written
|
||||
just once, early at boot) sets this flag to false.
|
||||
|
||||
There's one complication: these NV indexes (like any NV indexes) can be deleted
|
||||
by anyone with access to the TPM, and then be recreated. This could be used to
|
||||
|
||||
+87
-50
@@ -7183,9 +7183,9 @@ static int tpm2_define_nvpcr_nv_index(
|
||||
const Tpm2Handle *session,
|
||||
TPM2_HANDLE nv_index,
|
||||
TPMI_ALG_HASH algorithm,
|
||||
bool orderly,
|
||||
Tpm2Handle **ret_nv_handle) {
|
||||
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL;
|
||||
TSS2_RC rc;
|
||||
int r;
|
||||
|
||||
@@ -7203,11 +7203,13 @@ static int tpm2_define_nvpcr_nv_index(
|
||||
if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Digest too large for extension.");
|
||||
|
||||
r = tpm2_handle_new(c, &new_handle);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
|
||||
/* If we already ran into NV index space exhaustion for this orderly mode during this boot, don't
|
||||
* bother trying again — the situation is unlikely to improve until reboot. We track this via a flag
|
||||
* 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";
|
||||
|
||||
TPM2B_NV_PUBLIC public_info = {
|
||||
.size = sizeof_field(TPM2B_NV_PUBLIC, nvPublic),
|
||||
@@ -7215,7 +7217,7 @@ static int tpm2_define_nvpcr_nv_index(
|
||||
.nvIndex = nv_index,
|
||||
.nameAlg = algorithm,
|
||||
.attributes = TPMA_NV_CLEAR_STCLEAR |
|
||||
TPMA_NV_ORDERLY |
|
||||
(orderly ? TPMA_NV_ORDERLY : 0) |
|
||||
TPMA_NV_OWNERWRITE |
|
||||
TPMA_NV_AUTHWRITE |
|
||||
TPMA_NV_OWNERREAD |
|
||||
@@ -7225,62 +7227,93 @@ static int tpm2_define_nvpcr_nv_index(
|
||||
},
|
||||
};
|
||||
|
||||
rc = sym_Esys_NV_DefineSpace(
|
||||
c->esys_context,
|
||||
/* authHandle= */ ESYS_TR_RH_OWNER,
|
||||
/* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
|
||||
/* shandle2= */ ESYS_TR_NONE,
|
||||
/* shandle3= */ ESYS_TR_NONE,
|
||||
/* auth= */ NULL,
|
||||
&public_info,
|
||||
&new_handle->esys_handle);
|
||||
if (rc == TPM2_RC_NV_SPACE)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
|
||||
"NV index space on TPM exhausted, cannot allocate NvPCR.");
|
||||
if (rc == TPM2_RC_NV_DEFINED) {
|
||||
log_debug("NV index 0x%" PRIu32 " already registered.", nv_index);
|
||||
bool exhausted;
|
||||
if (access(exhausted_flag, F_OK) < 0) {
|
||||
if (errno != ENOENT)
|
||||
log_debug_errno(errno, "Failed to check whether %s exists, assuming it does not: %m", exhausted_flag);
|
||||
|
||||
new_handle = tpm2_handle_free(new_handle);
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL;
|
||||
r = tpm2_handle_new(c, &new_handle);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL;
|
||||
r = tpm2_nv_index_to_handle(
|
||||
c,
|
||||
nv_index,
|
||||
session,
|
||||
&nv_public_real,
|
||||
/* ret_name= */ NULL,
|
||||
&new_handle);
|
||||
if (r <= 0)
|
||||
return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index);
|
||||
new_handle->flush = false; /* This is a persistent NV index, don't flush hence */
|
||||
|
||||
log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index);
|
||||
rc = sym_Esys_NV_DefineSpace(
|
||||
c->esys_context,
|
||||
/* authHandle= */ ESYS_TR_RH_OWNER,
|
||||
/* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD,
|
||||
/* shandle2= */ ESYS_TR_NONE,
|
||||
/* shandle3= */ ESYS_TR_NONE,
|
||||
/* auth= */ NULL,
|
||||
&public_info,
|
||||
&new_handle->esys_handle);
|
||||
if (rc == TPM2_RC_NV_SPACE) {
|
||||
/* Remember that we ran out of NV index space for this orderly mode, so that we don't keep
|
||||
* retrying the (doomed) allocation until reboot. */
|
||||
r = touch(exhausted_flag);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to create %s flag file, ignoring: %m", exhausted_flag);
|
||||
|
||||
if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) ||
|
||||
nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex ||
|
||||
nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg ||
|
||||
((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~TPMA_NV_WRITTEN) != 0 ||
|
||||
nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST),
|
||||
"Public data of nvindex 0x%x does not match our expectations.", nv_index);
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
|
||||
"NV index space on TPM exhausted, cannot allocate NvPCR.");
|
||||
}
|
||||
if (rc == TSS2_RC_SUCCESS) {
|
||||
log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index);
|
||||
|
||||
log_debug("Public info for nvindex 0x%x checks out, using.", nv_index);
|
||||
if (ret_nv_handle)
|
||||
*ret_nv_handle = TAKE_PTR(new_handle);
|
||||
|
||||
if (ret_nv_handle)
|
||||
*ret_nv_handle = TAKE_PTR(new_handle);
|
||||
return 1;
|
||||
}
|
||||
if (rc != TPM2_RC_NV_DEFINED)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc));
|
||||
|
||||
return 0;
|
||||
log_debug("NV index 0x%" PRIx32 " already registered.", nv_index);
|
||||
exhausted = false;
|
||||
} else {
|
||||
log_debug("TPM NV index space previously found exhausted (%s exists), refusing to allocate %s NvPCR, but checking if it already exists.",
|
||||
exhausted_flag, orderly ? "orderly" : "non-orderly");
|
||||
exhausted = true;
|
||||
}
|
||||
if (rc != TSS2_RC_SUCCESS)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc));
|
||||
|
||||
log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index);
|
||||
/* We either got told that this NV index already exists or we didn't even try to allocate it, because
|
||||
* it failed before. Let's get information about it, in the hope it exists. */
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL;
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL;
|
||||
r = tpm2_nv_index_to_handle(
|
||||
c,
|
||||
nv_index,
|
||||
session,
|
||||
&nv_public_real,
|
||||
/* ret_name= */ NULL,
|
||||
&new_handle);
|
||||
if (r <= 0) {
|
||||
if (exhausted)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), "Unable to acquire NvPCR and space exhaustion was indicated before.");
|
||||
|
||||
return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to acquire handle to NV index 0x%" PRIx32 ".", nv_index);
|
||||
}
|
||||
|
||||
log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index);
|
||||
|
||||
if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) ||
|
||||
nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex ||
|
||||
nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg ||
|
||||
((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~(TPMA_NV_WRITTEN|TPMA_NV_ORDERLY)) != 0 ||
|
||||
nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST),
|
||||
"Public data of nvindex 0x%" PRIx32 " does not match our expectations.", nv_index);
|
||||
|
||||
log_debug("Public info for nvindex 0x%" PRIx32 " checks out, using.", nv_index);
|
||||
|
||||
if (ret_nv_handle)
|
||||
*ret_nv_handle = TAKE_PTR(new_handle);
|
||||
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm2_extend_nvpcr_nv_index(
|
||||
@@ -7976,6 +8009,7 @@ typedef struct NvPCRData {
|
||||
uint16_t algorithm;
|
||||
uint32_t nv_index;
|
||||
uint64_t priority;
|
||||
bool orderly;
|
||||
} NvPCRData;
|
||||
|
||||
static void nvpcr_data_done(NvPCRData *d) {
|
||||
@@ -8016,12 +8050,14 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) {
|
||||
{ "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 },
|
||||
{ "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY },
|
||||
{ "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(NvPCRData, priority), 0 },
|
||||
{ "orderly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(NvPCRData, orderly), 0 },
|
||||
{},
|
||||
};
|
||||
|
||||
_cleanup_(nvpcr_data_done) NvPCRData p = {
|
||||
.algorithm = TPM2_ALG_SHA256,
|
||||
.priority = TPM2_NVPCR_PRIORITY_DEFAULT,
|
||||
.orderly = true,
|
||||
};
|
||||
r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
|
||||
if (r < 0)
|
||||
@@ -8652,6 +8688,7 @@ int tpm2_nvpcr_initialize(
|
||||
session,
|
||||
p.nv_index,
|
||||
p.algorithm,
|
||||
p.orderly,
|
||||
&nv_handle);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
"name" : "hardware",
|
||||
"algorithm" : "sha256",
|
||||
"nvIndex" : {{TPM2_NVPCR_BASE + 0}},
|
||||
"priority" : 500
|
||||
"priority" : 500,
|
||||
"orderly" : false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user