From patchwork Wed Dec 4 00:06:46 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eric Richter X-Patchwork-Id: 1203928 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47SK2j05mbz9s4Y for ; Wed, 4 Dec 2019 11:09:01 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=linux.ibm.com Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 47SK2h699qzDqST for ; Wed, 4 Dec 2019 11:09:00 +1100 (AEDT) X-Original-To: skiboot@lists.ozlabs.org Delivered-To: skiboot@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=linux.ibm.com (client-ip=148.163.158.5; helo=mx0a-001b2d01.pphosted.com; envelope-from=erichte@linux.ibm.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=linux.ibm.com Received: from mx0a-001b2d01.pphosted.com (mx0b-001b2d01.pphosted.com [148.163.158.5]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 47SK0X4x74zDqLd for ; Wed, 4 Dec 2019 11:07:08 +1100 (AEDT) Received: from pps.filterd (m0098421.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.42/8.16.0.42) with SMTP id xB3NWEdK090521 for ; Tue, 3 Dec 2019 19:07:05 -0500 Received: from e06smtp01.uk.ibm.com (e06smtp01.uk.ibm.com [195.75.94.97]) by mx0a-001b2d01.pphosted.com with ESMTP id 2wnsvhapbx-1 (version=TLSv1.2 cipher=AES256-GCM-SHA384 bits=256 verify=NOT) for ; Tue, 03 Dec 2019 19:07:05 -0500 Received: from localhost by e06smtp01.uk.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Wed, 4 Dec 2019 00:07:03 -0000 Received: from b06cxnps3074.portsmouth.uk.ibm.com (9.149.109.194) by e06smtp01.uk.ibm.com (192.168.101.131) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; (version=TLSv1/SSLv3 cipher=AES256-GCM-SHA384 bits=256/256) Wed, 4 Dec 2019 00:07:01 -0000 Received: from d06av23.portsmouth.uk.ibm.com (d06av23.portsmouth.uk.ibm.com [9.149.105.59]) by b06cxnps3074.portsmouth.uk.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id xB406wnG52756700 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK); Wed, 4 Dec 2019 00:06:58 GMT Received: from d06av23.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 0A3C2A4053; Wed, 4 Dec 2019 00:06:58 +0000 (GMT) Received: from d06av23.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id BD07AA404D; Wed, 4 Dec 2019 00:06:56 +0000 (GMT) Received: from ceres.ibmuc.com (unknown [9.80.225.147]) by d06av23.portsmouth.uk.ibm.com (Postfix) with ESMTP; Wed, 4 Dec 2019 00:06:56 +0000 (GMT) From: Eric Richter To: skiboot@lists.ozlabs.org Date: Tue, 3 Dec 2019 18:06:46 -0600 X-Mailer: git-send-email 2.21.0 In-Reply-To: <20191204000650.28649-1-erichte@linux.ibm.com> References: <20191204000650.28649-1-erichte@linux.ibm.com> MIME-Version: 1.0 X-TM-AS-GCONF: 00 x-cbid: 19120400-4275-0000-0000-0000038AC492 X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 19120400-4276-0000-0000-0000389E646C Message-Id: <20191204000650.28649-4-erichte@linux.ibm.com> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10434:6.0.95, 18.0.572 definitions=2019-12-03_07:2019-12-02, 2019-12-03 signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 phishscore=0 impostorscore=0 priorityscore=1501 bulkscore=0 mlxlogscore=999 malwarescore=0 mlxscore=0 adultscore=0 suspectscore=3 lowpriorityscore=0 clxscore=1015 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-1910280000 definitions=main-1912030171 Subject: [Skiboot] [PATCH 3/7] secvar/storage: add secvar storage driver for pnor-based p9 platforms X-BeenThere: skiboot@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Mailing list for skiboot development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: nayna@linux.ibm.com, gcwilson@linux.ibm.com, erpalmer@us.ibm.com Errors-To: skiboot-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Skiboot" This patch implements the platform specific logic for persisting the secure variable storage banks across reboots via the SECBOOT PNOR partition. For POWER 9, all secure variables and updates are stored in the in the SECBOOT PNOR partition. The partition is split into three sections: two variable bank sections, and a section for storing updates. The driver alternates writes between the two variable sections, so that the final switch from one set of variables to the next can be as atomic as possible by flipping an "active bit" stored in TPM NV. PNOR space provides no lock protection, so prior to writing the variable bank, a sha256 hash is calculated and stored in TPM NV. This hash is compared against the hash of the variables loaded from PNOR to ensure consistency -- otherwise a failure is reported, no keys are loaded (which should cause skiroot to refuse to boot if secure boot support is enabled). Signed-off-by: Eric Richter --- include/secvar.h | 1 + libstb/secvar/storage/Makefile.inc | 4 +- libstb/secvar/storage/secboot_tpm.c | 293 +++++++++++++++++++ libstb/secvar/test/secvar-test-secboot-tpm.c | 134 +++++++++ 4 files changed, 430 insertions(+), 2 deletions(-) create mode 100644 libstb/secvar/storage/secboot_tpm.c create mode 100644 libstb/secvar/test/secvar-test-secboot-tpm.c diff --git a/include/secvar.h b/include/secvar.h index c41fb739..2875c700 100644 --- a/include/secvar.h +++ b/include/secvar.h @@ -23,6 +23,7 @@ struct secvar_backend_driver { const char *compatible; // String to use for compatible in secvar node }; +extern struct secvar_storage_driver secboot_tpm_driver; int secvar_main(struct secvar_storage_driver, struct secvar_backend_driver); diff --git a/libstb/secvar/storage/Makefile.inc b/libstb/secvar/storage/Makefile.inc index b7a821ec..5926e2f5 100644 --- a/libstb/secvar/storage/Makefile.inc +++ b/libstb/secvar/storage/Makefile.inc @@ -1,11 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 # -*-Makefile-*- -SECVAR_STORAGE_DIR = libstb/secvar/storage +SECVAR_STORAGE_DIR = $(SRC)/libstb/secvar/storage SUBDIRS += $(SECVAR_STORAGE_DIR) -SECVAR_STORAGE_SRCS = +SECVAR_STORAGE_SRCS = secboot_tpm.c SECVAR_STORAGE_OBJS = $(SECVAR_STORAGE_SRCS:%.c=%.o) SECVAR_STORAGE = $(SECVAR_STORAGE_DIR)/built-in.a diff --git a/libstb/secvar/storage/secboot_tpm.c b/libstb/secvar/storage/secboot_tpm.c new file mode 100644 index 00000000..97b59b98 --- /dev/null +++ b/libstb/secvar/storage/secboot_tpm.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: Apache-2.0 +/* Copyright 2019 IBM Corp. */ +#ifndef pr_fmt +#define pr_fmt(fmt) "SECBOOT_TPM: " fmt +#endif + +#include +#include +#include +#include +#include "../secvar.h" +#include "../secvar_tpmnv.h" + +//#define CYCLE_BIT(b) (((((b-1)%SECBOOT_VARIABLE_BANK_NUM)+1)%SECBOOT_VARIABLE_BANK_NUM)+1) +#define CYCLE_BIT(b) (b^0x1) + +#define TPMNV_ID_ACTIVE_BIT 0x53414242 // SABB +#define TPMNV_ID_HASH_BANK_0 0x53484230 // SHB0 +#define TPMNV_ID_HASH_BANK_1 0x53484231 // SHB1 + +#define GET_HASH_BANK_ID(bit) ((bit)?TPMNV_ID_HASH_BANK_1:TPMNV_ID_HASH_BANK_0) + +// TODO: Determine reasonable values for these, or have platform set it? +#define SECBOOT_VARIABLE_BANK_SIZE 32000 +#define SECBOOT_UPDATE_BANK_SIZE 32000 + +#define SECBOOT_VARIABLE_BANK_NUM 2 + +// Because mbedtls doesn't define this? +#define SHA256_DIGEST_LENGTH 32 + +/* 0x5053424b = "PSBK" or Power Secure Boot Keystore */ +#define SECBOOT_MAGIC_NUMBER 0x5053424b +#define SECBOOT_VERSION 1 + +struct secboot_header { + uint32_t magic_number; + uint8_t version; + uint8_t reserved[3]; // Fix alignment +} __packed; + +struct secboot { + struct secboot_header header; + char bank[SECBOOT_VARIABLE_BANK_NUM][SECBOOT_VARIABLE_BANK_SIZE]; + char update[SECBOOT_UPDATE_BANK_SIZE]; +} __packed; + +// TODO: This should be deleted when the TSS is added +// Only here to fix a collision that occurs in the test case where the +// temporary copy of the above in secvar_tpmnv.c conflicts +#define _secboot_header_ + +struct secboot *secboot_image; + +static void calc_bank_hash(char *target_hash, char *source_buf, uint64_t size) +{ + mbedtls_sha256_context ctx; + + mbedtls_sha256_init(&ctx); + mbedtls_sha256_update_ret(&ctx, source_buf, size); + mbedtls_sha256_finish_ret(&ctx, target_hash); +} + +static int secboot_format(void) +{ + char bank_hash[SHA256_DIGEST_LENGTH]; + + if (!platform.secboot_write) + return OPAL_UNSUPPORTED; + + memset(secboot_image, 0x00, sizeof(struct secboot)); + + secboot_image->header.magic_number = SECBOOT_MAGIC_NUMBER; + secboot_image->header.version = SECBOOT_VERSION; + + // Write the empty hash to the tpm so loads work in the future + calc_bank_hash(bank_hash, secboot_image->bank[0], SECBOOT_VARIABLE_BANK_SIZE); + secvar_tpmnv_write(TPMNV_ID_HASH_BANK_0, bank_hash, SHA256_DIGEST_LENGTH, 0); + + return platform.secboot_write(0, secboot_image, sizeof(struct secboot)); +} + +// Flattens a linked-list bank into a contiguous buffer for writing +static int secboot_serialize_bank(struct list_head *bank, char *target, size_t target_size, int flags) +{ + struct secvar_node *node; + char *tmp = target; + + if (!bank) + return OPAL_INTERNAL_ERROR; + if (!target) + return OPAL_INTERNAL_ERROR; + + list_for_each(bank, node, link) { + if (node->flags != flags) + continue; + + // Bail early if we are out of storage space + if ((target - tmp) + sizeof(struct secvar) + node->var->data_size > target_size) { + return OPAL_EMPTY; + } + + memcpy(target, node->var, sizeof(struct secvar) + node->var->data_size); + + target += sizeof(struct secvar) + node->var->data_size; + } + + return OPAL_SUCCESS; +} + + +static int secboot_load_from_pnor(struct list_head *bank, char *source, size_t max_size) +{ + char *src; + struct secvar_node *tmp; + struct secvar *hdr; + + src = source; + + while (src < (source + max_size)) { + // Load in the header first to get the size, and check if we are at the end + hdr = (struct secvar *) src; + if (hdr->key_len == 0) { + break; + } + + tmp = alloc_secvar(hdr->data_size); + if (!tmp) { + prlog(PR_ERR, "Could not allocate memory for loading secvar from image\n"); + return OPAL_NO_MEM; + } + + memcpy(tmp->var, src, sizeof(struct secvar) + hdr->data_size); + + list_add_tail(bank, &tmp->link); + src += sizeof(struct secvar) + hdr->data_size; + } + + return OPAL_SUCCESS; +} + + +static int secboot_tpm_write_bank(struct list_head *bank, int section) +{ + int rc; + uint64_t bit; + char bank_hash[SHA256_DIGEST_LENGTH]; + + switch(section) { + case SECVAR_VARIABLE_BANK: + // Get the current bit and flip it + secvar_tpmnv_read(TPMNV_ID_ACTIVE_BIT, &bit, sizeof(bit), 0); + bit = CYCLE_BIT(bit); + + // Calculate the bank hash, and write to TPM NV + rc = secboot_serialize_bank(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE, 0); + if (rc) + break; + + calc_bank_hash(bank_hash, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE); + rc = secvar_tpmnv_write(GET_HASH_BANK_ID(bit), bank_hash, SHA256_DIGEST_LENGTH, 0); + if (rc) + break; + + // Write new variable bank to pnor + rc = platform.secboot_write(0, secboot_image, sizeof(struct secboot)); + if (rc) + break; + + // Flip the bit, and write to TPM NV + rc = secvar_tpmnv_write(TPMNV_ID_ACTIVE_BIT, &bit, sizeof(bit), 0); + break; + case SECVAR_UPDATE_BANK: + memset(secboot_image->update, 0, SECBOOT_UPDATE_BANK_SIZE); + rc = secboot_serialize_bank(bank, secboot_image->update, SECBOOT_UPDATE_BANK_SIZE, 0); + if (rc) + break; + + rc = platform.secboot_write(0, secboot_image, sizeof(struct secboot)); + break; + default: + rc = OPAL_HARDWARE; + } + + return rc; +} + + +static int secboot_tpm_load_variable_bank(struct list_head *bank) +{ + char bank_hash[SHA256_DIGEST_LENGTH]; + char tpm_bank_hash[SHA256_DIGEST_LENGTH]; + uint64_t bit; + + secvar_tpmnv_read(TPMNV_ID_ACTIVE_BIT, &bit, sizeof(bit), 0); + secvar_tpmnv_read(GET_HASH_BANK_ID(bit), tpm_bank_hash, SHA256_DIGEST_LENGTH, 0); + + calc_bank_hash(bank_hash, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE); + if (memcmp(bank_hash, tpm_bank_hash, SHA256_DIGEST_LENGTH)) + return OPAL_PERMISSION; // Tampered pnor space detected, abandon ship + + return secboot_load_from_pnor(bank, secboot_image->bank[bit], SECBOOT_VARIABLE_BANK_SIZE); +} + + +static int secboot_tpm_load_bank(struct list_head *bank, int section) +{ + switch(section) { + case SECVAR_VARIABLE_BANK: + return secboot_tpm_load_variable_bank(bank); + case SECVAR_UPDATE_BANK: + return secboot_load_from_pnor(bank, secboot_image->update, SECBOOT_UPDATE_BANK_SIZE); + default: + return OPAL_HARDWARE; + } + + return OPAL_HARDWARE; +} + + +static int secboot_tpm_store_init(void) +{ + int rc; + unsigned secboot_size; + + // Already initialized + if (secboot_image) + return OPAL_SUCCESS; + + if (!platform.secboot_info) + return OPAL_UNSUPPORTED; + + prlog(PR_DEBUG, "Initializing for pnor+tpm based platform\n"); + + rc = secvar_tpmnv_alloc(TPMNV_ID_ACTIVE_BIT, sizeof(uint64_t)); + rc |= secvar_tpmnv_alloc(TPMNV_ID_HASH_BANK_0, SHA256_DIGEST_LENGTH); + rc |= secvar_tpmnv_alloc(TPMNV_ID_HASH_BANK_1, SHA256_DIGEST_LENGTH); + if (rc) { + prlog(PR_ERR, "unable to alloc or find the tpmnv space\n"); + return rc; + } + + rc = platform.secboot_info(&secboot_size); + if (rc) { + prlog(PR_ERR, "error %d retrieving keystore info\n", rc); + return rc; + } + if (sizeof(struct secboot) > secboot_size) { + prlog(PR_ERR, "secboot partition %d KB too small. min=%ld\n", + secboot_size >> 10, sizeof(struct secboot)); + return OPAL_RESOURCE; + } + + secboot_image = memalign(0x1000, sizeof(struct secboot)); + if (!secboot_image) { + prlog(PR_ERR, "Failed to allocate space for the secboot image\n"); + return OPAL_NO_MEM; + } + + /* Read it in */ + rc = platform.secboot_read(secboot_image, 0, sizeof(struct secboot)); + if (rc) { + prlog(PR_ERR, "failed to read the secboot partition, rc=%d\n", rc); + goto out_free; + } + + if ((secboot_image->header.magic_number != SECBOOT_MAGIC_NUMBER) + || tpm_first_init ) { + prlog(PR_INFO, "Formatting secboot partition...\n"); + rc = secboot_format(); + if (rc) { + prlog(PR_ERR, "Failed to format secboot!\n"); + goto out_free; + } + } + + return OPAL_SUCCESS; + +out_free: + if (secboot_image) { + free(secboot_image); + secboot_image = NULL; + } + + return rc; +} + +struct secvar_storage_driver secboot_tpm_driver = { + .load_bank = secboot_tpm_load_bank, + .write_bank = secboot_tpm_write_bank, + .store_init = secboot_tpm_store_init, + .max_var_size = 8192, +}; diff --git a/libstb/secvar/test/secvar-test-secboot-tpm.c b/libstb/secvar/test/secvar-test-secboot-tpm.c new file mode 100644 index 00000000..9a433f92 --- /dev/null +++ b/libstb/secvar/test/secvar-test-secboot-tpm.c @@ -0,0 +1,134 @@ +#include "secvar_common_test.c" +#include "../storage/secboot_tpm.c" +#include "../../crypto/mbedtls/library/sha256.c" +#include "../../crypto/mbedtls/library/platform_util.c" +#include "../secvar_tpmnv.c" +#include "../secvar_util.c" + +char *secboot_buffer; + +#define ARBITRARY_SECBOOT_SIZE 128000 + +const char *secvar_test_name = "secboot_tpm"; + +static int secboot_read(void *dst, uint32_t src, uint32_t len) +{ + memcpy(dst, secboot_buffer + src, len); + return 0; +} + +static int secboot_write(uint32_t dst, void *src, uint32_t len) +{ + memcpy(secboot_buffer + dst, src, len); + return 0; +} + +static int secboot_info(uint32_t *total_size) +{ + *total_size = ARBITRARY_SECBOOT_SIZE; + return 0; +} + +struct platform platform; + +int run_test(void) +{ + int rc; + struct secvar_node *tmp; + + platform.secboot_read = secboot_read; + platform.secboot_write = secboot_write; + platform.secboot_info = secboot_info; + + secboot_buffer = zalloc(ARBITRARY_SECBOOT_SIZE); + + // Initialize and format the storage + rc = secboot_tpm_store_init(); + ASSERT(OPAL_SUCCESS == rc); + + // Load the just-formatted empty section + rc = secboot_tpm_load_bank(&variable_bank, SECVAR_VARIABLE_BANK); + ASSERT(OPAL_SUCCESS == rc); + ASSERT(0 == list_length(&variable_bank)); + + // Add some test variables + tmp = alloc_secvar(8); + tmp->var->key_len = 5; + memcpy(tmp->var->key, "test", 5); + tmp->var->data_size = 8; + memcpy(tmp->var->data, "testdata", 8); + list_add_tail(&variable_bank, &tmp->link); + + tmp = alloc_secvar(8); + tmp->var->key_len = 4; + memcpy(tmp->var->key, "foo", 4); + tmp->var->data_size = 8; + memcpy(tmp->var->data, "moredata", 8); + list_add_tail(&variable_bank, &tmp->link); + + // Write the bank + rc = secboot_tpm_write_bank(&variable_bank, SECVAR_VARIABLE_BANK); + ASSERT(OPAL_SUCCESS == rc); + // should write to bank 1 first + ASSERT(secboot_image->bank[1][0] != 0); + ASSERT(secboot_image->bank[0][0] == 0); + + // Clear the variable list + clear_bank_list(&variable_bank); + ASSERT(0 == list_length(&variable_bank)); + + // Load the bank + rc = secboot_tpm_load_bank(&variable_bank, SECVAR_VARIABLE_BANK); + ASSERT(OPAL_SUCCESS == rc); + ASSERT(2 == list_length(&variable_bank)); + + // Change a variable + tmp = list_top(&variable_bank, struct secvar_node, link); + memcpy(tmp->var->data, "somethin", 8); + + // Write the bank + rc = secboot_tpm_write_bank(&variable_bank, SECVAR_VARIABLE_BANK); + ASSERT(OPAL_SUCCESS == rc); + // should have data in both now + ASSERT(secboot_image->bank[0][0] != 0); + ASSERT(secboot_image->bank[1][0] != 0); + + clear_bank_list(&variable_bank); + + // 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); + ASSERT(rc != OPAL_SUCCESS); // TODO: permission? + + // Fix it back... + secboot_image->bank[0][0] = ~secboot_image->bank[0][0]; + + // Should be ok again + rc = secboot_tpm_load_bank(&variable_bank, SECVAR_VARIABLE_BANK); + ASSERT(rc == OPAL_SUCCESS); + + clear_bank_list(&variable_bank); + free(secboot_buffer); + + return 0; +} + +int main(void) +{ + int rc = 0; + + list_head_init(&variable_bank); + + rc = run_test(); + + if (rc) + printf(COLOR_RED "FAILED" COLOR_RESET "\n"); + else + printf(COLOR_GREEN "OK" COLOR_RESET "\n"); + + free(tpm_image); + free(secboot_image); + + return rc; +}