diff options
author | Randall Spangler <rspangler@chromium.org> | 2014-06-10 17:03:40 -0700 |
---|---|---|
committer | chrome-internal-fetch <chrome-internal-fetch@google.com> | 2014-06-19 03:23:24 +0000 |
commit | da2b49cf08a27551fd910626f669910a636378d4 (patch) | |
tree | 09ae3f54a80bf9dcb564db44f0f5da3d0d6398d7 | |
parent | 4521c1f19f3b3f6000bb437140b85389d38bf655 (diff) | |
download | vboot-da2b49cf08a27551fd910626f669910a636378d4.tar.gz |
vboot2: misc higher-level routines
I'm breaking the last chunk of vboot2 into smaller pieces as I add
tests. This has a bunch of misc routines like the dev switch logic
and GBB header parsing.
BUG=chromium:370082
BRANCH=none
TEST=make clean && VBOOT2=1 COV=1 make
Change-Id: I0f67400d9b59ec21ed5cc155a9b774fd37eb559b
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/203374
Reviewed-by: Bill Richardson <wfrichar@chromium.org>
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | firmware/2lib/2misc.c | 307 | ||||
-rw-r--r-- | firmware/2lib/2stub.c | 23 | ||||
-rw-r--r-- | firmware/2lib/include/2api.h | 40 | ||||
-rw-r--r-- | firmware/2lib/include/2misc.h | 80 | ||||
-rw-r--r-- | firmware/2lib/include/2return_codes.h | 128 | ||||
-rw-r--r-- | tests/vb2_misc_tests.c | 429 |
7 files changed, 970 insertions, 41 deletions
@@ -322,6 +322,10 @@ VBSF_SRCS += \ VBSLK_SRCS += \ firmware/stub/vboot_api_stub.c \ firmware/stub/vboot_api_stub_disk.c + +FWLIB2_SRCS += \ + firmware/2lib/2stub.c + endif VBSF_SRCS += ${VBINIT_SRCS} diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c index 9713ffc0..c0692c53 100644 --- a/firmware/2lib/2misc.c +++ b/firmware/2lib/2misc.c @@ -14,6 +14,93 @@ #include "2sha.h" #include "2rsa.h" +void vb2_workbuf_from_ctx(struct vb2_context *ctx, struct vb2_workbuf *wb) +{ + vb2_workbuf_init(wb, ctx->workbuf + ctx->workbuf_used, + ctx->workbuf_size - ctx->workbuf_used); +} + +int vb2_read_gbb_header(struct vb2_context *ctx, struct vb2_gbb_header *gbb) +{ + static const uint8_t expect_sig[VB2_GBB_SIGNATURE_SIZE] = + VB2_GBB_SIGNATURE; + int rv; + + /* Read the entire header */ + rv = vb2ex_read_resource(ctx, VB2_RES_GBB, 0, gbb, sizeof(*gbb)); + if (rv) + return rv; + + /* Make sure it's really a GBB */ + if (memcmp(gbb->signature, expect_sig, sizeof(expect_sig))) + return VB2_ERROR_GBB_MAGIC; + + /* Check for compatible version */ + if (gbb->major_version != VB2_GBB_MAJOR_VER) + return VB2_ERROR_GBB_VERSION; + + /* Current code is not backwards-compatible to 1.0 headers */ + if (gbb->minor_version < VB2_GBB_MINOR_VER) + return VB2_ERROR_GBB_TOO_OLD; + + /* + * Header size should be at least as big as we expect. It could be + * bigger, if the header has grown. + */ + if (gbb->header_size < sizeof(*gbb)) + return VB2_ERROR_GBB_HEADER_SIZE; + + return VB2_SUCCESS; +} + +void vb2_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + + /* If NV data hasn't been initialized, initialize it now */ + if (!(sd->status & VB2_SD_STATUS_NV_INIT)) + vb2_nv_init(ctx); + + /* See if we were far enough in the boot process to choose a slot */ + if (sd->status & VB2_SD_STATUS_CHOSE_SLOT) { + + /* Boot failed */ + vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE); + + /* Use up remaining tries */ + vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 0); + + /* + * Try the other slot next time. We'll alternate + * between slots, which may help if one or both slots is + * flaky. + */ + vb2_nv_set(ctx, VB2_NV_TRY_NEXT, 1 - sd->fw_slot); + + /* + * If we didn't try the other slot last boot, or we tried it + * and it didn't fail, try it next boot. + */ + if (sd->last_fw_slot != 1 - sd->fw_slot || + sd->last_fw_result != VB2_FW_RESULT_FAILURE) + return; + } + + /* + * If we're still here, we failed before choosing a slot, or both + * this slot and the other slot failed in successive boots. So we + * need to go to recovery. + * + * Set a recovery reason and subcode only if they're not already set. + * If recovery is already requested, it's a more specific error code + * than later code is providing and we shouldn't overwrite it. + */ + if (!vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST)) { + vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, reason); + vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, subcode); + } +} + int vb2_init_context(struct vb2_context *ctx) { struct vb2_shared_data *sd = vb2_get_sd(ctx); @@ -29,7 +116,7 @@ int vb2_init_context(struct vb2_context *ctx) */ if (ctx->workbuf_size < sizeof(*sd)) return VB2_ERROR_INITCTX_WORKBUF_SMALL; - if (!vb_aligned(ctx->workbuf, sizeof(uint32_t))) + if (!vb_aligned(ctx->workbuf, VB2_WORKBUF_ALIGN)) return VB2_ERROR_INITCTX_WORKBUF_ALIGN; /* Initialize the shared data at the start of the work buffer */ @@ -37,3 +124,221 @@ int vb2_init_context(struct vb2_context *ctx) ctx->workbuf_used = sizeof(*sd); return VB2_SUCCESS; } + +void vb2_check_recovery(struct vb2_context *ctx) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + + /* + * Read the current recovery request, unless there's already been a + * failure earlier in the boot process. + */ + if (!sd->recovery_reason) + sd->recovery_reason = vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST); + + /* Clear the recovery request so we don't get stuck in recovery mode */ + if (sd->recovery_reason) { + vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, + VB2_RECOVERY_NOT_REQUESTED); + /* + * Note that we ignore failures clearing the request. We only + * hit this code path if recovery mode has already been + * requested, so what more can we do? Don't want to obscure + * the original reason for going into recovery mode. + */ + } + + /* If forcing recovery, override recovery reason */ + if (ctx->flags & VB2_CONTEXT_FORCE_RECOVERY_MODE) { + sd->recovery_reason = VB2_RECOVERY_RO_MANUAL; + sd->flags = VB2_SD_FLAG_MANUAL_RECOVERY; + } + + /* If recovery reason is non-zero, tell caller we need recovery mode */ + if (sd->recovery_reason) + ctx->flags |= VB2_CONTEXT_RECOVERY_MODE; +} + +int vb2_fw_parse_gbb(struct vb2_context *ctx) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + struct vb2_gbb_header *gbb; + struct vb2_workbuf wb; + int rv; + + vb2_workbuf_from_ctx(ctx, &wb); + + /* Read GBB into next chunk of work buffer */ + gbb = vb2_workbuf_alloc(&wb, sizeof(*gbb)); + if (!gbb) + return VB2_ERROR_GBB_WORKBUF; + + rv = vb2_read_gbb_header(ctx, gbb); + if (rv) + return rv; + + /* Extract the only things we care about at firmware time */ + sd->gbb_flags = gbb->flags; + sd->gbb_rootkey_offset = gbb->rootkey_offset; + sd->gbb_rootkey_size = gbb->rootkey_size; + + return VB2_SUCCESS; +} + +int vb2_check_dev_switch(struct vb2_context *ctx) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + uint32_t flags; + uint32_t old_flags; + int is_dev = 0; + int rv; + + /* Read secure flags */ + rv = vb2_secdata_get(ctx, VB2_SECDATA_FLAGS, &flags); + if (rv) + return rv; + + old_flags = flags; + + /* Handle dev disable request */ + if (vb2_nv_get(ctx, VB2_NV_DISABLE_DEV_REQUEST)) { + flags &= ~VB2_SECDATA_FLAG_DEV_MODE; + + /* Clear the request */ + vb2_nv_set(ctx, VB2_NV_DISABLE_DEV_REQUEST, 0); + } + + /* Check virtual dev switch */ + if (flags & VB2_SECDATA_FLAG_DEV_MODE) + is_dev = 1; + + /* Handle forcing dev mode via physical switch */ + if (ctx->flags & VB2_CONTEXT_FORCE_DEVELOPER_MODE) + is_dev = 1; + + /* Check if GBB is forcing dev mode */ + if (sd->gbb_flags & VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON) + is_dev = 1; + + /* Handle whichever mode we end up in */ + if (is_dev) { + /* Developer mode */ + sd->flags |= VB2_SD_DEV_MODE_ENABLED; + ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; + + flags |= VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER; + } else { + /* Normal mode */ + flags &= ~VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER; + + /* + * Disable dev_boot_* flags. This ensures they will be + * initially disabled if the user later transitions back into + * developer mode. + */ + vb2_nv_set(ctx, VB2_NV_DEV_BOOT_USB, 0); + vb2_nv_set(ctx, VB2_NV_DEV_BOOT_LEGACY, 0); + vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 0); + } + + if (flags != old_flags) { + /* + * Just changed dev mode state. Clear TPM owner. This must be + * done here instead of simply passing a flag to + * vb2_check_tpm_clear(), because we don't want to update + * last_boot_developer and then fail to clear the TPM owner. + */ + rv = vb2ex_tpm_clear_owner(ctx); + if (rv) { + /* + * Note that this truncates rv to 8 bit. Which is not + * as useful as the full error code, but we don't have + * NVRAM space to store the full 32-bit code. + */ + vb2_fail(ctx, VB2_RECOVERY_TPM_CLEAR_OWNER, rv); + return rv; + } + + /* Save new flags */ + rv = vb2_secdata_set(ctx, VB2_SECDATA_FLAGS, flags); + if (rv) + return rv; + } + + return VB2_SUCCESS; +} + +int vb2_check_tpm_clear(struct vb2_context *ctx) +{ + int rv; + + /* Check if we've been asked to clear the owner */ + if (!vb2_nv_get(ctx, VB2_NV_CLEAR_TPM_OWNER_REQUEST)) + return VB2_SUCCESS; /* No need to clear */ + + /* Request applies one time only */ + vb2_nv_set(ctx, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 0); + + /* Try clearing */ + rv = vb2ex_tpm_clear_owner(ctx); + if (rv) { + /* + * Note that this truncates rv to 8 bit. Which is not as + * useful as the full error code, but we don't have NVRAM space + * to store the full 32-bit code. + */ + vb2_fail(ctx, VB2_RECOVERY_TPM_CLEAR_OWNER, rv); + return rv; + } + + /* Clear successful */ + vb2_nv_set(ctx, VB2_NV_CLEAR_TPM_OWNER_DONE, 1); + return VB2_SUCCESS; +} + +int vb2_select_fw_slot(struct vb2_context *ctx) +{ + struct vb2_shared_data *sd = vb2_get_sd(ctx); + uint32_t tries; + + /* Get result of last boot */ + sd->last_fw_slot = vb2_nv_get(ctx, VB2_NV_FW_TRIED); + sd->last_fw_result = vb2_nv_get(ctx, VB2_NV_FW_RESULT); + + /* Clear result, since we don't know what will happen this boot */ + vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN); + + /* Get slot to try */ + sd->fw_slot = vb2_nv_get(ctx, VB2_NV_TRY_NEXT); + + /* Check try count */ + tries = vb2_nv_get(ctx, VB2_NV_TRY_COUNT); + + if (sd->last_fw_result == VB2_FW_RESULT_TRYING && + sd->last_fw_slot == sd->fw_slot && + tries == 0) { + /* + * We used up our last try on the previous boot, so fall back + * to the other slot this boot. + */ + sd->fw_slot = 1 - sd->fw_slot; + vb2_nv_set(ctx, VB2_NV_TRY_NEXT, sd->fw_slot); + } + + if (tries > 0) { + /* Still trying this firmware */ + vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_TRYING); + + /* Decrement non-zero try count */ + vb2_nv_set(ctx, VB2_NV_TRY_COUNT, tries - 1); + } + + /* Set context flag if we're using slot B */ + if (sd->fw_slot) + ctx->flags |= VB2_CONTEXT_FW_SLOT_B; + + /* Set status flag */ + sd->status |= VB2_SD_STATUS_CHOSE_SLOT; + + return VB2_SUCCESS; +} diff --git a/firmware/2lib/2stub.c b/firmware/2lib/2stub.c new file mode 100644 index 00000000..a544a4fb --- /dev/null +++ b/firmware/2lib/2stub.c @@ -0,0 +1,23 @@ +/* Copyright (c) 2014 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. + * + * Stub API implementations which should be implemented by the caller. + */ + +#include "2sysincludes.h" +#include "2api.h" + +int vb2ex_tpm_clear_owner(struct vb2_context *ctx) +{ + return VB2_SUCCESS; +} + +int vb2ex_read_resource(struct vb2_context *ctx, + enum vb2_resource_index index, + uint32_t offset, + void *buf, + uint32_t size) +{ + return VB2_SUCCESS; +} diff --git a/firmware/2lib/include/2api.h b/firmware/2lib/include/2api.h index 3fb75895..3a4772b3 100644 --- a/firmware/2lib/include/2api.h +++ b/firmware/2lib/include/2api.h @@ -143,4 +143,44 @@ struct vb2_context { uint32_t workbuf_used; }; +enum vb2_resource_index { + + /* Google binary block */ + VB2_RES_GBB, + + /* + * Verified boot block (keyblock+preamble). Use VB2_CONTEXT_FW_SLOT_B + * to determine whether this refers to slot A or slot B; vboot will + * set that flag to the proper state before reading the vblock. + */ + VB2_RES_FW_VBLOCK, +}; + +/*****************************************************************************/ +/* APIs provided by the caller to verified boot */ + +/** + * Clear the TPM owner. + * + * @param ctx Vboot context + * @return VB2_SUCCESS, or error code on error. + */ +int vb2ex_tpm_clear_owner(struct vb2_context *ctx); + +/** + * Read a verified boot resource. + * + * @param ctx Vboot context + * @param index Resource index to read + * @param offset Byte offset within resource to start at + * @param buf Destination for data + * @param size Amount of data to read + * @return VB2_SUCCESS, or error code on error. + */ +int vb2ex_read_resource(struct vb2_context *ctx, + enum vb2_resource_index index, + uint32_t offset, + void *buf, + uint32_t size); + #endif /* VBOOT_2_API_H_ */ diff --git a/firmware/2lib/include/2misc.h b/firmware/2lib/include/2misc.h index bf707ed9..f2e259e4 100644 --- a/firmware/2lib/include/2misc.h +++ b/firmware/2lib/include/2misc.h @@ -10,6 +10,8 @@ #include "2api.h" +struct vb2_gbb_header; + /** * Get the shared data pointer from the vboot context * @@ -21,6 +23,39 @@ static __inline struct vb2_shared_data *vb2_get_sd(struct vb2_context *ctx) { } /** + * Initialize a work buffer from the vboot context. + * + * This sets the work buffer to the unused portion of the context work buffer. + * + * @param ctx Vboot context + * @param wb Work buffer to initialize + */ +void vb2_workbuf_from_ctx(struct vb2_context *ctx, struct vb2_workbuf *wb); + +/** + * Read the GBB header. + * + * @param ctx Vboot context + * @param gbb Destination for header + * @return VB2_SUCCESS, or non-zero if error. + */ +int vb2_read_gbb_header(struct vb2_context *ctx, struct vb2_gbb_header *gbb); + +/** + * Handle vboot failure. + * + * If the failure occurred after choosing a firmware slot, and the other + * firmware slot is not known-bad, try the other firmware slot after reboot. + * + * If the failure occurred before choosing a firmware slot, or both slots have + * failed in successive boots, request recovery. + * + * @param reason Recovery reason + * @param subcode Recovery subcode + */ +void vb2_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode); + +/** * Set up the verified boot context data, if not already set up. * * This uses ctx->workbuf_used=0 as a flag to indicate that the data has not @@ -32,4 +67,49 @@ static __inline struct vb2_shared_data *vb2_get_sd(struct vb2_context *ctx) { */ int vb2_init_context(struct vb2_context *ctx); +/** + * Check for recovery reasons we can determine early in the boot process. + * + * On exit, check ctx->flags for VB2_CONTEXT_RECOVERY_MODE; if present, jump to + * the recovery path instead of continuing with normal boot. This is the only + * direct path to recovery mode. All other errors later in the boot process + * should induce a reboot instead of jumping to recovery, so that recovery mode + * starts from a consistent firmware state. + * + * @param ctx Vboot context + */ +void vb2_check_recovery(struct vb2_context *ctx); + +/** + * Parse the GBB header. + * + * @param ctx Vboot context + * @return VB2_SUCCESS, or error code on error. + */ +int vb2_fw_parse_gbb(struct vb2_context *ctx); + +/** + * Check developer switch position. + * + * @param ctx Vboot context + * @return VB2_SUCCESS, or error code on error. + */ +int vb2_check_dev_switch(struct vb2_context *ctx); + +/** + * Check if we need to clear the TPM owner. + * + * @param ctx Vboot context + * @return VB2_SUCCESS, or error code on error. + */ +int vb2_check_tpm_clear(struct vb2_context *ctx); + +/** + * Decide which firmware slot to try this boot. + * + * @param ctx Vboot context + * @return VB2_SUCCESS, or error code on error. + */ +int vb2_select_fw_slot(struct vb2_context *ctx); + #endif /* VBOOT_REFERENCE_VBOOT_2MISC_H_ */ diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h index e0c4cbcb..7c61a49c 100644 --- a/firmware/2lib/include/2return_codes.h +++ b/firmware/2lib/include/2return_codes.h @@ -9,8 +9,8 @@ /* * Return codes from verified boot functions. * - * TODO: Go through code and replace VB2_ERROR_UNKNOWN with more specific - * error codes, and make the existing codes more consistent and useful. + * Note that other values may be passed through from vb2ex_*() calls; see + * the comment for VB2_ERROR_EX below. */ enum vb2_return_code { /* Success - no error */ @@ -233,46 +233,117 @@ enum vb2_return_code { /* Work buffer unaligned in vb2_init_context() */ VB2_ERROR_INITCTX_WORKBUF_ALIGN, + /* Work buffer too small in vb2_fw_parse_gbb() */ + VB2_ERROR_GBB_WORKBUF, + + /* Bad magic number in vb2_read_gbb_header() */ + VB2_ERROR_GBB_MAGIC, + + /* Incompatible version in vb2_read_gbb_header() */ + VB2_ERROR_GBB_VERSION, + + /* Old version in vb2_read_gbb_header() */ + VB2_ERROR_GBB_TOO_OLD, + + /* Header size too small in vb2_read_gbb_header() */ + VB2_ERROR_GBB_HEADER_SIZE, + + /* Work buffer too small for root key in vb2_verify_fw_keyblock() */ + VB2_ERROR_FW_KEYBLOCK_WORKBUF_ROOT_KEY, + + /* Work buffer too small for header in vb2_verify_fw_keyblock() */ + VB2_ERROR_FW_KEYBLOCK_WORKBUF_HEADER, + + /* Work buffer too small for keyblock in vb2_verify_fw_keyblock() */ + VB2_ERROR_FW_KEYBLOCK_WORKBUF, + + /* Keyblock version out of range in vb2_verify_fw_keyblock() */ + VB2_ERROR_FW_KEYBLOCK_VERSION_RANGE, + + /* Keyblock version rollback in vb2_verify_fw_keyblock() */ + VB2_ERROR_FW_KEYBLOCK_VERSION_ROLLBACK, + + /* Missing firmware data key in vb2_verify_fw_preamble2() */ + VB2_ERROR_FW_PREAMBLE2_DATA_KEY, + + /* Work buffer too small for header in vb2_verify_fw_preamble2() */ + VB2_ERROR_FW_PREAMBLE2_WORKBUF_HEADER, + + /* Work buffer too small for preamble in vb2_verify_fw_preamble2() */ + VB2_ERROR_FW_PREAMBLE2_WORKBUF, + + /* Firmware version out of range in vb2_verify_fw_preamble2() */ + VB2_ERROR_FW_PREAMBLE2_VERSION_RANGE, + + /* Firmware version rollback in vb2_verify_fw_preamble2() */ + VB2_ERROR_FW_PREAMBLE2_VERSION_ROLLBACK, + /********************************************************************** - * TODO: errors which must still be made specific + * API-level errors */ - VB2_ERROR_TODO = VB2_ERROR_BASE + 0xff0000, + VB2_ERROR_API = VB2_ERROR_BASE + 0x090000, + + /* Bag tag in vb2api_init_hash() */ + VB2_ERROR_API_INIT_HASH_TAG, + + /* Preamble not present in vb2api_init_hash() */ + VB2_ERROR_API_INIT_HASH_PREAMBLE, + + /* Work buffer too small in vb2api_init_hash() */ + VB2_ERROR_API_INIT_HASH_WORKBUF, + + /* Missing firmware data key in vb2api_init_hash() */ + VB2_ERROR_API_INIT_HASH_DATA_KEY, - /* Work buffer too small */ - VB2_ERROR_WORKBUF_TOO_SMALL, + /* Uninitialized work area in vb2api_extend_hash() */ + VB2_ERROR_API_EXTEND_HASH_WORKBUF, - /* Buffer too small (other than the work buffer) */ - VB2_ERROR_BUFFER_TOO_SMALL, + /* Too much data hashed in vb2api_extend_hash() */ + VB2_ERROR_API_EXTEND_HASH_SIZE, - /* Buffer unaligned */ - VB2_ERROR_BUFFER_UNALIGNED, + /* Preamble not present in vb2api_check_hash() */ + VB2_ERROR_API_CHECK_HASH_PREAMBLE, - /* Bad GBB header */ - VB2_ERROR_BAD_GBB_HEADER, + /* Uninitialized work area in vb2api_check_hash() */ + VB2_ERROR_API_CHECK_HASH_WORKBUF, - /* Bad algorithm - unknown, or unsupported */ - VB2_ERROR_BAD_ALGORITHM, + /* Wrong amount of data hashed in vb2api_extend_hash() */ + VB2_ERROR_API_CHECK_HASH_SIZE, - /* Signature check failed */ - VB2_ERROR_BAD_SIGNATURE, + /* Bag tag in vb2api_check_hash() */ + VB2_ERROR_API_CHECK_HASH_TAG, - /* Bad key */ - VB2_ERROR_BAD_KEY, + /* Missing firmware data key in vb2api_check_hash() */ + VB2_ERROR_API_CHECK_HASH_DATA_KEY, - /* Bad keyblock */ - VB2_ERROR_BAD_KEYBLOCK, + /* Siganature size mismatch in vb2api_check_hash() */ + VB2_ERROR_API_CHECK_HASH_SIG_SIZE, - /* Bad preamble */ - VB2_ERROR_BAD_PREAMBLE, + /* Preamble not present in vb2api_get_kernel_subkey() */ + VB2_ERROR_API_KERNEL_SUBKEY_PREAMBLE, - /* Bad firmware keyblock version (out of range, or rollback) */ - VB2_ERROR_FW_KEYBLOCK_VERSION, + /* Destination buffer too small in vb2api_get_kernel_subkey() */ + VB2_ERROR_API_KERNEL_SUBKEY_DEST_SIZE, - /* Bad firmware version (out of range, or rollback) */ - VB2_ERROR_FW_VERSION, + /* Phase one needs recovery mode */ + VB2_ERROR_API_PHASE1_RECOVERY, - /* Bad hash tag */ - VB2_ERROR_BAD_TAG, + /********************************************************************** + * Errors which may be generated by implementations of vb2ex functions. + * Implementation may also return its own specific errors, which should + * NOT be in the range VB2_ERROR_BASE...VB2_ERROR_MAX to avoid + * conflicting with future vboot2 error codes. + */ + VB2_ERROR_EX = VB2_ERROR_BASE + 0x0a0000, + + /* Resource index not found */ + VB2_ERROR_EX_READ_RESOURCE_INDEX, + + /* Size of resource not big enough for requested offset and/or size */ + VB2_ERROR_EX_READ_RESOURCE_SIZE, + + /* TPM clear owner failed */ + VB2_ERROR_EX_TPM_CLEAR_OWNER, /********************************************************************** * Highest non-zero error generated inside vboot library. Note that @@ -280,7 +351,6 @@ enum vb2_return_code { * still be outside this range. */ VB2_ERROR_MAX = VB2_ERROR_BASE + 0xffffff, - }; #endif /* VBOOT_2_RETURN_CODES_H_ */ diff --git a/tests/vb2_misc_tests.c b/tests/vb2_misc_tests.c index a747a26f..74837c5e 100644 --- a/tests/vb2_misc_tests.c +++ b/tests/vb2_misc_tests.c @@ -5,27 +5,86 @@ * Tests for misc library */ -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "test_common.h" -#include "vboot_common.h" - +#include "2sysincludes.h" #include "2api.h" #include "2common.h" #include "2misc.h" +#include "2nvstorage.h" +#include "2secdata.h" + +#include "test_common.h" + +/* Common context for tests */ +static uint8_t workbuf[VB2_WORKBUF_RECOMMENDED_SIZE] + __attribute__ ((aligned (16))); +static struct vb2_context cc; +static struct vb2_shared_data *sd; + +/* Mocked function data */ +enum vb2_resource_index mock_resource_index; +void *mock_resource_ptr; +uint32_t mock_resource_size; +int mock_tpm_clear_called; +int mock_tpm_clear_retval; + + +static void reset_common_data(void) +{ + memset(workbuf, 0xaa, sizeof(workbuf)); + + memset(&cc, 0, sizeof(cc)); + cc.workbuf = workbuf; + cc.workbuf_size = sizeof(workbuf); + + vb2_init_context(&cc); + sd = vb2_get_sd(&cc); + + vb2_nv_init(&cc); + + vb2_secdata_create(&cc); + vb2_secdata_init(&cc); + + mock_tpm_clear_called = 0; + mock_tpm_clear_retval = VB2_SUCCESS; +}; + +/* Mocked functions */ + +int vb2ex_read_resource(struct vb2_context *ctx, + enum vb2_resource_index index, + uint32_t offset, + void *buf, + uint32_t size) +{ + if (index != mock_resource_index) + return VB2_ERROR_EX_READ_RESOURCE_INDEX; + + if (offset > mock_resource_size || offset + size > mock_resource_size) + return VB2_ERROR_EX_READ_RESOURCE_SIZE; + + memcpy(buf, (uint8_t *)mock_resource_ptr + offset, size); + return VB2_SUCCESS; +} -static void misc_test(void) +int vb2ex_tpm_clear_owner(struct vb2_context *ctx) { - uint8_t workbuf[VB2_WORKBUF_RECOMMENDED_SIZE]; + mock_tpm_clear_called++; + return mock_tpm_clear_retval; +} + +/* Tests */ + +static void init_context_tests(void) +{ + /* Use our own context struct so we can re-init it */ struct vb2_context c = { .workbuf = workbuf, .workbuf_size = sizeof(workbuf), }; + reset_common_data(); + TEST_SUCC(vb2_init_context(&c), "Init context good"); TEST_EQ(c.workbuf_used, sizeof(struct vb2_shared_data), "Init vbsd"); @@ -48,9 +107,357 @@ static void misc_test(void) VB2_ERROR_INITCTX_WORKBUF_ALIGN, "Init unaligned"); } +static void misc_tests(void) +{ + struct vb2_workbuf wb; + + reset_common_data(); + cc.workbuf_used = 16; + + vb2_workbuf_from_ctx(&cc, &wb); + + TEST_PTR_EQ(wb.buf, workbuf + 16, "vb_workbuf_from_ctx() buf"); + TEST_EQ(wb.size, cc.workbuf_size - 16, "vb_workbuf_from_ctx() size"); +} + +static void gbb_tests(void) +{ + struct vb2_gbb_header gbb = { + .signature = {'$', 'G', 'B', 'B'}, + .major_version = VB2_GBB_MAJOR_VER, + .minor_version = VB2_GBB_MINOR_VER, + .header_size = sizeof(struct vb2_gbb_header), + .flags = 0x1234, + .rootkey_offset = 240, + .rootkey_size = 1040, + }; + + struct vb2_gbb_header gbbdest; + + reset_common_data(); + + /* Good contents */ + mock_resource_index = VB2_RES_GBB; + mock_resource_ptr = &gbb; + mock_resource_size = sizeof(gbb); + TEST_SUCC(vb2_read_gbb_header(&cc, &gbbdest), "read gbb header good"); + TEST_SUCC(memcmp(&gbb, &gbbdest, sizeof(gbb)), "read gbb contents"); + + mock_resource_index = VB2_RES_GBB + 1; + TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), + VB2_ERROR_EX_READ_RESOURCE_INDEX, "read gbb header missing"); + mock_resource_index = VB2_RES_GBB; + + gbb.signature[0]++; + TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), + VB2_ERROR_GBB_MAGIC, "read gbb header bad magic"); + gbb.signature[0]--; + + gbb.major_version++; + TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), + VB2_ERROR_GBB_VERSION, "read gbb header major version"); + gbb.major_version--; + + gbb.minor_version++; + TEST_SUCC(vb2_read_gbb_header(&cc, &gbbdest), + "read gbb header minor++"); + gbb.minor_version -= 2; + TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), + VB2_ERROR_GBB_TOO_OLD, "read gbb header minor version old"); + gbb.minor_version++; + + gbb.header_size--; + TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), + VB2_ERROR_GBB_HEADER_SIZE, "read gbb header size"); + TEST_EQ(vb2_fw_parse_gbb(&cc), + VB2_ERROR_GBB_HEADER_SIZE, "parse gbb failure"); + gbb.header_size++; + + /* Parse GBB */ + TEST_SUCC(vb2_fw_parse_gbb(&cc), "parse gbb"); + TEST_EQ(sd->gbb_flags, gbb.flags, "gbb flags"); + TEST_EQ(sd->gbb_rootkey_offset, gbb.rootkey_offset, "rootkey offset"); + TEST_EQ(sd->gbb_rootkey_size, gbb.rootkey_size, "rootkey size"); + + /* Workbuf failure */ + reset_common_data(); + cc.workbuf_used = cc.workbuf_size - 4; + TEST_EQ(vb2_fw_parse_gbb(&cc), + VB2_ERROR_GBB_WORKBUF, "parse gbb no workbuf"); +} + +static void fail_tests(void) +{ + /* Early fail (before even NV init) */ + reset_common_data(); + sd->status &= ~VB2_SD_STATUS_NV_INIT; + vb2_fail(&cc, 1, 2); + TEST_NEQ(sd->status & VB2_SD_STATUS_NV_INIT, 0, "vb2_fail inits NV"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), + 1, "vb2_fail request"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), + 2, "vb2_fail subcode"); + + /* Repeated fail doesn't overwrite the error code */ + vb2_fail(&cc, 3, 4); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), + 1, "vb2_fail repeat"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), + 2, "vb2_fail repeat2"); + + /* Fail with other slot good doesn't trigger recovery */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_TRY_COUNT, 3); + vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN); + sd->status |= VB2_SD_STATUS_CHOSE_SLOT; + sd->fw_slot = 0; + sd->last_fw_slot = 1; + sd->last_fw_result = VB2_FW_RESULT_UNKNOWN; + vb2_fail(&cc, 5, 6); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 0, "vb2_failover"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), + VB2_FW_RESULT_FAILURE, "vb2_fail this fw"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 0, "vb2_fail use up tries"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 1, "vb2_fail try other slot"); + + /* Fail with other slot already failing triggers recovery */ + reset_common_data(); + sd->status |= VB2_SD_STATUS_CHOSE_SLOT; + sd->fw_slot = 1; + sd->last_fw_slot = 0; + sd->last_fw_result = VB2_FW_RESULT_FAILURE; + vb2_fail(&cc, 7, 8); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 7, + "vb2_fail both slots bad"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), + VB2_FW_RESULT_FAILURE, "vb2_fail this fw"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 0, "vb2_fail try other slot"); +} + +static void recovery_tests(void) +{ + /* No recovery */ + reset_common_data(); + vb2_check_recovery(&cc); + TEST_EQ(sd->recovery_reason, 0, "No recovery reason"); + TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, + 0, "Not manual recovery"); + TEST_EQ(cc.flags & VB2_CONTEXT_RECOVERY_MODE, + 0, "Not recovery mode"); + + /* From request */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_RECOVERY_REQUEST, 3); + vb2_check_recovery(&cc); + TEST_EQ(sd->recovery_reason, 3, "Recovery reason from request"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 0, "NV cleared"); + TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, + 0, "Not manual recovery"); + TEST_NEQ(cc.flags & VB2_CONTEXT_RECOVERY_MODE, + 0, "Recovery mode"); + + /* From request, but already failed */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_RECOVERY_REQUEST, 4); + sd->recovery_reason = 5; + vb2_check_recovery(&cc); + TEST_EQ(sd->recovery_reason, 5, "Recovery reason already failed"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), + 0, "NV still cleared"); + + /* Override */ + reset_common_data(); + sd->recovery_reason = 6; + cc.flags |= VB2_CONTEXT_FORCE_RECOVERY_MODE; + vb2_check_recovery(&cc); + TEST_EQ(sd->recovery_reason, VB2_RECOVERY_RO_MANUAL, + "Recovery reason forced"); + TEST_NEQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, + 0, "SD flag set"); +} + +static void dev_switch_tests(void) +{ + uint32_t v; + + /* Normal mode */ + reset_common_data(); + TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode off"); + TEST_EQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd not in dev"); + TEST_EQ(cc.flags & VB2_CONTEXT_DEVELOPER_MODE, 0, "ctx not in dev"); + TEST_EQ(mock_tpm_clear_called, 0, "no tpm clear"); + + /* Dev mode */ + reset_common_data(); + vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, + (VB2_SECDATA_FLAG_DEV_MODE | + VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER)); + TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode on"); + TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); + TEST_NEQ(cc.flags & VB2_CONTEXT_DEVELOPER_MODE, 0, "ctx in dev"); + TEST_EQ(mock_tpm_clear_called, 0, "no tpm clear"); + + /* Any normal mode boot clears dev boot flags */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_DEV_BOOT_USB, 1); + vb2_nv_set(&cc, VB2_NV_DEV_BOOT_LEGACY, 1); + vb2_nv_set(&cc, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1); + TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode off"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_USB), + 0, "cleared dev boot usb"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_LEGACY), + 0, "cleared dev boot legacy"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_SIGNED_ONLY), + 0, "cleared dev boot signed only"); + + /* Normal-dev transition clears TPM */ + reset_common_data(); + vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, VB2_SECDATA_FLAG_DEV_MODE); + TEST_SUCC(vb2_check_dev_switch(&cc), "to dev mode"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); + vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); + TEST_EQ(v, (VB2_SECDATA_FLAG_DEV_MODE | + VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER), + "last boot developer now"); + + /* Dev-normal transition clears TPM too */ + reset_common_data(); + vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, + VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER); + TEST_SUCC(vb2_check_dev_switch(&cc), "from dev mode"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); + vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); + TEST_EQ(v, 0, "last boot not developer now"); + + /* Disable dev mode */ + reset_common_data(); + vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, + (VB2_SECDATA_FLAG_DEV_MODE | + VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER)); + vb2_nv_set(&cc, VB2_NV_DISABLE_DEV_REQUEST, 1); + TEST_SUCC(vb2_check_dev_switch(&cc), "disable dev request"); + TEST_EQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd not in dev"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_DISABLE_DEV_REQUEST), + 0, "request cleared"); + + /* Force enabled by gbb */ + reset_common_data(); + sd->gbb_flags |= VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON; + TEST_SUCC(vb2_check_dev_switch(&cc), "dev on via gbb"); + TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); + vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); + TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, + "doesn't set dev on in secdata but does set last boot dev"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); + + /* Force enabled by ctx flag */ + reset_common_data(); + cc.flags |= VB2_CONTEXT_FORCE_DEVELOPER_MODE; + TEST_SUCC(vb2_check_dev_switch(&cc), "dev on via ctx flag"); + TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); + vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); + TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, + "doesn't set dev on in secdata but does set last boot dev"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); + + /* Simulate clear owner failure */ + reset_common_data(); + vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, + VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER); + mock_tpm_clear_retval = VB2_ERROR_EX_TPM_CLEAR_OWNER; + TEST_EQ(vb2_check_dev_switch(&cc), + VB2_ERROR_EX_TPM_CLEAR_OWNER, "tpm clear fail"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); + vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); + TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, + "last boot still developer"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), + VB2_RECOVERY_TPM_CLEAR_OWNER, "requests recovery"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), + (uint8_t)VB2_ERROR_EX_TPM_CLEAR_OWNER, "recovery subcode"); +} + +static void tpm_clear_tests(void) +{ + /* No clear request */ + reset_common_data(); + TEST_SUCC(vb2_check_tpm_clear(&cc), "no clear request"); + TEST_EQ(mock_tpm_clear_called, 0, "tpm not cleared"); + + /* Successful request */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 1); + TEST_SUCC(vb2_check_tpm_clear(&cc), "clear request"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST), + 0, "request cleared"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_DONE), + 1, "done set"); + TEST_EQ(mock_tpm_clear_called, 1, "tpm cleared"); + + /* Failed request */ + reset_common_data(); + mock_tpm_clear_retval = VB2_ERROR_EX_TPM_CLEAR_OWNER; + vb2_nv_set(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 1); + TEST_EQ(vb2_check_tpm_clear(&cc), + VB2_ERROR_EX_TPM_CLEAR_OWNER, "clear failure"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST), + 0, "request cleared"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_DONE), + 0, "done not set"); +} + +static void select_slot_tests(void) +{ + /* Slot A */ + reset_common_data(); + TEST_SUCC(vb2_select_fw_slot(&cc), "select slot A"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), + VB2_FW_RESULT_UNKNOWN, "result unknown"); + TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); + TEST_EQ(sd->fw_slot, 0, "selected A"); + TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B"); + + /* Slot B */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_TRY_NEXT, 1); + TEST_SUCC(vb2_select_fw_slot(&cc), "select slot B"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), + VB2_FW_RESULT_UNKNOWN, "result unknown"); + TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); + TEST_EQ(sd->fw_slot, 1, "selected B"); + TEST_NEQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "ctx says choose B"); + + /* Slot A ran out of tries */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_TRYING); + TEST_SUCC(vb2_select_fw_slot(&cc), "select slot A out of tries"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 1, "try B next"); + TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); + TEST_EQ(sd->fw_slot, 1, "selected B"); + TEST_NEQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "ctx says choose B"); + + /* Slot A used up a try */ + reset_common_data(); + vb2_nv_set(&cc, VB2_NV_TRY_COUNT, 3); + TEST_SUCC(vb2_select_fw_slot(&cc), "try slot A"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), + VB2_FW_RESULT_TRYING, "result trying"); + TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); + TEST_EQ(sd->fw_slot, 0, "selected A"); + TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B"); + TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 2, "tries decremented"); +} + int main(int argc, char* argv[]) { - misc_test(); + init_context_tests(); + misc_tests(); + gbb_tests(); + fail_tests(); + recovery_tests(); + dev_switch_tests(); + tpm_clear_tests(); + select_slot_tests(); return gTestSuccess ? 0 : 255; } |