diff options
Diffstat (limited to 'firmware/bdb/nvm.c')
-rw-r--r-- | firmware/bdb/nvm.c | 231 |
1 files changed, 231 insertions, 0 deletions
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; +} |