From 13f601fbd4c1b128f333391e4552082594f0ff25 Mon Sep 17 00:00:00 2001 From: Joel Kitching Date: Wed, 28 Apr 2021 16:50:21 +0800 Subject: vboot: boot from miniOS recovery kernels on disk Add VbTryLoadMiniOsKernel() to vboot API, which boots from a miniOS recovery kernel located on internal disk. In this boot path, an attempt is made to verify and boot this kernel. Recovery proceeds from within the miniOS kernel by downloading a recovery image over the network. No USB disk is used in the process. For more information, see go/nbr-firmware. BUG=b:188121855, b:186682292 TEST=make clean && make runtests BRANCH=none Change-Id: Ic4d1fe5642a2bf71c51c78fd7830ad2b6e9eebeb Signed-off-by: Yu-Ping Wu Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2856364 Reviewed-by: Julius Werner --- Makefile | 2 + firmware/2lib/include/2return_codes.h | 6 + firmware/include/vboot_api.h | 15 ++ firmware/lib/include/load_kernel_fw.h | 16 +- firmware/lib/vboot_api_kernel.c | 30 ++- firmware/lib/vboot_kernel.c | 177 +++++++++++++- tests/vboot_api_kernel_tests.c | 105 +++++++- tests/vboot_kernel2_tests.c | 434 ++++++++++++++++++++++++++++++++++ tests/vboot_kernel_tests.c | 72 ++++-- 9 files changed, 824 insertions(+), 33 deletions(-) create mode 100644 tests/vboot_kernel2_tests.c diff --git a/Makefile b/Makefile index 70ef346e..82d6a44a 100644 --- a/Makefile +++ b/Makefile @@ -710,6 +710,7 @@ TEST_NAMES = \ tests/vboot_api_kernel4_tests \ tests/vboot_api_kernel_tests \ tests/vboot_kernel_tests \ + tests/vboot_kernel2_tests \ tests/verify_kernel ifeq (${MOCK_TPM}${TPM2_MODE},) @@ -1267,6 +1268,7 @@ endif ${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel4_tests ${RUNTEST} ${BUILD_RUN}/tests/vboot_api_kernel_tests ${RUNTEST} ${BUILD_RUN}/tests/vboot_kernel_tests + ${RUNTEST} ${BUILD_RUN}/tests/vboot_kernel2_tests .PHONY: run2tests run2tests: install_for_test diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h index 33cb7623..0dc7c441 100644 --- a/firmware/2lib/include/2return_codes.h +++ b/firmware/2lib/include/2return_codes.h @@ -617,6 +617,12 @@ enum vb2_return_code { /* Escape from NO_BOOT mode is detected */ VB2_ERROR_ESCAPE_NO_BOOT = 0x10080034, + /* + * Keyblock flags don't match current mode in + * vb2_load_kernel_keyblock(). + */ + VB2_ERROR_KERNEL_KEYBLOCK_MINIOS_FLAG = 0x10080035, + /********************************************************************** * API-level errors */ diff --git a/firmware/include/vboot_api.h b/firmware/include/vboot_api.h index b7b0ce54..f19ee747 100644 --- a/firmware/include/vboot_api.h +++ b/firmware/include/vboot_api.h @@ -89,6 +89,21 @@ vb2_error_t VbSelectAndLoadKernel(struct vb2_context *ctx, */ vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags); +/** + * Attempt loading a miniOS kernel from internal disk. + * + * Scans sectors at the start and end of the disk, and looks for miniOS kernels + * starting at the beginning of the sector. Attempts loading any miniOS + * kernels found. + * + * If successful, sets lkp.disk_handle to the disk for the kernel and returns + * VB2_SUCCESS. + * + * @param ctx Vboot context + * @return VB2_SUCCESS or the most specific VB2_ERROR_LK error. + */ +vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx); + /*****************************************************************************/ /* Disk access (previously in boot_device.h) */ diff --git a/firmware/lib/include/load_kernel_fw.h b/firmware/lib/include/load_kernel_fw.h index 96c8f864..af454703 100644 --- a/firmware/lib/include/load_kernel_fw.h +++ b/firmware/lib/include/load_kernel_fw.h @@ -12,10 +12,11 @@ #include "vboot_api.h" /** - * Attempt to load the kernel from the current device. + * Attempt to load kernel from the specified device. * * @param ctx Vboot context * @param params Params specific to loading the kernel + * @param disk_info Disk from which to read kernel * * Returns VB2_SUCCESS if successful. If unsuccessful, returns an error code. */ @@ -23,4 +24,17 @@ vb2_error_t LoadKernel(struct vb2_context *ctx, VbSelectAndLoadKernelParams *params, VbDiskInfo *disk_info); +/** + * Attempt to load miniOS kernel from the specified device. + * + * @param ctx Vboot context + * @param params Params specific to loading the kernel + * @param disk_info Disk from which to read kernel + * + * Returns VB2_SUCCESS if successful. If unsuccessful, returns an error code. + */ +vb2_error_t LoadMiniOsKernel(struct vb2_context *ctx, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info); + #endif /* VBOOT_REFERENCE_LOAD_KERNEL_FW_H_ */ diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c index b79b26fd..2abd57eb 100644 --- a/firmware/lib/vboot_api_kernel.c +++ b/firmware/lib/vboot_api_kernel.c @@ -61,13 +61,14 @@ static int is_valid_disk(VbDiskInfo *info, uint32_t disk_flags) ((info->flags & VB_DISK_FLAG_SELECT_MASK) - 1)) == 0; } -test_mockable -vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags) +static vb2_error_t VbTryLoadKernelImpl(struct vb2_context *ctx, + uint32_t disk_flags, int minios) { vb2_error_t rv = VB2_ERROR_LK_NO_DISK_FOUND; VbDiskInfo* disk_info = NULL; uint32_t disk_count = 0; uint32_t i; + vb2_error_t new_rv; /* TODO: Should have been set by VbSelectAndLoadKernel. Remove when this global is no longer needed. */ @@ -91,11 +92,16 @@ vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags) disk_info[i].flags); continue; } - kparams_ptr->disk_handle = disk_info[i].handle; - vb2_error_t new_rv = LoadKernel(ctx, kparams_ptr, - &disk_info[i]); - VB2_DEBUG("LoadKernel() = %#x\n", new_rv); + + if (minios) { + new_rv = LoadMiniOsKernel(ctx, kparams_ptr, + &disk_info[i]); + VB2_DEBUG("LoadMiniOsKernel() = %#x\n", new_rv); + } else { + new_rv = LoadKernel(ctx, kparams_ptr, &disk_info[i]); + VB2_DEBUG("LoadKernel() = %#x\n", new_rv); + } /* Stop now if we found a kernel. */ if (VB2_SUCCESS == new_rv) { @@ -133,6 +139,18 @@ vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags) return rv; } +test_mockable +vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t disk_flags) +{ + return VbTryLoadKernelImpl(ctx, disk_flags, 0); +} + +test_mockable +vb2_error_t VbTryLoadMiniOsKernel(struct vb2_context *ctx) +{ + return VbTryLoadKernelImpl(ctx, VB_DISK_FLAG_FIXED, 1); +} + vb2_error_t VbSelectAndLoadKernel(struct vb2_context *ctx, VbSelectAndLoadKernelParams *kparams) { diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c index d4cca14e..34a8a426 100644 --- a/firmware/lib/vboot_kernel.c +++ b/firmware/lib/vboot_kernel.c @@ -20,6 +20,7 @@ enum vb2_load_partition_flags { VB2_LOAD_PARTITION_FLAG_VBLOCK_ONLY = (1 << 0), + VB2_LOAD_PARTITION_FLAG_MINIOS = (1 << 1), }; #define KBUF_SIZE 65536 /* Bytes to read at start of kernel partition */ @@ -173,15 +174,17 @@ static vb2_error_t vb2_verify_kernel_dev_key_hash( /** * Verify a kernel vblock. * + * @param ctx Vboot context * @param kbuf Buffer containing the vblock * @param kbuf_size Size of the buffer in bytes + * @param lpflags Flags (one or more of vb2_load_partition_flags) * @param wb Work buffer. Must be at least * VB2_VERIFY_KERNEL_PREAMBLE_WORKBUF_BYTES bytes. * @return VB2_SUCCESS, or non-zero error code. */ static vb2_error_t vb2_verify_kernel_vblock( struct vb2_context *ctx, uint8_t *kbuf, uint32_t kbuf_size, - struct vb2_workbuf *wb) + uint32_t lpflags, struct vb2_workbuf *wb) { struct vb2_shared_data *sd = vb2_get_sd(ctx); @@ -250,6 +253,15 @@ static vb2_error_t vb2_verify_kernel_vblock( if (need_keyblock_valid) return VB2_ERROR_KERNEL_KEYBLOCK_REC_FLAG; } + if (!(keyblock->keyblock_flags & + ((lpflags & VB2_LOAD_PARTITION_FLAG_MINIOS) ? + VB2_KEYBLOCK_FLAG_MINIOS_1 : + VB2_KEYBLOCK_FLAG_MINIOS_0))) { + VB2_DEBUG("Keyblock miniOS flag mismatch.\n"); + keyblock_valid = 0; + if (need_keyblock_valid) + return VB2_ERROR_KERNEL_KEYBLOCK_MINIOS_FLAG; + } /* Check for rollback of key version except in recovery mode. */ enum vb2_boot_mode boot_mode = get_boot_mode(ctx); @@ -309,6 +321,29 @@ static vb2_error_t vb2_verify_kernel_vblock( return rv; } + /* Rollback check for miniOS */ + if (need_keyblock_valid && (lpflags & VB2_LOAD_PARTITION_FLAG_MINIOS)) { + if (preamble->kernel_version < + (sd->kernel_version_secdata >> 24)) { + keyblock_valid = 0; + if (need_keyblock_valid) { + VB2_DEBUG("miniOS kernel version too old.\n"); + return VB2_ERROR_KERNEL_PREAMBLE_VERSION_ROLLBACK; + } + } + if (preamble->kernel_version > 0xff) { + /* + * Key version is stored in the top 8 bits of 16 bits + * in the TPM, so key versions greater than 0xFF can't + * be stored properly. + */ + VB2_DEBUG("Key version > 0xFF.\n"); + keyblock_valid = 0; + if (need_keyblock_valid) + return VB2_ERROR_KERNEL_PREAMBLE_VERSION_RANGE; + } + } + /* * Kernel preamble version is the lower 16 bits of the composite * kernel version. @@ -361,9 +396,8 @@ static vb2_error_t vb2_load_partition( } read_ms += vb2ex_mtime() - start_ts; - if (vb2_verify_kernel_vblock(ctx, kbuf, KBUF_SIZE, &wb)) { + if (vb2_verify_kernel_vblock(ctx, kbuf, KBUF_SIZE, lpflags, &wb)) return VB2_ERROR_LOAD_PARTITION_VERIFY_VBLOCK; - } if (lpflags & VB2_LOAD_PARTITION_FLAG_VBLOCK_ONLY) return VB2_SUCCESS; @@ -455,6 +489,143 @@ static vb2_error_t vb2_load_partition( return VB2_SUCCESS; } +static vb2_error_t try_minios_kernel(struct vb2_context *ctx, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info, + uint64_t sector) { + VbExStream_t stream; + uint64_t sectors_left = disk_info->lba_count - sector; + const uint32_t lpflags = VB2_LOAD_PARTITION_FLAG_MINIOS; + vb2_error_t rv = VB2_ERROR_LK_NO_KERNEL_FOUND; + + /* Re-open stream at correct offset to pass to vb2_load_partition. */ + if (VbExStreamOpen(params->disk_handle, sector, sectors_left, + &stream)) { + VB2_DEBUG("Unable to open disk handle.\n"); + return rv; + } + + rv = vb2_load_partition(ctx, params, stream, lpflags); + VB2_DEBUG("vb2_load_partition returned: %d\n", rv); + + VbExStreamClose(stream); + + if (rv) + return VB2_ERROR_LK_NO_KERNEL_FOUND; + return rv; +} + +static vb2_error_t try_minios_sectors(struct vb2_context *ctx, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info, + uint64_t start, uint64_t count) +{ + const uint32_t buf_size = count * disk_info->bytes_per_lba; + char *buf; + VbExStream_t stream; + uint64_t isector; + vb2_error_t rv = VB2_ERROR_LK_NO_KERNEL_FOUND; + + buf = malloc(buf_size); + if (buf == NULL) { + VB2_DEBUG("Unable to allocate disk read buffer.\n"); + return rv; + } + + if (VbExStreamOpen(params->disk_handle, start, count, &stream)) { + VB2_DEBUG("Unable to open disk handle.\n"); + free(buf); + return rv; + } + if (VbExStreamRead(stream, buf_size, buf)) { + VB2_DEBUG("Unable to read disk.\n"); + free(buf); + VbExStreamClose(stream); + return rv; + } + VbExStreamClose(stream); + + for (isector = 0; isector < count; isector++) { + if (memcmp(buf + isector * disk_info->bytes_per_lba, + VB2_KEYBLOCK_MAGIC, VB2_KEYBLOCK_MAGIC_SIZE)) + continue; + VB2_DEBUG("Match on sector %" PRIu64 " / %" PRIu64 "\n", + start + isector, + disk_info->lba_count - 1); + rv = try_minios_kernel(ctx, params, disk_info, start + isector); + if (rv == VB2_SUCCESS) + break; + } + + free(buf); + return rv; +} + +static vb2_error_t try_minios_sector_region(struct vb2_context *ctx, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info, + int end_region) +{ + const uint64_t disk_count_half = (disk_info->lba_count + 1) / 2; + const uint64_t check_count_256 = 256 * 1024 + * 1024 / disk_info->bytes_per_lba; // 256 MB + const uint64_t batch_count_1 = 1024 + * 1024 / disk_info->bytes_per_lba; // 1 MB + const uint64_t check_count = VB2_MIN(disk_count_half, check_count_256); + const uint64_t batch_count = VB2_MIN(disk_count_half, batch_count_1); + uint64_t sector; + uint64_t start; + uint64_t end; + const char *region_name; + vb2_error_t rv = VB2_ERROR_LK_NO_KERNEL_FOUND; + + if (!end_region) { + start = 0; + end = check_count; + region_name = "start"; + } else { + start = disk_info->lba_count - check_count; + end = disk_info->lba_count; + region_name = "end"; + } + + VB2_DEBUG("Checking %s of disk for kernels...\n", region_name); + for (sector = start; sector < end; sector += batch_count) { + rv = try_minios_sectors(ctx, params, disk_info, sector, + batch_count); + if (rv == VB2_SUCCESS) + return rv; + } + + return rv; +} + +/* + * Search for kernels by sector, rather than by partition. Only sectors near + * the start and end of disks are considered, and the kernel must start exactly + * at the first byte of the sector. + */ +vb2_error_t LoadMiniOsKernel(struct vb2_context *ctx, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info) +{ + vb2_error_t rv; + int end_region_first = vb2_nv_get(ctx, VB2_NV_MINIOS_PRIORITY); + + rv = try_minios_sector_region(ctx, params, disk_info, end_region_first); + if (rv) + rv = try_minios_sector_region(ctx, params, disk_info, + !end_region_first); + if (rv) + return rv; + + rv = vb2ex_tpm_set_mode(VB2_TPM_MODE_DISABLED); + if (rv) + VB2_DEBUG("Failed to disable TPM\n"); + + return rv; +} + vb2_error_t LoadKernel(struct vb2_context *ctx, VbSelectAndLoadKernelParams *params, VbDiskInfo *disk_info) diff --git a/tests/vboot_api_kernel_tests.c b/tests/vboot_api_kernel_tests.c index ea2f97c9..51f0ddb1 100644 --- a/tests/vboot_api_kernel_tests.c +++ b/tests/vboot_api_kernel_tests.c @@ -50,7 +50,7 @@ typedef struct { static const char pickme[] = "correct choice"; #define DONT_CARE ((const char *)42) -test_case_t test[] = { +test_case_t normal_tests[] = { { .name = "first drive (removable)", .ctx_flags = 0, @@ -388,12 +388,52 @@ test_case_t test[] = { }, }; +test_case_t minios_tests[] = { + { + .name = "pick first fixed drive", + .ctx_flags = 0, + .disks_to_provide = { + {4096, 100, VB_DISK_FLAG_REMOVABLE, 0}, + {4096, 100, VB_DISK_FLAG_FIXED, pickme}, + {4096, 100, VB_DISK_FLAG_FIXED, "holygrail"}, + }, + .disk_count_to_return = DEFAULT_COUNT, + .diskgetinfo_return_val = VB2_SUCCESS, + .loadkernel_return_val = {0}, + .external_expected = {0}, + + .expected_recovery_request_val = VB2_RECOVERY_NOT_REQUESTED, + .expected_to_find_disk = pickme, + .expected_to_load_disk = pickme, + .expected_return_val = VB2_SUCCESS + }, + { + .name = "skip failed fixed drive", + .ctx_flags = 0, + .disks_to_provide = { + {4096, 100, VB_DISK_FLAG_FIXED, "holygrail"}, + {4096, 100, VB_DISK_FLAG_FIXED, pickme}, + }, + .disk_count_to_return = DEFAULT_COUNT, + .diskgetinfo_return_val = VB2_SUCCESS, + .loadkernel_return_val = {VB2_ERROR_LK_INVALID_KERNEL_FOUND, 0}, + .external_expected = {0}, + + .expected_recovery_request_val = VB2_RECOVERY_NOT_REQUESTED, + .expected_to_find_disk = pickme, + .expected_to_load_disk = pickme, + .expected_return_val = VB2_SUCCESS + }, +}; + /****************************************************************************/ /* Mock data */ static VbDiskInfo mock_disks[MAX_TEST_DISKS]; static test_case_t *t; static int load_kernel_calls; +static int lk_normal_calls; +static int lk_minios_calls; static uint32_t got_recovery_request_val; static const char *got_find_disk; static const char *got_load_disk; @@ -407,7 +447,7 @@ static struct VbSelectAndLoadKernelParams kparams; /** * Reset mock data (for use before each test) */ -static void ResetMocks(int i) +static void ResetMocks(test_case_t *test_case) { TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx), "vb2api_init failed"); @@ -418,13 +458,15 @@ static void ResetMocks(int i) memset(&mock_disks, 0, sizeof(mock_disks)); load_kernel_calls = 0; + lk_normal_calls = 0; + lk_minios_calls = 0; got_recovery_request_val = VB2_RECOVERY_NOT_REQUESTED; got_find_disk = 0; got_load_disk = 0; got_return_val = 0xdeadbeef; - t = test + i; + t = test_case; } static int is_nonzero(const void *vptr, size_t count) @@ -497,9 +539,9 @@ vb2_error_t VbExDiskFreeInfo(VbDiskInfo *infos, return VB2_SUCCESS; } -vb2_error_t LoadKernel(struct vb2_context *c, - VbSelectAndLoadKernelParams *params, - VbDiskInfo *disk_info) +static vb2_error_t LoadKernelImpl(struct vb2_context *c, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info) { got_find_disk = (const char *)params->disk_handle; VB2_DEBUG("%s(%d): got_find_disk = %s\n", __FUNCTION__, @@ -511,6 +553,22 @@ vb2_error_t LoadKernel(struct vb2_context *c, return t->loadkernel_return_val[load_kernel_calls++]; } +vb2_error_t LoadKernel(struct vb2_context *c, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info) +{ + lk_normal_calls++; + return LoadKernelImpl(c, params, disk_info); +} + +vb2_error_t LoadMiniOsKernel(struct vb2_context *c, + VbSelectAndLoadKernelParams *params, + VbDiskInfo *disk_info) +{ + lk_minios_calls++; + return LoadKernelImpl(c, params, disk_info); +} + void vb2_nv_set(struct vb2_context *c, enum vb2_nv_param param, uint32_t value) @@ -527,11 +585,11 @@ void vb2_nv_set(struct vb2_context *c, static void VbTryLoadKernelTest(void) { int i; - int num_tests = sizeof(test) / sizeof(test[0]); + int num_tests = ARRAY_SIZE(normal_tests); for (i = 0; i < num_tests; i++) { - printf("Test case: %s ...\n", test[i].name); - ResetMocks(i); + printf("Test case: %s ...\n", normal_tests[i].name); + ResetMocks(&normal_tests[i]); ctx->flags = t->ctx_flags; TEST_EQ(VbTryLoadKernel(ctx, t->want_flags), t->expected_return_val, " return value"); @@ -545,11 +603,40 @@ static void VbTryLoadKernelTest(void) } TEST_EQ(got_external_mismatch, 0, " external GPT errors"); } + TEST_EQ(lk_normal_calls, load_kernel_calls, " LoadKernel called"); + TEST_EQ(lk_minios_calls, 0, " LoadMiniOsKernel not called"); +} + +static void VbTryLoadMiniOsKernelTest(void) +{ + int i; + int num_tests = ARRAY_SIZE(minios_tests); + + for (i = 0; i < num_tests; i++) { + printf("Test case: %s ...\n", minios_tests[i].name); + ResetMocks(&minios_tests[i]); + ctx->flags = t->ctx_flags; + TEST_EQ(VbTryLoadMiniOsKernel(ctx), + t->expected_return_val, " return value"); + TEST_EQ(got_recovery_request_val, + t->expected_recovery_request_val, " recovery_request"); + if (t->expected_to_find_disk != DONT_CARE) { + TEST_PTR_EQ(got_find_disk, t->expected_to_find_disk, + " find disk"); + TEST_PTR_EQ(got_load_disk, t->expected_to_load_disk, + " load disk"); + } + TEST_EQ(got_external_mismatch, 0, " external GPT errors"); + } + TEST_EQ(lk_normal_calls, 0, " LoadKernel not called"); + TEST_EQ(lk_minios_calls, load_kernel_calls, + " LoadMiniOsKernel called"); } int main(void) { VbTryLoadKernelTest(); + VbTryLoadMiniOsKernelTest(); return gTestSuccess ? 0 : 255; } diff --git a/tests/vboot_kernel2_tests.c b/tests/vboot_kernel2_tests.c new file mode 100644 index 00000000..5424e86c --- /dev/null +++ b/tests/vboot_kernel2_tests.c @@ -0,0 +1,434 @@ +/* Copyright 2021 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 miniOS kernel selection, loading, verification, and booting. + */ + +#include "2api.h" +#include "2common.h" +#include "2misc.h" +#include "2nvstorage.h" +#include "2secdata.h" +#include "load_kernel_fw.h" +#include "test_common.h" +#include "vboot_api.h" + +#define MAX_MOCK_KERNELS 10 +#define KBUF_SIZE 65536 + +/* Internal struct to simulate a stream for sector-based disks */ +struct disk_stream { + /* Disk handle */ + VbExDiskHandle_t handle; + + /* Next sector to read */ + uint64_t sector; + + /* Number of sectors left */ + uint64_t sectors_left; +}; + +/* Represent a "kernel" located on the disk */ +struct mock_kernel { + /* Sector where the kernel begins */ + uint64_t sector; + + /* Return value from vb2_load_partition */ + vb2_error_t rv; + + /* Number of times the sector was read */ + int read_count; +}; + +/* Mock data */ +static struct vb2_context *ctx; +static struct vb2_shared_data *sd; +static struct vb2_workbuf wb; +static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE] + __attribute__((aligned(VB2_WORKBUF_ALIGN))); + +static VbSelectAndLoadKernelParams lkp; +static VbDiskInfo disk_info; +static struct vb2_keyblock kbh; +static struct vb2_kernel_preamble kph; +static uint8_t kernel_buffer[80000]; + +static struct mock_kernel kernels[MAX_MOCK_KERNELS]; +static int kernel_count; +static struct mock_kernel *cur_kernel; + +static int mock_tpm_set_mode_calls; + +static void add_mock_kernel(uint64_t sector, vb2_error_t rv) +{ + if (kernel_count >= ARRAY_SIZE(kernels)) { + TEST_TRUE(0, " kernel_count ran out of entries!"); + return; + } + + kernels[kernel_count].sector = sector; + kernels[kernel_count].rv = rv; + kernel_count++; +} + +static void reset_common_data(void) +{ + TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx), + "vb2api_init failed"); + vb2_workbuf_from_ctx(ctx, &wb); + vb2_nv_init(ctx); + vb2api_secdata_kernel_create(ctx); + vb2_secdata_kernel_init(ctx); + ctx->flags = VB2_CONTEXT_RECOVERY_MODE; + + sd = vb2_get_sd(ctx); + sd->kernel_version_secdata = 0xabcdef | (1 << 24); + + memset(&lkp, 0, sizeof(lkp)); + lkp.kernel_buffer = kernel_buffer; + lkp.kernel_buffer_size = sizeof(kernel_buffer); + lkp.disk_handle = (VbExDiskHandle_t)1; + + memset(&disk_info, 0, sizeof(disk_info)); + disk_info.bytes_per_lba = 512; + disk_info.lba_count = 1024; + disk_info.handle = lkp.disk_handle; + + memset(&kbh, 0, sizeof(kbh)); + kbh.data_key.key_version = 2; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_DEVELOPER_1 + | VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_MINIOS_1; + kbh.keyblock_size = sizeof(kbh); + + memset(&kph, 0, sizeof(kph)); + kph.kernel_version = 1; + kph.preamble_size = 4096 - kbh.keyblock_size; + kph.body_signature.data_size = 0; + kph.bootloader_address = 0xbeadd008; + kph.bootloader_size = 0x1234; + + + memset(&kernels, 0, sizeof(kernels)); + kernel_count = 0; + cur_kernel = NULL; + + mock_tpm_set_mode_calls = 0; +} + +/* Mocks */ + +vb2_error_t VbExStreamOpen(VbExDiskHandle_t handle, uint64_t lba_start, + uint64_t lba_count, VbExStream_t *stream) +{ + struct disk_stream *s; + uint64_t i; + + if (!handle) { + *stream = NULL; + return VB2_ERROR_UNKNOWN; + } + + if (lba_start + lba_count > disk_info.lba_count) + return VB2_ERROR_UNKNOWN; + + s = malloc(sizeof(*s)); + s->handle = handle; + s->sector = lba_start; + s->sectors_left = lba_count; + + *stream = (void *)s; + + for (i = 0; i < kernel_count; i++) { + if (kernels[i].sector == lba_start) + cur_kernel = &kernels[i]; + } + + return VB2_SUCCESS; +} + +vb2_error_t VbExStreamRead(VbExStream_t stream, uint32_t bytes, void *buffer) +{ + struct disk_stream *s = (struct disk_stream *)stream; + uint64_t sectors; + uint64_t i; + + if (!s) + return VB2_ERROR_UNKNOWN; + + /* For now, require reads to be a multiple of the LBA size */ + if (bytes % disk_info.bytes_per_lba) + return VB2_ERROR_UNKNOWN; + + /* Fail on overflow */ + sectors = bytes / disk_info.bytes_per_lba; + if (sectors > s->sectors_left) + return VB2_ERROR_UNKNOWN; + + memset(buffer, 0, bytes); + for (i = 0; i < kernel_count; i++) { + if (kernels[i].sector >= s->sector && + kernels[i].sector < s->sector + sectors) { + VB2_DEBUG("Simulating kernel %" PRIu64 " match\n", i); + uint64_t buf_offset = (kernels[i].sector - s->sector) + * disk_info.bytes_per_lba; + memcpy(buffer + buf_offset, VB2_KEYBLOCK_MAGIC, + VB2_KEYBLOCK_MAGIC_SIZE); + kernels[i].read_count++; + TEST_TRUE(kernels[i].read_count <= 2, + " Max read count exceeded"); + } + } + + s->sector += sectors; + s->sectors_left -= sectors; + + return VB2_SUCCESS; +} + +void VbExStreamClose(VbExStream_t stream) +{ + free(stream); +} + +vb2_error_t vb2_unpack_key_buffer(struct vb2_public_key *key, + const uint8_t *buf, uint32_t size) +{ + return cur_kernel->rv; +} + +vb2_error_t vb2_verify_keyblock(struct vb2_keyblock *block, uint32_t size, + const struct vb2_public_key *key, + const struct vb2_workbuf *w) +{ + /* Use this as an opportunity to override the keyblock */ + memcpy((void *)block, &kbh, sizeof(kbh)); + + return cur_kernel->rv; +} + +vb2_error_t vb2_verify_keyblock_hash(const struct vb2_keyblock *block, + uint32_t size, + const struct vb2_workbuf *w) +{ + /* Use this as an opportunity to override the keyblock */ + memcpy((void *)block, &kbh, sizeof(kbh)); + + return cur_kernel->rv; +} + +vb2_error_t vb2_verify_kernel_preamble(struct vb2_kernel_preamble *preamble, + uint32_t size, const struct vb2_public_key *key, + const struct vb2_workbuf *w) +{ + /* Use this as an opportunity to override the preamble */ + memcpy((void *)preamble, &kph, sizeof(kph)); + + return cur_kernel->rv; +} + +vb2_error_t vb2_verify_data(const uint8_t *data, uint32_t size, + struct vb2_signature *sig, + const struct vb2_public_key *key, + const struct vb2_workbuf *w) +{ + return cur_kernel->rv; +} + +vb2_error_t vb2_digest_buffer(const uint8_t *buf, uint32_t size, + enum vb2_hash_algorithm hash_alg, uint8_t *digest, + uint32_t digest_size) +{ + return cur_kernel->rv; +} + +vb2_error_t vb2ex_tpm_set_mode(enum vb2_tpm_mode mode_val) +{ + mock_tpm_set_mode_calls++; + return VB2_SUCCESS; +} + +/* Make sure nothing tested here ever calls this directly. */ +void vb2api_fail(struct vb2_context *c, uint8_t reason, uint8_t subcode) +{ + TEST_TRUE(0, " called vb2api_fail()"); +} + +/* Tests */ + +static void load_minios_kernel_tests(void) +{ + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 1; + add_mock_kernel(0, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "{valid kernel}"); + TEST_EQ(mock_tpm_set_mode_calls, 1, + " TPM disabled"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 1; + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, "{no kernel}"); + TEST_EQ(mock_tpm_set_mode_calls, 0, + " TPM not disabled"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(1, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "{no kernel, valid kernel}"); + TEST_EQ(cur_kernel->sector, 1, " select kernel"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_ERROR_MOCK); + add_mock_kernel(1, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "{invalid kernel, valid kernel}"); + TEST_EQ(cur_kernel->sector, 1, " select second kernel"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_ERROR_MOCK); + add_mock_kernel(1, VB2_ERROR_MOCK); + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "{invalid kernel, invalid kernel}"); + TEST_EQ(mock_tpm_set_mode_calls, 0, + " TPM not disabled"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + add_mock_kernel(1, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "{valid kernel, valid kernel} minios_priority=0"); + TEST_EQ(cur_kernel->sector, 0, " select first kernel"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + add_mock_kernel(1, VB2_SUCCESS); + vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "{valid kernel, valid kernel} minios_priority=1"); + TEST_EQ(cur_kernel->sector, 1, " select second kernel"); + + reset_common_data(); + disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE; + disk_info.lba_count = 4; + add_mock_kernel(1, VB2_SUCCESS); + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "valid kernel header near start of disk (disk too small)"); + + reset_common_data(); + disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE; + disk_info.lba_count = 1000; + add_mock_kernel(999, VB2_SUCCESS); + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "valid kernel header near end of disk"); + + reset_common_data(); + disk_info.bytes_per_lba = 1024; + disk_info.lba_count = 128; + add_mock_kernel(63, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "start/end overlap assuming >128 MB search range (start)"); + + reset_common_data(); + disk_info.bytes_per_lba = 1024; + disk_info.lba_count = 128; + add_mock_kernel(64, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "start/end overlap assuming >128 MB search range (end)"); + + reset_common_data(); + disk_info.bytes_per_lba = 128; + disk_info.lba_count = 1024; + add_mock_kernel(3, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "kernel at last sector in batch assuming 512 KB batches"); + + reset_common_data(); + disk_info.bytes_per_lba = 256; + disk_info.lba_count = 1024; + add_mock_kernel(3, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "kernel at last sector in batch assuming 1 MB batches"); + + reset_common_data(); + disk_info.bytes_per_lba = 512; + disk_info.lba_count = 1024; + add_mock_kernel(3, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "kernel at last sector in batch assuming 2 MB batches"); + + reset_common_data(); + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_MINIOS_1; + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "kernel with minios keyblock flag"); + + reset_common_data(); + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_MINIOS_0; + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "kernel with !minios keyblock flag"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + sd->kernel_version_secdata = 5 << 24; + kph.kernel_version = 4; + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "kernel version too old"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + sd->kernel_version_secdata = 5 << 24; + kph.kernel_version = 0x100; + TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info), + VB2_ERROR_LK_NO_KERNEL_FOUND, + "kernel version greater than 0xff"); + + reset_common_data(); + disk_info.bytes_per_lba = KBUF_SIZE; + disk_info.lba_count = 2; + add_mock_kernel(0, VB2_SUCCESS); + sd->kernel_version_secdata = 5 << 24; + kph.kernel_version = 6; + TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info), + "newer kernel version"); +} + +int main(void) +{ + load_minios_kernel_tests(); + + return gTestSuccess ? 0 : 255; +} diff --git a/tests/vboot_kernel_tests.c b/tests/vboot_kernel_tests.c index 944be0aa..14e7e1f2 100644 --- a/tests/vboot_kernel_tests.c +++ b/tests/vboot_kernel_tests.c @@ -363,50 +363,94 @@ static void LoadKernelTest(void) TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Fail key block dev sig fwmp"); - /* Check keyblock flag mismatches */ + /* Check keyblock flags */ ResetMocks(); - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_0 | VB2_KEYBLOCK_FLAG_DEVELOPER_1; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0 + | VB2_KEYBLOCK_FLAG_DEVELOPER_1 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock dev flag mismatch"); ResetMocks(); - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_1 | VB2_KEYBLOCK_FLAG_DEVELOPER_0; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock rec flag mismatch"); + ResetMocks(); + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_1; + TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, + "Keyblock minios flag mismatch"); + ResetMocks(); ctx->flags |= VB2_CONTEXT_RECOVERY_MODE; - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_1 | VB2_KEYBLOCK_FLAG_DEVELOPER_1; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_1 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock recdev flag mismatch"); + ResetMocks(); + ctx->flags |= VB2_CONTEXT_RECOVERY_MODE; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_0; + TestLoadKernel(0, "Keyblock rec flag okay"); + ResetMocks(); ctx->flags |= VB2_CONTEXT_RECOVERY_MODE | VB2_CONTEXT_DEVELOPER_MODE; - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_1 | VB2_KEYBLOCK_FLAG_DEVELOPER_0; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock rec!dev flag mismatch"); - /* Check keyblock flag mismatches (dev mode + signed kernel required) */ + ResetMocks(); + ctx->flags |= VB2_CONTEXT_RECOVERY_MODE | VB2_CONTEXT_DEVELOPER_MODE; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_1 + | VB2_KEYBLOCK_FLAG_MINIOS_0; + TestLoadKernel(0, "Keyblock recdev flag okay"); + + /* Check keyblock flags (dev mode + signed kernel required) */ ResetMocks(); ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1); - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_1 | VB2_KEYBLOCK_FLAG_DEVELOPER_0; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock dev flag mismatch (signed kernel required)"); ResetMocks(); ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY; - kbh.keyblock_flags = - VB2_KEYBLOCK_FLAG_RECOVERY_1 | VB2_KEYBLOCK_FLAG_DEVELOPER_0; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_1 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_0; TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, "Keyblock dev flag mismatch (signed kernel required)"); + ResetMocks(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + fwmp->flags |= VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY; + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0 + | VB2_KEYBLOCK_FLAG_DEVELOPER_0 + | VB2_KEYBLOCK_FLAG_MINIOS_1; + TestLoadKernel(VB2_ERROR_LK_INVALID_KERNEL_FOUND, + "Keyblock dev flag mismatch (signed kernel required)"); + + ResetMocks(); + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1); + kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_RECOVERY_0 + | VB2_KEYBLOCK_FLAG_DEVELOPER_1 + | VB2_KEYBLOCK_FLAG_MINIOS_0; + TestLoadKernel(0, "Keyblock dev flag okay (signed kernel required)"); + /* Check kernel key version */ ResetMocks(); kbh.data_key.key_version = 1; -- cgit v1.2.1