bdb: Add vba_bdb_init
authorDaisuke Nojiri <dnojiri@chromium.org>
Wed, 4 May 2016 21:55:57 +0000 (14:55 -0700)
committerchrome-bot <chrome-bot@chromium.org>
Sat, 7 May 2016 10:32:47 +0000 (03:32 -0700)
vba_bdb_init initializes the vboot context and decides what to do next
based on the vboot register content. Possible actions are:
1. proceed to verify the current slot
2. reset to try the other slot
3. reset to recovery mode

bdb_sprw_test demonstrates these actions.

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

Change-Id: If72cdd575d09b9162a871f088064ca853b7fd74d
Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/342604
Reviewed-by: Randall Spangler <rspangler@chromium.org>
Makefile
firmware/bdb/bdb.h
firmware/bdb/bdb_api.h [new file with mode: 0644]
firmware/bdb/misc.c [new file with mode: 0644]
firmware/bdb/stub.c [new file with mode: 0644]
tests/bdb_sprw_test.c [new file with mode: 0644]

index dd28834..900214f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -374,7 +374,9 @@ FWLIB21_SRCS = \
 
 BDBLIB_SRCS = \
        firmware/bdb/bdb.c \
-       firmware/bdb/rsa.c
+       firmware/bdb/misc.c \
+       firmware/bdb/rsa.c \
+       firmware/bdb/stub.c
 
 # Support real TPM unless BIOS sets MOCK_TPM
 ifeq (${MOCK_TPM},)
@@ -781,7 +783,8 @@ TEST21_NAMES = \
        tests/vb21_host_sig_tests
 
 TESTBDB_NAMES = \
-       tests/bdb_test
+       tests/bdb_test \
+       tests/bdb_sprw_test
 
 TEST_NAMES += ${TEST2X_NAMES} ${TEST20_NAMES} ${TEST21_NAMES} ${TESTBDB_NAMES}
 
@@ -1259,6 +1262,7 @@ ${BUILD}/tests/vb20_common2_tests: LDLIBS += ${CRYPTO_LIBS}
 ${BUILD}/tests/vb20_common3_tests: LDLIBS += ${CRYPTO_LIBS}
 ${BUILD}/tests/verify_kernel: LDLIBS += ${CRYPTO_LIBS}
 ${BUILD}/tests/bdb_test: LDLIBS += ${CRYPTO_LIBS}
+${BUILD}/tests/bdb_sprw_test: LDLIBS += ${CRYPTO_LIBS}
 
 ${TEST21_BINS}: LDLIBS += ${CRYPTO_LIBS}
 
@@ -1464,6 +1468,7 @@ run2tests: test_setup
 .PHONY: runbdbtests
 runbdbtests: test_setup
        ${RUNTEST} ${BUILD_RUN}/tests/bdb_test ${TEST_KEYS}
+       ${RUNTEST} ${BUILD_RUN}/tests/bdb_sprw_test ${TEST_KEYS}
 
 .PHONY: runfutiltests
 runfutiltests: test_setup
index 9183491..30ecc17 100644 (file)
@@ -68,6 +68,10 @@ enum bdb_return_code {
        /* Other errors in bdb_verify() */
        BDB_ERROR_DIGEST,       /* Error calculating digest */
        BDB_ERROR_VERIFY_SIG,   /* Error verifying signature */
+
+       /* Errors in vba_bdb_init */
+       BDB_ERROR_TRY_OTHER_SLOT,
+       BDB_ERROR_RECOVERY_REQUEST,
 };
 
 /*****************************************************************************/
diff --git a/firmware/bdb/bdb_api.h b/firmware/bdb/bdb_api.h
new file mode 100644 (file)
index 0000000..53823fa
--- /dev/null
@@ -0,0 +1,68 @@
+/* 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_BDB_API_H
+#define VBOOT_REFERENCE_FIRMWARE_BDB_BDB_API_H
+
+#include <stdint.h>
+#include "vboot_register.h"
+
+struct vba_context {
+       /* Indicate which slot is being tried: 0 - primary, 1 - secondary */
+       uint8_t slot;
+};
+
+/**
+ * Initialize vboot process
+ *
+ * @param ctx
+ * @return     enum bdb_return_code
+ */
+int vba_bdb_init(struct vba_context *ctx);
+
+/**
+ * Finalize vboot process
+ *
+ * @param ctx
+ * @return     enum bdb_return_code
+ */
+int vba_bdb_finalize(struct vba_context *ctx);
+
+/**
+ * Log failed boot attempt and reset the chip
+ *
+ * @param ctx
+ */
+void vba_bdb_fail(struct vba_context *ctx);
+
+/**
+ * Get vboot register value
+ *
+ * Implemented by each chip
+ *
+ * @param type Type of register to get
+ * @return     Register value
+ */
+uint32_t vbe_get_vboot_register(enum vboot_register type);
+
+/**
+ * Set vboot register value
+ *
+ * Implemented by each chip
+ *
+ * @param type Type of register to set
+ * @param val  Value to set
+ */
+void vbe_set_vboot_register(enum vboot_register type, uint32_t val);
+
+/**
+ * Reset the SoC
+ *
+ * Implemented by each chip. This is different from reboot (a.k.a. board reset,
+ * cold reset).
+ */
+void vbe_reset(void);
+
+#endif
diff --git a/firmware/bdb/misc.c b/firmware/bdb/misc.c
new file mode 100644 (file)
index 0000000..fd3e0c9
--- /dev/null
@@ -0,0 +1,124 @@
+/* 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 <stdint.h>
+#include "bdb.h"
+#include "bdb_api.h"
+#include "vboot_register.h"
+
+static int did_current_slot_fail(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       if (ctx->slot)
+               return val & VBOOT_REGISTER_FAILED_RW_SECONDARY;
+       else
+               return val & VBOOT_REGISTER_FAILED_RW_PRIMARY;
+}
+
+static int did_other_slot_fail(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       if (ctx->slot)
+               return val & VBOOT_REGISTER_FAILED_RW_PRIMARY;
+       else
+               return val & VBOOT_REGISTER_FAILED_RW_SECONDARY;
+}
+
+static void set_try_other_slot(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       if (ctx->slot)
+               val &= ~VBOOT_REGISTER_TRY_SECONDARY_BDB;
+       else
+               val |= VBOOT_REGISTER_TRY_SECONDARY_BDB;
+
+       vbe_set_vboot_register(VBOOT_REGISTER_PERSIST, val);
+}
+
+static void set_recovery_request(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       val |= VBOOT_REGISTER_RECOVERY_REQUEST;
+
+       vbe_set_vboot_register(VBOOT_REGISTER_PERSIST, val);
+}
+
+static void get_current_slot(struct vba_context *ctx)
+{
+       /* Assume SP-RO selects slot this way */
+       ctx->slot = (vbe_get_vboot_register(VBOOT_REGISTER_PERSIST)
+                       & VBOOT_REGISTER_TRY_SECONDARY_BDB) ? 1 : 0;
+}
+
+static void set_current_slot_failed(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       if (ctx->slot)
+               val |= VBOOT_REGISTER_FAILED_RW_SECONDARY;
+       else
+               val |= VBOOT_REGISTER_FAILED_RW_PRIMARY;
+
+       vbe_set_vboot_register(VBOOT_REGISTER_PERSIST, val);
+}
+
+static void unset_current_slot_failed(struct vba_context *ctx)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       if (ctx->slot)
+               val &= ~VBOOT_REGISTER_FAILED_RW_SECONDARY;
+       else
+               val &= ~VBOOT_REGISTER_FAILED_RW_PRIMARY;
+
+       vbe_set_vboot_register(VBOOT_REGISTER_PERSIST, val);
+}
+
+int vba_bdb_init(struct vba_context *ctx)
+{
+       /* Get current slot */
+       get_current_slot(ctx);
+
+       /* Check current slot failed or not at the last boot */
+       if (!did_current_slot_fail(ctx)) {
+               /* If not, we try this slot. Prepare for any accidents */
+               set_current_slot_failed(ctx);
+               return BDB_SUCCESS;
+       }
+
+       /* Check other slot failed or not at the previous boot */
+       if (!did_other_slot_fail(ctx)) {
+               /* If not, we try the other slot after reboot. */
+               set_try_other_slot(ctx);
+               return BDB_ERROR_TRY_OTHER_SLOT;
+       } else {
+               /* Otherwise, both slots are bad. Reboot to recovery */
+               set_recovery_request(ctx);
+               return BDB_ERROR_RECOVERY_REQUEST;
+       }
+}
+
+int vba_bdb_finalize(struct vba_context *ctx)
+{
+       /* Mark the current slot good */
+       unset_current_slot_failed(ctx);
+
+       /* Disable NVM bus */
+
+       return BDB_SUCCESS;
+}
+
+void vba_bdb_fail(struct vba_context *ctx)
+{
+       /* We can do some logging here if we want */
+
+       /* Unconditionally reboot. FailedRW flag is already set.
+        * At the next boot, bdb_init will decide what to do. */
+       vbe_reset();
+}
diff --git a/firmware/bdb/stub.c b/firmware/bdb/stub.c
new file mode 100644 (file)
index 0000000..87efbc3
--- /dev/null
@@ -0,0 +1,24 @@
+/* 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 "bdb_api.h"
+
+__attribute__((weak))
+uint32_t vbe_get_vboot_register(enum vboot_register type)
+{
+       return 0;
+}
+
+__attribute__((weak))
+void vbe_set_vboot_register(enum vboot_register type, uint32_t val)
+{
+       return;
+}
+
+__attribute__((weak))
+void vbe_reset(void)
+{
+       return;
+}
diff --git a/tests/bdb_sprw_test.c b/tests/bdb_sprw_test.c
new file mode 100644 (file)
index 0000000..995707d
--- /dev/null
@@ -0,0 +1,273 @@
+/* Copyright 2015 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.
+ *
+ * Unit tests
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "2sha.h"
+#include "bdb.h"
+#include "bdb_api.h"
+#include "bdb_struct.h"
+#include "host.h"
+#include "test_common.h"
+#include "vboot_register.h"
+
+#define TEST_EQ_S(result, expect) TEST_EQ(result, expect, #result "==" #expect)
+
+static struct bdb_header *bdb, *bdb0, *bdb1;
+static uint32_t vboot_register;
+static uint32_t vboot_register_persist;
+static char slot_selected;
+static uint8_t aprw_digest[BDB_SHA256_DIGEST_SIZE];
+static uint8_t reset_count;
+
+static struct bdb_header *create_bdb(const char *key_dir,
+                                    struct bdb_hash *hash, int num_hashes)
+{
+       struct bdb_header *b;
+       uint8_t oem_area_0[32] = "Some OEM area.";
+       uint8_t oem_area_1[64] = "Some other OEM area.";
+       char filename[1024];
+
+       struct bdb_create_params p = {
+               .bdb_load_address = 0x11223344,
+               .oem_area_0 = oem_area_0,
+               .oem_area_0_size = sizeof(oem_area_0),
+               .oem_area_1 = oem_area_1,
+               .oem_area_1_size = sizeof(oem_area_1),
+               .header_sig_description = "The header sig",
+               .data_sig_description = "The data sig",
+               .data_description = "Test BDB data",
+               .data_version = 3,
+               .hash = hash,
+               .num_hashes = num_hashes,
+       };
+
+       uint8_t bdbkey_digest[BDB_SHA256_DIGEST_SIZE];
+
+       /* Load keys */
+       sprintf(filename, "%s/bdbkey.keyb", key_dir);
+       p.bdbkey = bdb_create_key(filename, 100, "BDB key");
+       sprintf(filename, "%s/datakey.keyb", key_dir);
+       p.datakey = bdb_create_key(filename, 200, "datakey");
+       sprintf(filename, "%s/bdbkey.pem", key_dir);
+       p.private_bdbkey = read_pem(filename);
+       sprintf(filename, "%s/datakey.pem", key_dir);
+       p.private_datakey = read_pem(filename);
+       if (!p.bdbkey || !p.datakey || !p.private_bdbkey || !p.private_datakey) {
+               fprintf(stderr, "Unable to load test keys\n");
+               exit(2);
+       }
+
+       vb2_digest_buffer((uint8_t *)p.bdbkey, p.bdbkey->struct_size,
+                         VB2_HASH_SHA256,
+                         bdbkey_digest, BDB_SHA256_DIGEST_SIZE);
+
+       b = bdb_create(&p);
+       if (!b) {
+               fprintf(stderr, "Unable to create test BDB\n");
+               exit(2);
+       }
+
+       /* Free keys and buffers */
+       free(p.bdbkey);
+       free(p.datakey);
+       RSA_free(p.private_bdbkey);
+       RSA_free(p.private_datakey);
+
+       return b;
+}
+
+static void calculate_aprw_digest(const struct bdb_hash *hash, uint8_t *digest)
+{
+       /* Locate AP-RW */
+       /* Calculate digest as loading AP-RW */
+       memcpy(digest, aprw_digest, sizeof(aprw_digest));
+}
+
+static void verstage_main(void)
+{
+       struct vba_context ctx;
+       const struct bdb_hash *hash;
+       uint8_t digest[BDB_SHA256_DIGEST_SIZE];
+       int rv;
+
+       rv = vba_bdb_init(&ctx);
+       if (rv) {
+               fprintf(stderr, "Initializing context failed for (%d)\n", rv);
+               vba_bdb_fail(&ctx);
+               /* This return is needed for unit test. vba_bdb_fail calls
+                * vbe_reset, which calls verstage_main. If verstage_main
+                * successfully returns, we return here as well. */
+               return;
+       }
+       fprintf(stderr, "Initialized context. Trying slot %c\n",
+               ctx.slot ? 'B' : 'A');
+
+       /* 1. Locate BDB */
+
+       /* 2. Get bdb_hash structure for AP-RW */
+       hash = bdb_get_hash(bdb, BDB_DATA_AP_RW);
+       fprintf(stderr, "Got hash of AP-RW\n");
+
+       /* 3. Load & calculate digest of AP-RW */
+       calculate_aprw_digest(hash, digest);
+       fprintf(stderr, "Calculated digest\n");
+
+       /* 4. Compare digests */
+       if (memcmp(hash->digest, digest, BDB_SHA256_DIGEST_SIZE)) {
+               fprintf(stderr, "Digests do not match\n");
+               vba_bdb_fail(&ctx);
+               /* This return is needed for unit test. vba_bdb_fail calls
+                * vbe_reset, which calls verstage_main. If verstage_main
+                * successfully returns, we return here as well. */
+               return;
+       }
+
+       /* 5. Record selected slot. This depends on the firmware */
+       slot_selected = ctx.slot ? 'B' : 'A';
+       fprintf(stderr, "Selected AP-RW in slot %c\n", slot_selected);
+
+       /* X. This should be done upon AP-RW's request after everything is
+        * successful. We do it here for the unit test. */
+       vba_bdb_finalize(&ctx);
+}
+
+uint32_t vbe_get_vboot_register(enum vboot_register type)
+{
+       switch (type) {
+       case VBOOT_REGISTER:
+               return vboot_register;
+       case VBOOT_REGISTER_PERSIST:
+               return vboot_register_persist;
+       default:
+               fprintf(stderr, "Invalid vboot register type (%d)\n", type);
+               exit(2);
+       }
+}
+
+void vbe_set_vboot_register(enum vboot_register type, uint32_t val)
+{
+       switch (type) {
+       case VBOOT_REGISTER:
+               vboot_register = val;
+               break;
+       case VBOOT_REGISTER_PERSIST:
+               vboot_register_persist = val;
+               break;
+       default:
+               fprintf(stderr, "Invalid vboot register type (%d)\n", type);
+               exit(2);
+       }
+}
+
+void vbe_reset(void)
+{
+       uint32_t val = vbe_get_vboot_register(VBOOT_REGISTER_PERSIST);
+
+       fprintf(stderr, "Booting ...\n");
+
+       if (++reset_count > 5) {
+               fprintf(stderr, "Reset counter exceeded maximum value\n");
+               exit(2);
+       }
+
+       /* Emulate warm reset */
+       vboot_register = 0;
+       if (val & VBOOT_REGISTER_RECOVERY_REQUEST) {
+               fprintf(stderr, "Recovery requested\n");
+               return;
+       }
+       /* Selected by SP-RO */
+       bdb = (val & VBOOT_REGISTER_TRY_SECONDARY_BDB) ? bdb1 : bdb0;
+       verstage_main();
+}
+
+static void test_verify_aprw(const char *key_dir)
+{
+       struct bdb_hash hash0 = {
+               .offset = 0x28000,
+               .size = 0x20000,
+               .partition = 1,
+               .type = BDB_DATA_AP_RW,
+               .load_address = 0x200000,
+               .digest = {0x11, 0x11, 0x11, 0x11},
+       };
+       struct bdb_hash hash1 = {
+               .offset = 0x28000,
+               .size = 0x20000,
+               .partition = 1,
+               .type = BDB_DATA_AP_RW,
+               .load_address = 0x200000,
+               .digest = {0x22, 0x22, 0x22, 0x22},
+       };
+
+       bdb0 = create_bdb(key_dir, &hash0, 1);
+       bdb1 = create_bdb(key_dir, &hash1, 1);
+       memset(aprw_digest, 0, BDB_SHA256_DIGEST_SIZE);
+
+       /* (slotA, slotB) = (good, bad) */
+       reset_count = 0;
+       vboot_register_persist = 0;
+       slot_selected = 'X';
+       memcpy(aprw_digest, hash0.digest, 4);
+       vbe_reset();
+       TEST_EQ_S(reset_count, 1);
+       TEST_EQ_S(slot_selected, 'A');
+       TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY,
+                  "VBOOT_REGISTER_FAILED_RW_PRIMARY==false");
+       TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY,
+                  "VBOOT_REGISTER_FAILED_RW_SECONDARY==false");
+
+       /* (slotA, slotB) = (bad, good) */
+       reset_count = 0;
+       vboot_register_persist = 0;
+       slot_selected = 'X';
+       memcpy(aprw_digest, hash1.digest, 4);
+       vbe_reset();
+       TEST_EQ_S(reset_count, 3);
+       TEST_EQ_S(slot_selected, 'B');
+       TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY,
+                 "VBOOT_REGISTER_FAILED_RW_PRIMARY==true");
+       TEST_FALSE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY,
+                  "VBOOT_REGISTER_FAILED_RW_SECONDARY==false");
+
+       /* (slotA, slotB) = (bad, bad) */
+       reset_count = 0;
+       vboot_register_persist = 0;
+       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_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_PRIMARY,
+                 "VBOOT_REGISTER_FAILED_RW_PRIMARY==true");
+       TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_FAILED_RW_SECONDARY,
+                 "VBOOT_REGISTER_FAILED_RW_SECONDARY==true");
+       TEST_TRUE(vboot_register_persist & VBOOT_REGISTER_RECOVERY_REQUEST,
+                 "Recovery request");
+
+       /* Clean up */
+       free(bdb0);
+       free(bdb1);
+}
+
+/*****************************************************************************/
+
+int main(int argc, char *argv[])
+{
+       if (argc != 2) {
+               fprintf(stderr, "Usage: %s <keys_dir>", argv[0]);
+               return -1;
+       }
+       printf("Running BDB SP-RW tests...\n");
+
+       test_verify_aprw(argv[1]);
+
+       return gTestSuccess ? 0 : 255;
+}