summaryrefslogtreecommitdiff
path: root/firmware/bdb/nvm.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/bdb/nvm.c')
-rw-r--r--firmware/bdb/nvm.c231
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;
+}