diff options
Diffstat (limited to 'roms/skiboot/libstb/secvar/backend/edk2-compat.c')
-rw-r--r-- | roms/skiboot/libstb/secvar/backend/edk2-compat.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/roms/skiboot/libstb/secvar/backend/edk2-compat.c b/roms/skiboot/libstb/secvar/backend/edk2-compat.c new file mode 100644 index 000000000..9e61fbc60 --- /dev/null +++ b/roms/skiboot/libstb/secvar/backend/edk2-compat.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2020 IBM Corp. */ +#ifndef pr_fmt +#define pr_fmt(fmt) "EDK2_COMPAT: " fmt +#endif + +#include <opal.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <stdint.h> +#include <skiboot.h> +#include <ccan/endian/endian.h> +#include <mbedtls/error.h> +#include "libstb/crypto/pkcs7/pkcs7.h" +#include "edk2.h" +#include "../secvar.h" +#include "edk2-compat-process.h" +#include "edk2-compat-reset.h" + +struct list_head staging_bank; + +/* + * Initializes supported variables as empty if not loaded from + * storage. Variables are initialized as volatile if not found. + * Updates should clear this flag. + * Returns OPAL Error if anything fails in initialization + */ +static int edk2_compat_pre_process(struct list_head *variable_bank, + struct list_head *update_bank __unused) +{ + struct secvar *pkvar; + struct secvar *kekvar; + struct secvar *dbvar; + struct secvar *dbxvar; + struct secvar *tsvar; + + pkvar = find_secvar("PK", 3, variable_bank); + if (!pkvar) { + pkvar = new_secvar("PK", 3, NULL, 0, SECVAR_FLAG_VOLATILE + | SECVAR_FLAG_PROTECTED); + if (!pkvar) + return OPAL_NO_MEM; + + list_add_tail(variable_bank, &pkvar->link); + } + if (pkvar->data_size == 0) + setup_mode = true; + else + setup_mode = false; + + kekvar = find_secvar("KEK", 4, variable_bank); + if (!kekvar) { + kekvar = new_secvar("KEK", 4, NULL, 0, SECVAR_FLAG_VOLATILE); + if (!kekvar) + return OPAL_NO_MEM; + + list_add_tail(variable_bank, &kekvar->link); + } + + dbvar = find_secvar("db", 3, variable_bank); + if (!dbvar) { + dbvar = new_secvar("db", 3, NULL, 0, SECVAR_FLAG_VOLATILE); + if (!dbvar) + return OPAL_NO_MEM; + + list_add_tail(variable_bank, &dbvar->link); + } + + dbxvar = find_secvar("dbx", 4, variable_bank); + if (!dbxvar) { + dbxvar = new_secvar("dbx", 4, NULL, 0, SECVAR_FLAG_VOLATILE); + if (!dbxvar) + return OPAL_NO_MEM; + + list_add_tail(variable_bank, &dbxvar->link); + } + + /* + * Should only ever happen on first boot. Timestamp is + * initialized with all zeroes. + */ + tsvar = find_secvar("TS", 3, variable_bank); + if (!tsvar) { + tsvar = alloc_secvar(3, sizeof(struct efi_time) * 4); + if (!tsvar) + return OPAL_NO_MEM; + + memcpy(tsvar->key, "TS", 3); + tsvar->key_len = 3; + tsvar->data_size = sizeof(struct efi_time) * 4; + memset(tsvar->data, 0, tsvar->data_size); + list_add_tail(variable_bank, &tsvar->link); + } + + return OPAL_SUCCESS; +}; + +static int edk2_compat_process(struct list_head *variable_bank, + struct list_head *update_bank) +{ + struct secvar *var = NULL; + struct secvar *tsvar = NULL; + struct efi_time timestamp; + char *newesl = NULL; + int neweslsize; + int rc = 0; + + prlog(PR_INFO, "Setup mode = %d\n", setup_mode); + + /* Check HW-KEY-HASH */ + if (!setup_mode) { + rc = verify_hw_key_hash(); + if (rc != OPAL_SUCCESS) { + prlog(PR_ERR, "Hardware key hash verification mismatch. Keystore and update queue is reset.\n"); + rc = reset_keystore(variable_bank); + if (rc) + goto cleanup; + setup_mode = true; + goto cleanup; + } + } + + /* Return early if we have no updates to process */ + if (list_empty(update_bank)) { + return OPAL_EMPTY; + } + + /* + * Make a working copy of variable bank that is updated + * during process + */ + list_head_init(&staging_bank); + copy_bank_list(&staging_bank, variable_bank); + + /* + * Loop through each command in the update bank. + * If any command fails, it just loops out of the update bank. + * It should also clear the update bank. + */ + + /* Read the TS variable first time and then keep updating it in-memory */ + tsvar = find_secvar("TS", 3, &staging_bank); + + /* + * We cannot find timestamp variable, did someone tamper it ?, return + * OPAL_PERMISSION + */ + if (!tsvar) + return OPAL_PERMISSION; + + list_for_each(update_bank, var, link) { + + /* + * Submitted data is auth_2 descriptor + new ESL data + * Extract the auth_2 2 descriptor + */ + prlog(PR_INFO, "Update for %s\n", var->key); + + rc = process_update(var, &newesl, + &neweslsize, ×tamp, + &staging_bank, + tsvar->data); + if (rc) { + prlog(PR_ERR, "Update processing failed with rc %04x\n", rc); + break; + } + + /* + * If reached here means, signature is verified so update the + * value in the variable bank + */ + rc = update_variable_in_bank(var, + newesl, + neweslsize, + &staging_bank); + if (rc) { + prlog(PR_ERR, "Updating the variable data failed %04x\n", rc); + break; + } + + free(newesl); + newesl = NULL; + /* Update the TS variable with the new timestamp */ + rc = update_timestamp(var->key, + ×tamp, + tsvar->data); + if (rc) { + prlog (PR_ERR, "Variable updated, but timestamp updated failed %04x\n", rc); + break; + } + + /* + * If the PK is updated, update the secure boot state of the + * system at the end of processing + */ + if (key_equals(var->key, "PK")) { + /* + * PK is tied to a particular firmware image by mapping it with + * hw-key-hash of that firmware. When PK is updated, hw-key-hash + * is updated. And when PK is deleted, delete hw-key-hash as well + */ + if(neweslsize == 0) { + setup_mode = true; + delete_hw_key_hash(&staging_bank); + } else { + setup_mode = false; + add_hw_key_hash(&staging_bank); + } + prlog(PR_DEBUG, "setup mode is %d\n", setup_mode); + } + } + + if (rc == 0) { + /* Update the variable bank with updated working copy */ + clear_bank_list(variable_bank); + copy_bank_list(variable_bank, &staging_bank); + } + + free(newesl); + clear_bank_list(&staging_bank); + + /* Set the global variable setup_mode as per final contents in variable_bank */ + var = find_secvar("PK", 3, variable_bank); + if (!var) { + /* This should not happen */ + rc = OPAL_INTERNAL_ERROR; + goto cleanup; + } + + if (var->data_size == 0) + setup_mode = true; + else + setup_mode = false; + +cleanup: + /* + * For any failure in processing update queue, we clear the update bank + * and return failure + */ + clear_bank_list(update_bank); + + return rc; +} + +static int edk2_compat_post_process(struct list_head *variable_bank, + struct list_head *update_bank __unused) +{ + struct secvar *hwvar; + if (!setup_mode) { + secvar_set_secure_mode(); + prlog(PR_INFO, "Enforcing OS secure mode\n"); + /* + * HW KEY HASH is no more needed after this point. It is already + * visible to userspace via device-tree, so exposing via sysfs is + * just a duplication. Remove it from in-memory copy. + */ + hwvar = find_secvar("HWKH", 5, variable_bank); + if (!hwvar) { + prlog(PR_ERR, "cannot find hw-key-hash, should not happen\n"); + return OPAL_INTERNAL_ERROR; + } + list_del(&hwvar->link); + dealloc_secvar(hwvar); + } + + return OPAL_SUCCESS; +} + +static int edk2_compat_validate(struct secvar *var) +{ + + /* + * Checks if the update is for supported + * Non-volatile secure variables + */ + if (!key_equals(var->key, "PK") + && !key_equals(var->key, "KEK") + && !key_equals(var->key, "db") + && !key_equals(var->key, "dbx")) + return OPAL_PARAMETER; + + /* Check that signature type is PKCS7 */ + if (!is_pkcs7_sig_format(var->data)) + return OPAL_PARAMETER; + + return OPAL_SUCCESS; +}; + +struct secvar_backend_driver edk2_compatible_v1 = { + .pre_process = edk2_compat_pre_process, + .process = edk2_compat_process, + .post_process = edk2_compat_post_process, + .validate = edk2_compat_validate, + .compatible = "ibm,edk2-compat-v1", +}; |