[Skiboot] [PATCH v2 2/6] secvar/secboot_tpm: unify behavior for bank hash check and secboot header check
Daniel Axtens
dja at axtens.net
Wed Nov 3 01:16:40 AEDT 2021
Eric Richter <erichte at linux.ibm.com> writes:
> As the PNOR variable space cannot be locked, the data must be integrity
> checked when loaded to ensure it has not beeen modified by an unauthorized
> party. In the event that a modification has been detected (i.e. hash mismatch),
> we must not load in data that could potentially be compromised.
>
> However, the previous code was a bit overzealous with its reaction to detecting
> a compromised SECBOOT partition, and also had some inconsistencies in behavior.
>
> Case 1: SECBOOT partition cleared.
> .init() checks the header for the magic number and version. As neither matches,
> will reformat the entire partition. Now, .load_bank() will pass, as the data
> was just freshly reformatted (note: this also could trigger the bug addressed
> in the previous patch). Only variables in the TPM will be loaded by
> .load_bank() as the data in SECBOOT is now empty.
>
> Case 2: Bank hash mismatch.
> .load_bank() panics and returns an error code, causing secvar_main() to jump
> to the error scenario, which prevents the secvar API from being exposed.
> os-secure-enforcing is set unconditionally, and the user will have no API to
> manage or attempt to fix their system without issuing a key clear request.
>
> This patch unifies the behavior of both of these cases. Now, .init() handles
> checking the header AND comparing the bank hash. If either check fails, the
> SECBOOT partition will be reformatted. Variables in the TPM will still be
> loaded in the .load_bank() step, and provided the backend stores its
> secure boot state in the TPM, secure boot state can be preserved.
>
> Signed-off-by: Eric Richter <erichte at linux.ibm.com>
> Tested-by: Nick Child <nick.child at ibm.com>
> ---
> libstb/secvar/storage/secboot_tpm.c | 30 +++++++++++++++++++-
> libstb/secvar/test/secvar-test-secboot-tpm.c | 6 ++++
> 2 files changed, 35 insertions(+), 1 deletion(-)
>
> diff --git a/libstb/secvar/storage/secboot_tpm.c b/libstb/secvar/storage/secboot_tpm.c
> index 5907ff07..932e5fd3 100644
> --- a/libstb/secvar/storage/secboot_tpm.c
> +++ b/libstb/secvar/storage/secboot_tpm.c
> @@ -374,7 +374,9 @@ fail:
> return rc;
> }
>
> -static int secboot_tpm_load_variable_bank(struct list_head *bank)
> +
> +/* Helper to validate the current active SECBOOT bank's data against the hash stored in the TPM */
> +static int compare_bank_hash(void)
> {
> char bank_hash[SHA256_DIGEST_LENGTH];
> uint64_t bit = tpmnv_control_image->active_bit;
> @@ -394,6 +396,15 @@ static int secboot_tpm_load_variable_bank(struct list_head *bank)
> /* Tampered pnor space detected, abandon ship */
> return OPAL_PERMISSION;
>
> + return OPAL_SUCCESS;
> +}
> +
So in doing this you take the check out of secvar_ops.load_bank(). I am
guessing that:
- secboot_tpm_store_init() gets called before any calls to load_bank()
- there's nothing that skiboot does that might cause the contents of
the PNOR to change between store_init() and load_bank()?
- anything that's able to change the PNOR while skiboot is running this
early boot stuff is outside the threat model?
Is that right?
> +
> +static int secboot_tpm_load_variable_bank(struct list_head *bank)
> +{
> + uint64_t bit = tpmnv_control_image->active_bit;
> + int rc;
> +
> rc = secboot_tpm_deserialize_from_buffer(bank, tpmnv_vars_image->vars, tpmnv_vars_size, SECVAR_FLAG_PROTECTED);
> if (rc)
> return rc;
> @@ -692,8 +703,25 @@ static int secboot_tpm_store_init(void)
> rc = secboot_format();
> if (rc)
> goto error;
> + goto done;
> }
>
> + /* Verify the active bank's integrity by comparing against the hash in TPM.
> + * Reformat if it does not match -- we do not want to load potentially
> + * compromised data.
> + * Ideally, the backend driver should retain secure boot state in
> + * protected (TPM) storage, so secure boot state should be the same, albeit
> + * without the data in unprotected (PNOR) storage.
Don't you have 3 reserved bytes in struct secboot_header? Could you use
one and say it's only defined in the case of the tpmnv_control header?
Or does this state already get preserved and I've just misunderstood the
interplay between backends/storage drivers/whatever?
> + */
> + rc = compare_bank_hash();
> + if (rc == OPAL_PERMISSION) {
> + rc = secboot_format();
> + if (rc)
> + goto error;
> + }
> + else if (rc)
> + goto error;
> +
> done:
> return OPAL_SUCCESS;
>
> diff --git a/libstb/secvar/test/secvar-test-secboot-tpm.c b/libstb/secvar/test/secvar-test-secboot-tpm.c
> index 798ca281..f203e57d 100644
> --- a/libstb/secvar/test/secvar-test-secboot-tpm.c
> +++ b/libstb/secvar/test/secvar-test-secboot-tpm.c
> @@ -101,10 +101,15 @@ int run_test(void)
>
> clear_bank_list(&variable_bank);
>
> + /* Disabling check for now, since bank hash check has been moved to _init().
> + * This will be re-enabled with the forthcoming rework of the tests/_init() procedure.
> + */
> +#if 0
> // Tamper with pnor, hash check should catch this
> secboot_image->bank[0][0] = ~secboot_image->bank[0][0];
>
> rc = secboot_tpm_load_bank(&variable_bank, SECVAR_VARIABLE_BANK);
> + fprintf(stderr, "rc = %d\n", rc);
> ASSERT(rc != OPAL_SUCCESS); // TODO: permission?
>
> // Fix it back...
> @@ -113,6 +118,7 @@ int run_test(void)
> // Should be ok again
> rc = secboot_tpm_load_bank(&variable_bank, SECVAR_VARIABLE_BANK);
> ASSERT(rc == OPAL_SUCCESS);
> +#endif
>
I haven't go through the rest of the series but is the rework part of
this series or not?
I think I conceptually prefer just deleting the code and leaving it in
the git history but I don't _really_ care, especially for tests...
Kind regards,
Daniel
More information about the Skiboot
mailing list