bdb: Add NVM library
authorDaisuke Nojiri <dnojiri@chromium.org>
Fri, 6 May 2016 19:32:38 +0000 (12:32 -0700)
committerchrome-bot <chrome-bot@chromium.org>
Sat, 21 May 2016 03:49:34 +0000 (20:49 -0700)
This patch adds NVM library, which verifies, updates, and syncs NVM-RW of
vboot SoC.

BUG=chrome-os-partner:51907
BRANCH=tot
TEST=make runtests

Change-Id: I5adc399f9e582bd9ea7d9ee73482ed9a924837e0
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/343121
Reviewed-by: Daisuke Nojiri <dnojiri@google.com>
Makefile
firmware/bdb/bdb.h
firmware/bdb/bdb_api.h
firmware/bdb/nvm.c [new file with mode: 0644]
firmware/bdb/nvm.h [new file with mode: 0644]
firmware/bdb/secrets.h [new file with mode: 0644]
firmware/bdb/stub.c
tests/bdb_sprw_test.c

index 8989147..e0b4193 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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"
index 30ecc17..9f3b9c6 100644 (file)
@@ -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,
 };
 
 /*****************************************************************************/
index 53823fa..9979824 100644 (file)
@@ -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 (file)
index 0000000..b5c53af
--- /dev/null
@@ -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 (file)
index 0000000..3de1a2d
--- /dev/null
@@ -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 (file)
index 0000000..e26e97c
--- /dev/null
@@ -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
index 87efbc3..58a0096 100644 (file)
@@ -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;
+}
index 4528751..06739ae 100644 (file)
 #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;
 }