| // 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; |
| tsvar->flags = SECVAR_FLAG_PROTECTED; |
| 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", |
| }; |