diff options
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | firmware/bdb/bdb.h | 16 | ||||
-rw-r--r-- | firmware/bdb/bdb_api.h | 54 | ||||
-rw-r--r-- | firmware/bdb/nvm.c | 231 | ||||
-rw-r--r-- | firmware/bdb/nvm.h | 100 | ||||
-rw-r--r-- | firmware/bdb/secrets.h | 31 | ||||
-rw-r--r-- | firmware/bdb/stub.c | 13 | ||||
-rw-r--r-- | tests/bdb_sprw_test.c | 328 |
8 files changed, 762 insertions, 16 deletions
@@ -377,7 +377,8 @@ BDBLIB_SRCS = \ firmware/bdb/bdb.c \ firmware/bdb/misc.c \ firmware/bdb/rsa.c \ - firmware/bdb/stub.c + firmware/bdb/stub.c \ + firmware/bdb/nvm.c # Support real TPM unless BIOS sets MOCK_TPM ifeq (${MOCK_TPM},) @@ -1195,7 +1196,7 @@ ${TEST21_BINS}: LIBS += ${UTILLIB21} ${TESTBDB_BINS}: ${FWLIB2X} ${UTILBDB} ${TESTBDB_BINS}: INCLUDES += -Ifirmware/bdb -${TESTBDB_BINS}: LIBS += ${FWLIB2X} ${UTILBDB_OBJS} ${BDBLIB_OBJS} +${TESTBDB_BINS}: LIBS += ${UTILBDB_OBJS} ${BDBLIB_OBJS} ${FWLIB2X} ${TESTLIB}: ${TESTLIB_OBJS} @${PRINTF} " RM $(subst ${BUILD}/,,$@)\n" diff --git a/firmware/bdb/bdb.h b/firmware/bdb/bdb.h index 30ecc17c..9f3b9c6b 100644 --- a/firmware/bdb/bdb.h +++ b/firmware/bdb/bdb.h @@ -37,6 +37,9 @@ enum bdb_return_code { * fully verified. */ BDB_GOOD_OTHER_THAN_KEY = 1, + /* Function is not implemented, thus supposed to be not called */ + BDB_ERROR_NOT_IMPLEMENTED, + /* Other errors */ BDB_ERROR_UNKNOWN = 100, @@ -72,6 +75,19 @@ enum bdb_return_code { /* Errors in vba_bdb_init */ BDB_ERROR_TRY_OTHER_SLOT, BDB_ERROR_RECOVERY_REQUEST, + + BDB_ERROR_NVM_INIT, + BDB_ERROR_NVM_WRITE, + BDB_ERROR_NVM_RW_HMAC, + BDB_ERROR_NVM_RW_INVALID_HMAC, + BDB_ERROR_NVM_INVALID_PARAMETER, + BDB_ERROR_NVM_INVALID_SECRET, + BDB_ERROR_NVM_RW_MAGIC, + BDB_ERROR_NVM_STRUCT_SIZE, + BDB_ERROR_NVM_WRITE_VERIFY, + BDB_ERROR_NVM_STRUCT_VERSION, + BDB_ERROR_NVM_VBE_READ, + BDB_ERROR_NVM_RW_BUFFER_SMALL, }; /*****************************************************************************/ diff --git a/firmware/bdb/bdb_api.h b/firmware/bdb/bdb_api.h index 53823fae..9979824e 100644 --- a/firmware/bdb/bdb_api.h +++ b/firmware/bdb/bdb_api.h @@ -8,10 +8,22 @@ #include <stdint.h> #include "vboot_register.h" +#include "nvm.h" +#include "secrets.h" struct vba_context { /* Indicate which slot is being tried: 0 - primary, 1 - secondary */ uint8_t slot; + + /* BDB */ + uint8_t *bdb; + + /* Secrets */ + struct bdb_ro_secrets *ro_secrets; + struct bdb_rw_secrets *rw_secrets; + + /* NVM-RW buffer */ + struct nvmrw nvmrw; }; /** @@ -38,6 +50,24 @@ int vba_bdb_finalize(struct vba_context *ctx); void vba_bdb_fail(struct vba_context *ctx); /** + * Update kernel and its data key version in NVM + * + * This is the function called from SP-RW, which receives a kernel version + * from an AP-RW after successful verification of a kernel. + * + * It checks whether the version in NVM-RW is older than the reported version + * or not. If so, it updates the version in NVM-RW. + * + * @param ctx + * @param kernel_data_key_version + * @param kernel_version + * @return BDB_SUCCESS or BDB_ERROR_* + */ +int vba_update_kernel_version(struct vba_context *ctx, + uint32_t kernel_data_key_version, + uint32_t kernel_version); + +/** * Get vboot register value * * Implemented by each chip @@ -65,4 +95,28 @@ void vbe_set_vboot_register(enum vboot_register type, uint32_t val); */ void vbe_reset(void); +/** + * Read contents from Non-Volatile Memory + * + * Implemented by each chip. + * + * @param type Type of NVM + * @param buf Buffer where the data will be read to + * @param size Size of data to read + * @return Zero if success or non-zero otherwise + */ +int vbe_read_nvm(enum nvm_type type, uint8_t *buf, uint32_t size); + +/** + * Write contents to Non-Volatile Memory + * + * Implemented by each chip. + * + * @param type Type of NVM + * @param buf Buffer where the data will be written from + * @param size Size of data to write + * @return Zero if success or non-zero otherwise + */ +int vbe_write_nvm(enum nvm_type type, void *buf, uint32_t size); + #endif diff --git a/firmware/bdb/nvm.c b/firmware/bdb/nvm.c new file mode 100644 index 00000000..b5c53af9 --- /dev/null +++ b/firmware/bdb/nvm.c @@ -0,0 +1,231 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "2sysincludes.h" +#include "2hmac.h" +#include "2sha.h" +#include "bdb_api.h" +#include "bdb_struct.h" +#include "bdb.h" +#include "nvm.h" +#include "secrets.h" + +static int nvmrw_validate(const void *buf, uint32_t size) +{ + const struct nvmrw *nvm = buf; + + if (nvm->struct_magic != NVM_RW_MAGIC) + return BDB_ERROR_NVM_RW_MAGIC; + + if (nvm->struct_major_version != NVM_HEADER_VERSION_MAJOR) + return BDB_ERROR_NVM_STRUCT_VERSION; + + if (size < nvm->struct_size) + return BDB_ERROR_NVM_STRUCT_SIZE; + + /* + * We allow any sizes between min and max so that we can handle minor + * version mismatches. Reader can be older than data or the other way + * around. FW in slot B can upgrade NVM-RW but fails to qualify as a + * stable boot path. Then, FW in slot A is invoked which is older than + * the NVM-RW written by FW in slot B. + */ + if (nvm->struct_size < NVM_RW_MIN_STRUCT_SIZE || + NVM_RW_MAX_STRUCT_SIZE < nvm->struct_size) + return BDB_ERROR_NVM_STRUCT_SIZE; + + return BDB_SUCCESS; +} + +static int nvmrw_verify(const struct bdb_ro_secrets *secrets, + const struct nvmrw *nvm, uint32_t size) +{ + uint8_t mac[NVM_HMAC_SIZE]; + int rv; + + if (!secrets || !nvm) + return BDB_ERROR_NVM_INVALID_PARAMETER; + + rv = nvmrw_validate(nvm, size); + if (rv) + return rv; + + /* Compute and verify HMAC */ + if (hmac(VB2_HASH_SHA256, secrets->nvm_rw, BDB_SECRET_SIZE, + nvm, nvm->struct_size - sizeof(mac), mac, sizeof(mac))) + return BDB_ERROR_NVM_RW_HMAC; + /* TODO: Use safe_memcmp */ + if (memcmp(mac, nvm->hmac, sizeof(mac))) + return BDB_ERROR_NVM_RW_INVALID_HMAC; + + return BDB_SUCCESS; +} + +int nvmrw_write(struct vba_context *ctx, enum nvm_type type) +{ + struct nvmrw *nvm = &ctx->nvmrw; + int retry = NVM_MAX_WRITE_RETRY; + int rv; + + if (!ctx) + return BDB_ERROR_NVM_INVALID_PARAMETER; + + if (!ctx->ro_secrets) + return BDB_ERROR_NVM_INVALID_SECRET; + + rv = nvmrw_validate(nvm, sizeof(*nvm)); + if (rv) + return rv; + + /* Update HMAC */ + hmac(VB2_HASH_SHA256, ctx->ro_secrets->nvm_rw, BDB_SECRET_SIZE, + nvm, nvm->struct_size - sizeof(nvm->hmac), + nvm->hmac, sizeof(nvm->hmac)); + + while (retry--) { + uint8_t buf[sizeof(struct nvmrw)]; + if (vbe_write_nvm(type, nvm, nvm->struct_size)) + continue; + if (vbe_read_nvm(type, buf, sizeof(buf))) + continue; + if (memcmp(buf, nvm, sizeof(buf))) + continue; + /* Write success */ + return BDB_SUCCESS; + } + + /* NVM seems corrupted. Go to chip recovery mode */ + return BDB_ERROR_NVM_WRITE; +} + +static int read_verify_nvmrw(enum nvm_type type, + const struct bdb_ro_secrets *secrets, + uint8_t *buf, uint32_t buf_size) +{ + struct nvmrw *nvm = (struct nvmrw *)buf; + int rv; + + /* Read minimum amount */ + if (vbe_read_nvm(type, buf, NVM_MIN_STRUCT_SIZE)) + return BDB_ERROR_NVM_VBE_READ; + + /* Validate the content */ + rv = nvmrw_validate(buf, buf_size); + if (rv) + return rv; + + /* Read full body */ + if (vbe_read_nvm(type, buf, nvm->struct_size)) + return BDB_ERROR_NVM_VBE_READ; + + /* Verify the content */ + rv = nvmrw_verify(secrets, nvm, sizeof(*nvm)); + return rv; + + return BDB_SUCCESS; +} + +int nvmrw_read(struct vba_context *ctx) +{ + uint8_t buf1[NVM_RW_MAX_STRUCT_SIZE]; + uint8_t buf2[NVM_RW_MAX_STRUCT_SIZE]; + struct nvmrw *nvm1 = (struct nvmrw *)buf1; + struct nvmrw *nvm2 = (struct nvmrw *)buf2; + int rv1, rv2; + + /* Read and verify the 1st copy */ + rv1 = read_verify_nvmrw(NVM_TYPE_RW_PRIMARY, ctx->ro_secrets, + buf1, sizeof(buf1)); + + /* Read and verify the 2nd copy */ + rv2 = read_verify_nvmrw(NVM_TYPE_RW_SECONDARY, ctx->ro_secrets, + buf2, sizeof(buf2)); + + if (rv1 == BDB_SUCCESS && rv2 == BDB_SUCCESS) { + /* Sync primary and secondary based on update_count. */ + if (nvm1->update_count > nvm2->update_count) + rv2 = !BDB_SUCCESS; + else if (nvm1->update_count < nvm2->update_count) + rv1 = !BDB_SUCCESS; + } else if (rv1 != BDB_SUCCESS && rv2 != BDB_SUCCESS){ + /* Abort. Neither was successful. */ + return rv1; + } + + if (rv1 == BDB_SUCCESS) + /* both copies are good. use primary copy */ + memcpy(&ctx->nvmrw, buf1, sizeof(ctx->nvmrw)); + else + /* primary is bad but secondary is good. */ + memcpy(&ctx->nvmrw, buf2, sizeof(ctx->nvmrw)); + + if (ctx->nvmrw.struct_minor_version != NVM_HEADER_VERSION_MINOR) { + /* + * Upgrade or downgrade is required. So, we need to write both. + * When upgrading, this is the place where new fields should be + * initialized. We don't increment update_count. + */ + ctx->nvmrw.struct_minor_version = NVM_HEADER_VERSION_MINOR; + ctx->nvmrw.struct_size = sizeof(ctx->nvmrw); + /* We don't worry about calculating hmac twice because + * this is a corner case. */ + rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); + rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); + } else if (rv1 != BDB_SUCCESS) { + /* primary copy is bad. sync it with secondary copy */ + rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); + } else if (rv2 != BDB_SUCCESS){ + /* secondary copy is bad. sync it with primary copy */ + rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); + } else { + /* Both copies are good and versions are same as the reader. + * Skip writing. This should be the common case. */ + } + + if (rv1 || rv2) + return rv1 ? rv1 : rv2; + + return BDB_SUCCESS; +} + +static int nvmrw_init(struct vba_context *ctx) +{ + if (nvmrw_read(ctx)) + return BDB_ERROR_NVM_INIT; + + return BDB_SUCCESS; +} + +int vba_update_kernel_version(struct vba_context *ctx, + uint32_t kernel_data_key_version, + uint32_t kernel_version) +{ + struct nvmrw *nvm = &ctx->nvmrw; + + if (nvmrw_verify(ctx->ro_secrets, nvm, sizeof(*nvm))) { + if (nvmrw_init(ctx)) + return BDB_ERROR_NVM_INIT; + } + + if (nvm->min_kernel_data_key_version < kernel_data_key_version || + nvm->min_kernel_version < kernel_version) { + int rv1, rv2; + + /* Roll forward versions */ + nvm->min_kernel_data_key_version = kernel_data_key_version; + nvm->min_kernel_version = kernel_version; + + /* Increment update counter */ + nvm->update_count++; + + /* Update both copies */ + rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); + rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); + if (rv1 || rv2) + return BDB_ERROR_RECOVERY_REQUEST; + } + + return BDB_SUCCESS; +} diff --git a/firmware/bdb/nvm.h b/firmware/bdb/nvm.h new file mode 100644 index 00000000..3de1a2df --- /dev/null +++ b/firmware/bdb/nvm.h @@ -0,0 +1,100 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef VBOOT_REFERENCE_BDB_NVM_H_ +#define VBOOT_REFERENCE_BDB_NVM_H_ + +#include <stdint.h> +#include "bdb_struct.h" +#include "bdb_api.h" + +enum nvm_type { + NVM_TYPE_WP_PRIMARY, + NVM_TYPE_WP_SECONDARY, + NVM_TYPE_RW_PRIMARY, + NVM_TYPE_RW_SECONDARY, +}; + +#define NVM_RW_MAGIC 0x3052766e + +/* Size in bytes of encrypted BUC (Boot Unlock Code) */ +#define BUC_ENC_DIGEST_SIZE 32 +/* Size in bytes of HMAC of struct NVM-RW */ +#define NVM_HMAC_SIZE BDB_SHA256_DIGEST_SIZE + +#define NVM_RW_FLAG_BUC_PRESENT (1 << 0) +#define NVM_RW_FLAG_DFM_DISABLE (1 << 1) +#define NVM_RW_FLAG_DOSM (1 << 2) + +/* This is the minimum size of the data needed to learn the actual size */ +#define NVM_MIN_STRUCT_SIZE 8 + +#define NVM_HEADER_VERSION_MAJOR 1 +#define NVM_HEADER_VERSION_MINOR 1 + +/* Maximum number of retries for writing NVM */ +#define NVM_MAX_WRITE_RETRY 2 + +struct nvmrw { + /* Magic number to identify struct */ + uint32_t struct_magic; + + /* Structure version */ + uint8_t struct_major_version; + uint8_t struct_minor_version; + + /* Size of struct in bytes. 96 for version 1.0 */ + uint16_t struct_size; + + /* Number of updates to structure contents */ + uint32_t update_count; + + /* Flags: NVM_RW_FLAG_* */ + uint32_t flags; + + /* Minimum valid kernel data key version */ + uint32_t min_kernel_data_key_version; + + /* Minimum valid kernel version */ + uint32_t min_kernel_version; + + /* Type of BUC */ + uint8_t buc_type; + + uint8_t reserved0[7]; + + /* Encrypted BUC */ + uint8_t buc_enc_digest[BUC_ENC_DIGEST_SIZE]; + + /* SHA-256 HMAC of the struct contents. Add new fields before this. */ + uint8_t hmac[NVM_HMAC_SIZE]; +} __attribute__((packed)); + +/* Size of the version 1.0 */ +#define NVM_RW_MIN_STRUCT_SIZE 96 +/* 4 Kbit EEPROM divided by 4 regions (RO,RW) x (1st,2nd) = 128 KB */ +#define NVM_RW_MAX_STRUCT_SIZE 128 + +/* For nvm_rw_read and nvm_write */ +struct vba_context; + +/** + * Read NVM-RW contents into the context + * + * @param ctx struct vba_context + * @return BDB_SUCCESS or BDB_ERROR_NVM_* + */ +int nvmrw_read(struct vba_context *ctx); + +/** + * Write to NVM-RW from the context + * + * @param ctx struct vba_context + * @param type NVM_TYPE_RW_* + * @return BDB_SUCCESS or BDB_ERROR_NVM_* + */ +int nvmrw_write(struct vba_context *ctx, enum nvm_type type); + +#endif diff --git a/firmware/bdb/secrets.h b/firmware/bdb/secrets.h new file mode 100644 index 00000000..e26e97cf --- /dev/null +++ b/firmware/bdb/secrets.h @@ -0,0 +1,31 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef VBOOT_REFERENCE_FIRMWARE_BDB_SECRETS_H_ +#define VBOOT_REFERENCE_FIRMWARE_BDB_SECRETS_H_ + +#define BDB_SECRET_SIZE 32 + +/* + * Secrets passed to SP-RW by SP-RO. How it's passed depends on chips. + * These are hash-extended by SP-RW. + */ +struct bdb_ro_secrets { + uint8_t nvm_wp[BDB_SECRET_SIZE]; + uint8_t nvm_rw[BDB_SECRET_SIZE]; + uint8_t bdb[BDB_SECRET_SIZE]; + uint8_t boot_verified[BDB_SECRET_SIZE]; + uint8_t boot_path[BDB_SECRET_SIZE]; +}; + +/* + * Additional secrets SP-RW derives from RO secrets. This can be independently + * updated as more secrets are needed. + */ +struct bdb_rw_secrets { + uint8_t buc[BDB_SECRET_SIZE]; +}; + +#endif diff --git a/firmware/bdb/stub.c b/firmware/bdb/stub.c index 87efbc33..58a00967 100644 --- a/firmware/bdb/stub.c +++ b/firmware/bdb/stub.c @@ -4,6 +4,7 @@ */ #include "bdb_api.h" +#include "bdb.h" __attribute__((weak)) uint32_t vbe_get_vboot_register(enum vboot_register type) @@ -22,3 +23,15 @@ void vbe_reset(void) { return; } + +__attribute__((weak)) +int vbe_read_nvm(enum nvm_type type, uint8_t *buf, uint32_t size) +{ + return BDB_ERROR_NOT_IMPLEMENTED; +} + +__attribute__((weak)) +int vbe_write_nvm(enum nvm_type type, void *buf, uint32_t size) +{ + return BDB_ERROR_NOT_IMPLEMENTED; +} diff --git a/tests/bdb_sprw_test.c b/tests/bdb_sprw_test.c index 4528751d..06739ae5 100644 --- a/tests/bdb_sprw_test.c +++ b/tests/bdb_sprw_test.c @@ -10,12 +10,14 @@ #include <string.h> #include "2sha.h" +#include "2hmac.h" #include "bdb.h" #include "bdb_api.h" #include "bdb_struct.h" #include "host.h" #include "test_common.h" #include "vboot_register.h" +#include "secrets.h" static struct bdb_header *bdb, *bdb0, *bdb1; static uint32_t vboot_register; @@ -24,6 +26,16 @@ static char slot_selected; static uint8_t aprw_digest[BDB_SHA256_DIGEST_SIZE]; static uint8_t reset_count; +/* NVM-RW image in storage (e.g. EEPROM) */ +static uint8_t nvmrw1[NVM_RW_MAX_STRUCT_SIZE]; +static uint8_t nvmrw2[NVM_RW_MAX_STRUCT_SIZE]; + +struct bdb_ro_secrets secrets = { + .nvm_rw = {0x00, }, +}; + +static int vbe_write_nvm_failure = 0; + static struct bdb_header *create_bdb(const char *key_dir, struct bdb_hash *hash, int num_hashes) { @@ -215,12 +227,12 @@ static void test_verify_aprw(const char *key_dir) slot_selected = 'X'; memcpy(aprw_digest, hash0.digest, 4); vbe_reset(); - TEST_EQ_S(reset_count, 1); - TEST_EQ_S(slot_selected, 'A'); + TEST_EQ(reset_count, 1, NULL); + TEST_EQ(slot_selected, 'A', NULL); TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY, - "VBOOT_REGISTER_FAILED_RW_PRIMARY==false"); + NULL); TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY, - "VBOOT_REGISTER_FAILED_RW_SECONDARY==false"); + NULL); /* (slotA, slotB) = (bad, good) */ reset_count = 0; @@ -228,12 +240,12 @@ static void test_verify_aprw(const char *key_dir) slot_selected = 'X'; memcpy(aprw_digest, hash1.digest, 4); vbe_reset(); - TEST_EQ_S(reset_count, 3); - TEST_EQ_S(slot_selected, 'B'); + TEST_EQ(reset_count, 3, NULL); + TEST_EQ(slot_selected, 'B', NULL); TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY, - "VBOOT_REGISTER_FAILED_RW_PRIMARY==true"); + NULL); TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY, - "VBOOT_REGISTER_FAILED_RW_SECONDARY==false"); + NULL); /* (slotA, slotB) = (bad, bad) */ reset_count = 0; @@ -241,21 +253,306 @@ static void test_verify_aprw(const char *key_dir) slot_selected = 'X'; memset(aprw_digest, 0, BDB_SHA256_DIGEST_SIZE); vbe_reset(); - TEST_EQ_S(reset_count, 5); - TEST_EQ_S(slot_selected, 'X'); + TEST_EQ(reset_count, 5, NULL); + TEST_EQ(slot_selected, 'X', NULL); TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY, - "VBOOT_REGISTER_FAILED_RW_PRIMARY==true"); + NULL); TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY, - "VBOOT_REGISTER_FAILED_RW_SECONDARY==true"); + NULL); TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_RECOVERY_REQUEST, - "Recovery request"); + NULL); /* Clean up */ free(bdb0); free(bdb1); } -/*****************************************************************************/ +int vbe_read_nvm(enum nvm_type type, uint8_t *buf, uint32_t size) +{ + /* Read NVM-RW contents (from EEPROM for example) */ + switch (type) { + case NVM_TYPE_RW_PRIMARY: + if (sizeof(nvmrw1) < size) + return -1; + memcpy(buf, nvmrw1, size); + break; + case NVM_TYPE_RW_SECONDARY: + if (sizeof(nvmrw2) < size) + return -1; + memcpy(buf, nvmrw2, size); + break; + default: + return -1; + } + return 0; +} + +int vbe_write_nvm(enum nvm_type type, void *buf, uint32_t size) +{ + if (vbe_write_nvm_failure > 0) { + fprintf(stderr, "Failed to write NVM (type=%d failure=%d)\n", + type, vbe_write_nvm_failure); + vbe_write_nvm_failure--; + return -1; + } + + /* Write NVM-RW contents (to EEPROM for example) */ + switch (type) { + case NVM_TYPE_RW_PRIMARY: + memcpy(nvmrw1, buf, size); + break; + case NVM_TYPE_RW_SECONDARY: + memcpy(nvmrw2, buf, size); + break; + default: + return -1; + } + return 0; +} + +static void install_nvm(enum nvm_type type, + uint32_t min_kernel_data_key_version, + uint32_t min_kernel_version, + uint32_t update_count) +{ + struct nvmrw nvm = { + .struct_magic = NVM_RW_MAGIC, + .struct_major_version = NVM_HEADER_VERSION_MAJOR, + .struct_minor_version = NVM_HEADER_VERSION_MINOR, + .struct_size = sizeof(struct nvmrw), + .min_kernel_data_key_version = min_kernel_data_key_version, + .min_kernel_version = min_kernel_version, + .update_count = update_count, + }; + + /* Compute HMAC */ + hmac(VB2_HASH_SHA256, secrets.nvm_rw, BDB_SECRET_SIZE, + &nvm, nvm.struct_size - sizeof(nvm.hmac), + nvm.hmac, sizeof(nvm.hmac)); + + /* Install NVM-RWs (in EEPROM for example) */ + switch (type) { + case NVM_TYPE_RW_PRIMARY: + memset(nvmrw1, 0, sizeof(nvmrw1)); + memcpy(nvmrw1, &nvm, sizeof(nvm)); + break; + case NVM_TYPE_RW_SECONDARY: + memset(nvmrw2, 0, sizeof(nvmrw2)); + memcpy(nvmrw2, &nvm, sizeof(nvm)); + break; + default: + fprintf(stderr, "Unsupported NVM type (%d)\n", type); + exit(2); + return; + } +} + +static void test_nvm_read(void) +{ + struct vba_context ctx = { + .bdb = NULL, + .ro_secrets = &secrets, + }; + struct nvmrw *nvm; + uint8_t nvmrw1_copy[NVM_RW_MAX_STRUCT_SIZE]; + uint8_t nvmrw2_copy[NVM_RW_MAX_STRUCT_SIZE]; + + install_nvm(NVM_TYPE_RW_PRIMARY, 0, 1, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 1, 0, 0); + memcpy(nvmrw1_copy, nvmrw1, sizeof(nvmrw1)); + memcpy(nvmrw2_copy, nvmrw2, sizeof(nvmrw2)); + + /* Test nvm_read: both good -> pick primary, no sync */ + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + TEST_SUCC(nvmrw_read(&ctx), NULL); + TEST_SUCC(memcmp(&ctx.nvmrw, nvmrw1, sizeof(*nvm)), NULL); + TEST_SUCC(memcmp(nvmrw1, nvmrw1_copy, sizeof(nvmrw1)), NULL); + TEST_SUCC(memcmp(nvmrw2, nvmrw2_copy, sizeof(nvmrw2)), NULL); + + /* Test nvm_read: primary bad -> pick secondary */ + install_nvm(NVM_TYPE_RW_PRIMARY, 0, 1, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 1, 0, 0); + memcpy(nvmrw2_copy, nvmrw2, sizeof(*nvm)); + nvm = (struct nvmrw *)nvmrw1; + nvm->hmac[0] ^= 0xff; + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + TEST_SUCC(nvmrw_read(&ctx), NULL); + TEST_SUCC(memcmp(&ctx.nvmrw, nvmrw2, sizeof(*nvm)), NULL); + TEST_SUCC(memcmp(nvmrw1, nvmrw2_copy, sizeof(nvmrw2)), NULL); + TEST_SUCC(memcmp(nvmrw2, nvmrw2_copy, sizeof(nvmrw2)), NULL); + + /* Test nvm_read: secondary bad -> pick primary */ + install_nvm(NVM_TYPE_RW_PRIMARY, 0, 1, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 1, 0, 0); + memcpy(nvmrw1_copy, nvmrw1, sizeof(*nvm)); + nvm = (struct nvmrw *)nvmrw2; + nvm->hmac[0] ^= 0xff; + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + TEST_SUCC(nvmrw_read(&ctx), NULL); + TEST_SUCC(memcmp(&ctx.nvmrw, nvmrw1, sizeof(*nvm)), NULL); + TEST_SUCC(memcmp(nvmrw1, nvmrw1_copy, sizeof(nvmrw1)), NULL); + TEST_SUCC(memcmp(nvmrw2, nvmrw1_copy, sizeof(nvmrw1)), NULL); + + /* Test nvm_read: both bad */ + nvm = (struct nvmrw *)nvmrw1; + nvm->hmac[0] ^= 0xff; + nvm = (struct nvmrw *)nvmrw2; + nvm->hmac[0] ^= 0xff; + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + TEST_EQ(nvmrw_read(&ctx), BDB_ERROR_NVM_RW_INVALID_HMAC, NULL); + + /* Test update count: secondary new -> pick secondary */ + install_nvm(NVM_TYPE_RW_PRIMARY, 0, 1, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 1, 0, 1); + memcpy(nvmrw2_copy, nvmrw2, sizeof(*nvm)); + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + TEST_SUCC(nvmrw_read(&ctx), NULL); + TEST_SUCC(memcmp(&ctx.nvmrw, nvmrw2, sizeof(*nvm)), NULL); + TEST_SUCC(memcmp(nvmrw1, nvmrw2_copy, sizeof(nvmrw1)), NULL); + TEST_SUCC(memcmp(nvmrw2, nvmrw2_copy, sizeof(nvmrw2)), NULL); + + /* Test old reader -> minor version downgrade */ + install_nvm(NVM_TYPE_RW_PRIMARY, 0, 1, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 1, 0, 1); + memset(&ctx.nvmrw, 0, sizeof(ctx.nvmrw)); + nvm = (struct nvmrw *)nvmrw1; + nvm->struct_minor_version++; + nvm->struct_size++; + TEST_SUCC(nvmrw_read(&ctx), NULL); + TEST_EQ(ctx.nvmrw.struct_minor_version, NVM_HEADER_VERSION_MINOR, NULL); + TEST_EQ(ctx.nvmrw.struct_size, sizeof(*nvm), NULL); +} + +static void verify_nvm_write(struct vba_context *ctx, + int expected_result) +{ + struct nvmrw *nvmrw; + struct nvmrw *nvm = &ctx->nvmrw; + + TEST_EQ(nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY), expected_result, NULL); + + if (expected_result != BDB_SUCCESS) + return; + + nvmrw = (struct nvmrw *)nvmrw1; + TEST_EQ(nvmrw->min_kernel_data_key_version, + nvm->min_kernel_data_key_version, NULL); + TEST_EQ(nvmrw->min_kernel_version, nvm->min_kernel_version, NULL); + TEST_EQ(nvmrw->update_count, nvm->update_count, NULL); +} + +static void test_nvm_write(void) +{ + struct vba_context ctx = { + .bdb = NULL, + .ro_secrets = &secrets, + }; + struct nvmrw nvm = { + .struct_magic = NVM_RW_MAGIC, + .struct_major_version = NVM_HEADER_VERSION_MAJOR, + .struct_minor_version = NVM_HEADER_VERSION_MINOR, + .struct_size = sizeof(struct nvmrw), + .min_kernel_data_key_version = 1, + .min_kernel_version = 2, + .update_count = 3, + }; + + /* Test normal case */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + vbe_write_nvm_failure = 0; + verify_nvm_write(&ctx, BDB_SUCCESS); + + /* Test write failure: once */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + vbe_write_nvm_failure = 1; + verify_nvm_write(&ctx, BDB_SUCCESS); + + /* Test write failure: twice */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + vbe_write_nvm_failure = 2; + verify_nvm_write(&ctx, BDB_ERROR_NVM_WRITE); + + /* Test invalid struct magic */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + ctx.nvmrw.struct_magic ^= 0xff; + verify_nvm_write(&ctx, BDB_ERROR_NVM_RW_MAGIC); + + /* Test struct size too small */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + ctx.nvmrw.struct_size = NVM_RW_MIN_STRUCT_SIZE - 1; + verify_nvm_write(&ctx, BDB_ERROR_NVM_STRUCT_SIZE); + + /* Test struct size too large */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + ctx.nvmrw.struct_size = NVM_RW_MAX_STRUCT_SIZE + 1; + verify_nvm_write(&ctx, BDB_ERROR_NVM_STRUCT_SIZE); + + /* Test invalid struct version */ + memcpy(&ctx.nvmrw, &nvm, sizeof(nvm)); + ctx.nvmrw.struct_major_version = NVM_HEADER_VERSION_MAJOR - 1; + verify_nvm_write(&ctx, BDB_ERROR_NVM_STRUCT_VERSION); + + vbe_write_nvm_failure = 0; +} + +static void verify_kernel_version(uint32_t min_kernel_data_key_version, + uint32_t new_kernel_data_key_version, + uint32_t min_kernel_version, + uint32_t new_kernel_version, + int expected_result) +{ + struct vba_context ctx = { + .bdb = NULL, + .ro_secrets = &secrets, + }; + struct nvmrw *nvm = (struct nvmrw *)nvmrw1; + uint32_t expected_kernel_data_key_version = min_kernel_data_key_version; + uint32_t expected_kernel_version = min_kernel_version; + int should_update = 0; + + if (min_kernel_data_key_version < new_kernel_data_key_version) { + expected_kernel_data_key_version = new_kernel_data_key_version; + should_update = 1; + } + if (min_kernel_version < new_kernel_version) { + expected_kernel_version = new_kernel_version; + should_update = 1; + } + + install_nvm(NVM_TYPE_RW_PRIMARY, min_kernel_data_key_version, + min_kernel_version, 0); + install_nvm(NVM_TYPE_RW_SECONDARY, 0, 0, 0); + + TEST_EQ(vba_update_kernel_version(&ctx, new_kernel_data_key_version, + new_kernel_version), + expected_result, NULL); + + if (expected_result != BDB_SUCCESS) + return; + + /* Check data key version */ + TEST_EQ(nvm->min_kernel_data_key_version, + expected_kernel_data_key_version, NULL); + /* Check kernel version */ + TEST_EQ(nvm->min_kernel_version, expected_kernel_version, NULL); + /* Check update_count */ + TEST_EQ(nvm->update_count, 0 + should_update, NULL); + /* Check sync if update is expected */ + if (should_update) + TEST_SUCC(memcmp(nvmrw2, nvmrw1, sizeof(nvmrw1)), NULL); +} + +static void test_update_kernel_version(void) +{ + /* Test update: data key version */ + verify_kernel_version(0, 1, 0, 0, BDB_SUCCESS); + /* Test update: kernel version */ + verify_kernel_version(0, 0, 0, 1, BDB_SUCCESS); + /* Test no update: data key version */ + verify_kernel_version(1, 0, 0, 0, BDB_SUCCESS); + /* Test no update: kernel version */ + verify_kernel_version(0, 0, 1, 0, BDB_SUCCESS); +} int main(int argc, char *argv[]) { @@ -266,6 +563,9 @@ int main(int argc, char *argv[]) printf("Running BDB SP-RW tests...\n"); test_verify_aprw(argv[1]); + test_nvm_read(); + test_nvm_write(); + test_update_kernel_version(); return gTestSuccess ? 0 : 255; } |