summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFurquan Shaikh <furquan@google.com>2015-05-21 14:39:11 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-05-27 23:18:43 +0000
commitf27436032668e332b4ed7cc168bbcc2a939ae7b9 (patch)
tree89e269aa24e47bb0c102606b4c96992e80d3d87e
parent05371345b75454c20a12433384a0d5dc29064f07 (diff)
downloadvboot-f27436032668e332b4ed7cc168bbcc2a939ae7b9.tar.gz
fastboot: Add routine for verifying kernel image loaded in memory
This API allows fastboot boot from memory command to verify that the image loaded in memory is signed properly using recovery keys. Thus, only officially signed recovery images can be booted using fastboot boot command in recovery mode. However, if GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP is set, then this routine will not perform any check and return okay for any image sent by fastboot boot. BUG=chrome-os-partner:40196 BRANCH=None TEST=Compiles successfully. With GBB override for FASTBOOT_FULL_CAP set any signed image is allowed to boot. With FASTBOOT_FULL_CAP not set, then only officially signed image is allowed to boot. (make -j runtests successful) Change-Id: I78028853bd1ad09d3c610a687f327560557d5681 Signed-off-by: Furquan Shaikh <furquan@google.com> Reviewed-on: https://chromium-review.googlesource.com/272696 Reviewed-by: Randall Spangler <rspangler@chromium.org> Commit-Queue: Furquan Shaikh <furquan@chromium.org> Trybot-Ready: Furquan Shaikh <furquan@chromium.org> Tested-by: Furquan Shaikh <furquan@chromium.org>
-rw-r--r--Makefile2
-rw-r--r--firmware/include/vboot_api.h21
-rw-r--r--firmware/lib/vboot_api_kernel.c136
-rw-r--r--tests/vboot_api_kernel5_tests.c281
4 files changed, 440 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 2d9ab423..23916256 100644
--- a/Makefile
+++ b/Makefile
@@ -685,6 +685,7 @@ TEST_NAMES = \
tests/vboot_api_kernel2_tests \
tests/vboot_api_kernel3_tests \
tests/vboot_api_kernel4_tests \
+ tests/vboot_api_kernel5_tests \
tests/vboot_audio_tests \
tests/vboot_common_tests \
tests/vboot_common2_tests \
@@ -1363,6 +1364,7 @@ runmisctests: test_setup
${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel2_tests
${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel3_tests
${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel4_tests
+ ${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel5_tests
${RUNTEST} ${BUILD_RUN}/tests/vboot_audio_tests
${RUNTEST} ${BUILD_RUN}/tests/vboot_common_tests
${RUNTEST} ${BUILD_RUN}/tests/vboot_common2_tests ${TEST_KEYS}
diff --git a/firmware/include/vboot_api.h b/firmware/include/vboot_api.h
index a818a7ca..e66a2fba 100644
--- a/firmware/include/vboot_api.h
+++ b/firmware/include/vboot_api.h
@@ -1004,4 +1004,25 @@ VbError_t VbExRegionRead(VbCommonParams *cparams,
enum vb_firmware_region region, uint32_t offset,
uint32_t size, void *buf);
+/**
+ * Verify Kernel Image loaded in memory.
+ *
+ * This routine is used by fastboot boot command to verify the kernel image in
+ * memory sent by the host device using fastboot protocol. It checks if the
+ * image in memory is signed using official recovery keys. In case of GBB
+ * override to allow full fastboot functionality, it checks image integrity, but
+ * does not check the image signature.
+ *
+ * @param cparams Common parameters, e.g. use member caller_context
+ * to point to useful context data
+ * @param kparams kernel params
+ * @param boot_image Image in memory that needs to be verified
+ * @param image_size Size of the image in memory
+ * @return VBERROR_... error, VBERROR_SUCCESS on success.
+ */
+VbError_t VbVerifyMemoryBootImage(VbCommonParams *cparams,
+ VbSelectAndLoadKernelParams *kparams,
+ void *boot_image,
+ size_t image_size);
+
#endif /* VBOOT_REFERENCE_VBOOT_API_H_ */
diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c
index fc6df72e..f3ff3ea1 100644
--- a/firmware/lib/vboot_api_kernel.c
+++ b/firmware/lib/vboot_api_kernel.c
@@ -1194,3 +1194,139 @@ VbError_t VbSelectAndLoadKernel(VbCommonParams *cparams,
/* Pass through return value from boot path */
return retval;
}
+
+VbError_t VbVerifyMemoryBootImage(VbCommonParams *cparams,
+ VbSelectAndLoadKernelParams *kparams,
+ void *boot_image,
+ size_t image_size)
+{
+ VbError_t retval;
+ VbPublicKey* kernel_subkey = NULL;
+ uint8_t *kbuf;
+ VbKeyBlockHeader *key_block;
+ VbSharedDataHeader *shared =
+ (VbSharedDataHeader *)cparams->shared_data_blob;
+ RSAPublicKey *data_key = NULL;
+ VbKernelPreambleHeader *preamble;
+ uint64_t body_offset;
+ int hash_only = 0;
+ int dev_switch;
+
+ if ((boot_image == NULL) || (image_size == 0))
+ return VBERROR_INVALID_PARAMETER;
+
+ /* Clear output params in case we fail. */
+ kparams->disk_handle = NULL;
+ kparams->partition_number = 0;
+ kparams->bootloader_address = 0;
+ kparams->bootloader_size = 0;
+ kparams->flags = 0;
+ Memset(kparams->partition_guid, 0, sizeof(kparams->partition_guid));
+
+ /* Populate pointers to all components in the image. */
+ kbuf = boot_image;
+ key_block = (VbKeyBlockHeader *)kbuf;
+ preamble = (VbKernelPreambleHeader *)(kbuf + key_block->key_block_size);
+ body_offset = key_block->key_block_size + preamble->preamble_size;
+
+ /* Read GBB Header */
+ cparams->bmp = NULL;
+ cparams->gbb = VbExMalloc(sizeof(*cparams->gbb));
+ retval = VbGbbReadHeader_static(cparams, cparams->gbb);
+ if (VBERROR_SUCCESS != retval) {
+ VBDEBUG(("Gbb read header failed.\n"));
+ return retval;
+ }
+
+ /*
+ * We don't care verifying the image if:
+ * 1. dev-mode switch is on and
+ * 2. GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP is set.
+ *
+ * Check only the integrity of the image.
+ */
+ dev_switch = shared->flags & VBSD_BOOT_DEV_SWITCH_ON;
+ if (dev_switch && (cparams->gbb->flags &
+ GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP)) {
+ VBDEBUG(("Only performing integrity-check.\n"));
+ hash_only = 1;
+ } else {
+ /* Get recovery key. */
+ retval = VbGbbReadRecoveryKey(cparams, &kernel_subkey);
+ if (VBERROR_SUCCESS != retval) {
+ VBDEBUG(("Gbb Read Recovery key failed.\n"));
+ return retval;
+ }
+ }
+
+ /* If we fail at any step, retval returned would be invalid kernel. */
+ retval = VBERROR_INVALID_KERNEL_FOUND;
+
+ /* Verify the key block. */
+ if (0 != KeyBlockVerify(key_block, image_size, kernel_subkey,
+ hash_only)) {
+ VBDEBUG(("Verifying key block signature/hash failed.\n"));
+ goto fail;
+ }
+
+ /* Check the key block flags against the current boot mode. */
+ if (!(key_block->key_block_flags &
+ (dev_switch ? KEY_BLOCK_FLAG_DEVELOPER_1 :
+ KEY_BLOCK_FLAG_DEVELOPER_0))) {
+ VBDEBUG(("Key block developer flag mismatch.\n"));
+ if (hash_only == 0)
+ goto fail;
+ }
+
+ if (!(key_block->key_block_flags & KEY_BLOCK_FLAG_RECOVERY_1)) {
+ VBDEBUG(("Key block recovery flag mismatch.\n"));
+ if (hash_only == 0)
+ goto fail;
+ }
+
+ /* Get key for preamble/data verification from the key block. */
+ data_key = PublicKeyToRSA(&key_block->data_key);
+ if (!data_key) {
+ VBDEBUG(("Data key bad.\n"));
+ goto fail;
+ }
+
+ /* Verify the preamble, which follows the key block */
+ if ((0 != VerifyKernelPreamble(preamble,
+ image_size -
+ key_block->key_block_size,
+ data_key))) {
+ VBDEBUG(("Preamble verification failed.\n"));
+ goto fail;
+ }
+
+ VBDEBUG(("Kernel preamble is good.\n"));
+
+ /* Verify kernel data */
+ if (0 != VerifyData((const uint8_t *)(kbuf + body_offset),
+ image_size - body_offset,
+ &preamble->body_signature, data_key)) {
+ VBDEBUG(("Kernel data verification failed.\n"));
+ goto fail;
+ }
+
+ VBDEBUG(("Kernel is good.\n"));
+
+ /* Fill in output parameters. */
+ kparams->kernel_buffer = kbuf + body_offset;
+ kparams->kernel_buffer_size = image_size - body_offset;
+ kparams->bootloader_address = preamble->bootloader_address;
+ kparams->bootloader_size = preamble->bootloader_size;
+ if (VbKernelHasFlags(preamble) == VBOOT_SUCCESS)
+ kparams->flags = preamble->flags;
+
+ retval = VBERROR_SUCCESS;
+
+fail:
+ VbApiKernelFree(cparams);
+ if (NULL != data_key)
+ RSAPublicKeyFree(data_key);
+ if (NULL != kernel_subkey)
+ VbExFree(kernel_subkey);
+ return retval;
+}
diff --git a/tests/vboot_api_kernel5_tests.c b/tests/vboot_api_kernel5_tests.c
new file mode 100644
index 00000000..8c59622f
--- /dev/null
+++ b/tests/vboot_api_kernel5_tests.c
@@ -0,0 +1,281 @@
+/* Copyright (c) 2013 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.
+ *
+ * Tests for vboot_api_kernel.c
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gbb_header.h"
+#include "host_common.h"
+#include "load_kernel_fw.h"
+#include "test_common.h"
+#include "vboot_api.h"
+#include "vboot_common.h"
+#include "vboot_kernel.h"
+#include "vboot_nvstorage.h"
+#include "vboot_struct.h"
+
+/* Mock data */
+static VbCommonParams cparams;
+static VbSelectAndLoadKernelParams kparams;
+static uint8_t shared_data[VB_SHARED_DATA_MIN_SIZE];
+static VbSharedDataHeader *shared = (VbSharedDataHeader *)shared_data;
+static GoogleBinaryBlockHeader gbb;
+
+static uint8_t kernel_buffer[80000];
+static int key_block_verify_fail; /* 0=ok, 1=sig, 2=hash */
+static int preamble_verify_fail;
+static int verify_data_fail;
+static RSAPublicKey *mock_data_key;
+static int mock_data_key_allocated;
+
+static VbNvContext vnc;
+static VbKeyBlockHeader kbh;
+static VbKernelPreambleHeader kph;
+
+static int hash_only_check;
+
+/**
+ * Reset mock data (for use before each test)
+ */
+static void ResetMocks(void)
+{
+ Memset(&cparams, 0, sizeof(cparams));
+ cparams.shared_data_size = sizeof(shared_data);
+ cparams.shared_data_blob = shared_data;
+ cparams.gbb_data = &gbb;
+ cparams.gbb_size = sizeof(gbb);
+
+ Memset(&kparams, 0, sizeof(kparams));
+
+ Memset(&gbb, 0, sizeof(gbb));
+ gbb.major_version = GBB_MAJOR_VER;
+ gbb.minor_version = GBB_MINOR_VER;
+ gbb.flags = 0;
+
+ memset(&vnc, 0, sizeof(vnc));
+ VbNvSetup(&vnc);
+ VbNvTeardown(&vnc); /* So CRC gets generated */
+
+ memset(&shared_data, 0, sizeof(shared_data));
+ VbSharedDataInit(shared, sizeof(shared_data));
+
+ key_block_verify_fail = 0;
+ preamble_verify_fail = 0;
+ verify_data_fail = 0;
+
+ mock_data_key = (RSAPublicKey *)"TestDataKey";
+ mock_data_key_allocated = 0;
+
+ memset(&kbh, 0, sizeof(kbh));
+ kbh.data_key.key_version = 2;
+ kbh.key_block_flags = -1;
+ kbh.key_block_size = sizeof(kbh);
+
+ memset(&kph, 0, sizeof(kph));
+ kph.kernel_version = 1;
+ kph.preamble_size = 4096 - kbh.key_block_size;
+ kph.body_signature.data_size = 70144;
+ kph.bootloader_address = 0xbeadd008;
+ kph.bootloader_size = 0x1234;
+
+ memcpy(kernel_buffer, &kbh, sizeof(kbh));
+ memcpy((kernel_buffer + kbh.key_block_size), &kph, sizeof(kph));
+
+ hash_only_check = -1;
+}
+
+static void copy_kbh(void)
+{
+ memcpy(kernel_buffer, &kbh, sizeof(kbh));
+}
+
+/* Mocks */
+int KeyBlockVerify(const VbKeyBlockHeader *block, uint64_t size,
+ const VbPublicKey *key, int hash_only) {
+ hash_only_check = hash_only;
+
+ if (key_block_verify_fail)
+ return VBERROR_SIMULATED;
+
+ /* Use this as an opportunity to override the key block */
+ memcpy((void *)block, &kbh, sizeof(kbh));
+ return VBERROR_SUCCESS;
+}
+
+RSAPublicKey *PublicKeyToRSA(const VbPublicKey *key)
+{
+ TEST_EQ(mock_data_key_allocated, 0, " mock data key not allocated");
+
+ if (mock_data_key)
+ mock_data_key_allocated++;
+
+ return mock_data_key;
+}
+
+void RSAPublicKeyFree(RSAPublicKey* key)
+{
+ TEST_EQ(mock_data_key_allocated, 1, " mock data key allocated");
+ TEST_PTR_EQ(key, mock_data_key, " data key ptr");
+ mock_data_key_allocated--;
+}
+
+int VerifyKernelPreamble(const VbKernelPreambleHeader *preamble,
+ uint64_t size, const RSAPublicKey *key)
+{
+ if (preamble_verify_fail)
+ return VBERROR_SIMULATED;
+
+ /* Use this as an opportunity to override the preamble */
+ memcpy((void *)preamble, &kph, sizeof(kph));
+ return VBERROR_SUCCESS;
+}
+
+int VerifyData(const uint8_t *data, uint64_t size, const VbSignature *sig,
+ const RSAPublicKey *key)
+{
+ if (verify_data_fail)
+ return VBERROR_SIMULATED;
+
+ return VBERROR_SUCCESS;
+}
+
+static void VerifyMemoryBootImageTest(void)
+{
+ uint32_t u;
+
+ int kernel_body_offset;
+ int kernel_body_size;
+ uintptr_t kernel_body_start;
+ size_t kernel_buffer_size = sizeof(kernel_buffer);
+
+ ResetMocks();
+
+ kernel_body_offset = kbh.key_block_size + kph.preamble_size;
+ kernel_body_size = sizeof(kernel_buffer) - kernel_body_offset;
+ kernel_body_start = (uintptr_t)kernel_buffer + kernel_body_offset;
+
+ u = VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size);
+ TEST_EQ(u, 0, "Image good");
+ TEST_EQ(kparams.partition_number, 0, " part num");
+ TEST_EQ(kparams.bootloader_address, 0xbeadd008, " bootloader addr");
+ TEST_EQ(kparams.bootloader_size, 0x1234, " bootloader size");
+ TEST_PTR_EQ(kparams.kernel_buffer, (void *)(kernel_body_start),
+ " kernel buffer");
+ TEST_EQ(kparams.kernel_buffer_size, kernel_body_size,
+ " kernel buffer size");
+
+ /* Empty image buffer. */
+ ResetMocks();
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, NULL,
+ kernel_buffer_size),
+ VBERROR_INVALID_PARAMETER, "Empty image");
+
+ /* Illegal image size. */
+ ResetMocks();
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer, 0),
+ VBERROR_INVALID_PARAMETER, "Illegal image size");
+
+ /* Key Block Verification Failure */
+ ResetMocks();
+ key_block_verify_fail = 1;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND, "Key verify failed");
+ TEST_EQ(hash_only_check, 0, " hash check");
+
+ /* Key Block Hash Failure */
+ ResetMocks();
+ shared->flags = VBSD_BOOT_DEV_SWITCH_ON;
+ gbb.flags = GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP;
+ key_block_verify_fail = 1;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND, "Key verify failed");
+ TEST_EQ(hash_only_check, 1, " hash check");
+
+ /* Developer flag mismatch - dev switch on */
+ ResetMocks();
+ kbh.key_block_flags = KEY_BLOCK_FLAG_DEVELOPER_0 |
+ KEY_BLOCK_FLAG_RECOVERY_1;
+ copy_kbh();
+ shared->flags = VBSD_BOOT_DEV_SWITCH_ON;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND,
+ "Developer flag mismatch - dev switch on");
+
+ /* Developer flag mismatch - dev switch on with GBB override */
+ ResetMocks();
+ kbh.key_block_flags = KEY_BLOCK_FLAG_DEVELOPER_0 |
+ KEY_BLOCK_FLAG_RECOVERY_1;
+ copy_kbh();
+ gbb.flags = GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP;
+ shared->flags = VBSD_BOOT_DEV_SWITCH_ON;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_SUCCESS,
+ "Developer flag mismatch - dev switch on(gbb override)");
+
+ /* Recovery flag mismatch - dev switch on with GBB override */
+ ResetMocks();
+ kbh.key_block_flags = KEY_BLOCK_FLAG_DEVELOPER_0 |
+ KEY_BLOCK_FLAG_RECOVERY_0;
+ copy_kbh();
+ shared->flags = VBSD_BOOT_DEV_SWITCH_ON;
+ gbb.flags = GBB_FLAG_FORCE_DEV_BOOT_FASTBOOT_FULL_CAP;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_SUCCESS,
+ "Recovery flag mismatch - dev switch on(gbb override)");
+
+ /* Developer flag mismatch - dev switch off */
+ ResetMocks();
+ kbh.key_block_flags = KEY_BLOCK_FLAG_DEVELOPER_1 |
+ KEY_BLOCK_FLAG_RECOVERY_1;
+ copy_kbh();
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND,
+ "Developer flag mismatch - dev switch off");
+
+ /* Recovery flag mismatch */
+ ResetMocks();
+ kbh.key_block_flags = KEY_BLOCK_FLAG_DEVELOPER_0 |
+ KEY_BLOCK_FLAG_RECOVERY_0;
+ shared->flags = 0;
+ copy_kbh();
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND, "Recovery flag mismatch");
+
+ /* Preamble verification */
+ ResetMocks();
+ preamble_verify_fail = 1;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND, "Preamble verification");
+
+ /* Data verification */
+ ResetMocks();
+ verify_data_fail = 1;
+ TEST_EQ(VbVerifyMemoryBootImage(&cparams, &kparams, kernel_buffer,
+ kernel_buffer_size),
+ VBERROR_INVALID_KERNEL_FOUND, "Data verification");
+}
+
+int main(void)
+{
+ VerifyMemoryBootImageTest();
+
+ if (vboot_api_stub_check_memory())
+ return 255;
+
+ return gTestSuccess ? 0 : 255;
+}