Add VbSharedData debug output to LoadKernel()
Change-Id: Id2837c83877520067e46e2434460349ef001b7b9
R=dlaurie@chromium.org
BUG=chrome-os-partner:2748
TEST=nmake uefi64; boot in normal and recovery modes
Review URL: http://codereview.chromium.org/6688035
diff --git a/firmware/include/vboot_struct.h b/firmware/include/vboot_struct.h
index 7cf8803..f82f7e9 100644
--- a/firmware/include/vboot_struct.h
+++ b/firmware/include/vboot_struct.h
@@ -131,6 +131,8 @@
#define EXPECTED_VBKERNELPREAMBLEHEADER_SIZE 96
+/* Constants and sub-structures for VbSharedDataHeader */
+
/* Magic number for recognizing VbSharedDataHeader ("VbSD") */
#define VB_SHARED_DATA_MAGIC 0x44536256
@@ -141,14 +143,14 @@
/* Flags for VbSharedDataHeader */
/* LoadFirmware() tried firmware B because of VbNvStorage firmware B tries */
#define VBSD_FWB_TRIED 0x00000001
-/* LoadKernel() verified the kernel keyblock using the kernel subkey from
+/* LoadKernel() verified the good kernel keyblock using the kernel subkey from
* the firmware. If this flag is not present, it just used the hash of the
* kernel keyblock. */
#define VBSD_KERNEL_KEY_VERIFIED 0x00000002
/* LoadFirmware() was told the developer switch was on */
#define VBSD_LF_DEV_SWITCH_ON 0x00000004
-/* Result codes for checking firmware A and B */
+/* Result codes for VbSharedDataHeader.check_fw_a_result (and b_result) */
#define VBSD_LF_CHECK_NOT_DONE 0
#define VBSD_LF_CHECK_DEV_MISMATCH 1
#define VBSD_LF_CHECK_REC_MISMATCH 2
@@ -163,6 +165,81 @@
#define VBSD_LF_CHECK_VERIFY_BODY 11
#define VBSD_LF_CHECK_VALID 12
+/* Boot mode for VbSharedDataHeader.lk_boot_mode */
+#define VBSD_LK_BOOT_MODE_RECOVERY 0
+#define VBSD_LK_BOOT_MODE_NORMAL 1
+#define VBSD_LK_BOOT_MODE_DEVELOPER 2
+
+/* Flags for VbSharedDataKernelPart.flags */
+#define VBSD_LKP_FLAG_KEY_BLOCK_VALID 0x01
+
+/* Result codes for VbSharedDataKernelPart.check_result */
+#define VBSD_LKP_CHECK_NOT_DONE 0
+#define VBSD_LKP_CHECK_TOO_SMALL 1
+#define VBSD_LKP_CHECK_READ_START 2
+#define VBSD_LKP_CHECK_KEY_BLOCK_SIG 3
+#define VBSD_LKP_CHECK_KEY_BLOCK_HASH 4
+#define VBSD_LKP_CHECK_DEV_MISMATCH 5
+#define VBSD_LKP_CHECK_REC_MISMATCH 6
+#define VBSD_LKP_CHECK_KEY_ROLLBACK 7
+#define VBSD_LKP_CHECK_DATA_KEY_PARSE 8
+#define VBSD_LKP_CHECK_VERIFY_PREAMBLE 9
+#define VBSD_LKP_CHECK_KERNEL_ROLLBACK 10
+#define VBSD_LKP_CHECK_PREAMBLE_VALID 11
+#define VBSD_LKP_CHECK_BODY_ADDRESS 12
+#define VBSD_LKP_CHECK_BODY_OFFSET 13
+#define VBSD_LKP_CHECK_BODY_EXCEEDS_MEM 15
+#define VBSD_LKP_CHECK_BODY_EXCEEDS_PART 16
+#define VBSD_LKP_CHECK_READ_DATA 17
+#define VBSD_LKP_CHECK_VERIFY_DATA 18
+#define VBSD_LKP_CHECK_KERNEL_GOOD 19
+
+
+/* Information about a single kernel partition check in LoadKernel() */
+typedef struct VbSharedDataKernelPart {
+ uint64_t sector_start; /* Start sector of partition */
+ uint64_t sector_count; /* Sector count of partition */
+ uint32_t combined_version; /* Combined key+kernel version */
+ uint8_t gpt_index; /* Index of partition in GPT */
+ uint8_t check_result; /* Check result; see VBSD_LKP_CHECK_* */
+ uint8_t flags; /* Flags (see VBSD_LKP_FLAG_* */
+} VbSharedDataKernelPart;
+
+/* Number of kernel partitions to track per call. Must be power of 2. */
+#define VBSD_MAX_KERNEL_PARTS 8
+
+/* Flags for VbSharedDataKernelCall.flags */
+/* Error initializing TPM in recovery mode */
+#define VBSD_LK_FLAG_REC_TPM_INIT_ERROR 0x00000001
+
+/* Result codes for VbSharedDataKernelCall.check_result */
+#define VBSD_LKC_CHECK_NOT_DONE 0
+#define VBSD_LKC_CHECK_DEV_SWITCH_MISMATCH 1
+#define VBSD_LKC_CHECK_GPT_READ_ERROR 2
+#define VBSD_LKC_CHECK_GPT_PARSE_ERROR 3
+#define VBSD_LKC_CHECK_GOOD_PARTITION 4
+#define VBSD_LKC_CHECK_INVALID_PARTITIONS 5
+#define VBSD_LKC_CHECK_NO_PARTITIONS 6
+
+/* Information about a single call to LoadKernel() */
+typedef struct VbSharedDataKernelCall {
+ uint32_t boot_flags; /* Bottom 32 bits of flags passed in
+ * LoadKernelParams.boot_flags */
+ uint32_t flags; /* Debug flags; see VBSD_LK_FLAG_* */
+ uint64_t sector_count; /* Number of sectors on drive */
+ uint32_t sector_size; /* Sector size in bytes */
+ uint8_t check_result; /* Check result; see VBSD_LKC_CHECK_* */
+ uint8_t boot_mode; /* Boot mode for LoadKernel(); see
+ * VBSD_LK_BOOT_MODE_* constants */
+ uint8_t test_error_num; /* Test error number, if non-zero */
+ uint8_t return_code; /* Return code from LoadKernel() */
+ uint8_t kernel_parts_found; /* Number of kernel partitions found */
+ VbSharedDataKernelPart parts[VBSD_MAX_KERNEL_PARTS]; /* Data on kernels */
+} VbSharedDataKernelCall;
+
+/* Number of kernel calls to track. Must be power of 2. */
+#define VBSD_MAX_KERNEL_CALLS 4
+
/* Data shared between LoadFirmware(), LoadKernel(), and OS.
*
* The boot process is:
@@ -200,6 +277,11 @@
uint64_t timer_load_kernel_enter; /* LoadKernel() - enter */
uint64_t timer_load_kernel_exit; /* LoadKernel() - exit */
+ /* Information stored in TPM, as retrieved by firmware */
+ uint32_t fw_version_tpm; /* Current firmware version in TPM */
+ uint32_t kernel_version_tpm; /* Current kernel version in TPM */
+
+ /* Debugging information from LoadFirmware() */
uint8_t check_fw_a_result; /* Result of checking RW firmware A */
uint8_t check_fw_b_result; /* Result of checking RW firmware B */
uint8_t firmware_index; /* Firmware index returned by
@@ -208,8 +290,17 @@
* LoadFirmware() */
uint32_t fw_version_lowest; /* Firmware lowest version found */
- uint32_t fw_version_tpm; /* Current firmware version in TPM */
- uint32_t kernel_version_tpm; /* Current kernel version in TPM */
+ /* Debugging information from LoadKernel() */
+ uint32_t lk_call_count; /* Number of times LoadKernel() called */
+ VbSharedDataKernelCall lk_calls[VBSD_MAX_KERNEL_CALLS]; /* Info on calls */
+
+ /* Offset and size of supplemental kernel data. Reserve space for these
+ * fields now, so that future LoadKernel() versions can store information
+ * there without needing to shift down whatever data the original
+ * LoadFirmware() might have put immediately following its
+ * VbSharedDataHeader. */
+ uint64_t kernel_supplemental_offset;
+ uint64_t kernel_supplemental_size;
/* After read-only firmware which uses version 1 is released, any additional
* fields must be added below, and the struct version must be increased.
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index 204874f..302a7f6 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -21,9 +21,9 @@
#define LOWEST_TPM_VERSION 0xffffffff
typedef enum BootMode {
- kBootNormal, /* Normal firmware */
- kBootDev, /* Dev firmware AND dev switch is on */
- kBootRecovery /* Recovery firmware, regardless of dev switch position */
+ kBootRecovery = 0, /* Recovery firmware, regardless of dev switch position */
+ kBootNormal = 1, /* Normal firmware */
+ kBootDev = 2 /* Dev firmware AND dev switch is on */
} BootMode;
@@ -121,6 +121,7 @@
int LoadKernel(LoadKernelParams* params) {
VbSharedDataHeader* shared = (VbSharedDataHeader*)params->shared_data_blob;
+ VbSharedDataKernelCall* shcall = NULL;
VbNvContext* vnc = params->nv_context;
GoogleBinaryBlockHeader* gbb = (GoogleBinaryBlockHeader*)params->gbb_data;
VbPublicKey* kernel_subkey;
@@ -161,11 +162,51 @@
params->bootloader_address = 0;
params->bootloader_size = 0;
+ /* Calculate switch positions and boot mode */
+ rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0);
+ dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0);
+ if (rec_switch)
+ boot_mode = kBootRecovery;
+ else if (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags)
+ boot_mode = kBootDev;
+ else {
+ /* Normal firmware */
+ boot_mode = kBootNormal;
+ dev_switch = 0; /* Always do a fully verified boot */
+ }
+
+ if (kBootRecovery == boot_mode) {
+ /* Initialize the shared data structure, since LoadFirmware() didn't do it
+ * for us. */
+ if (0 != VbSharedDataInit(shared, params->shared_data_size)) {
+ /* Error initializing the shared data, but we can keep going. We just
+ * can't use the shared data. */
+ VBDEBUG(("Shared data init error\n"));
+ params->shared_data_size = 0;
+ shared = NULL;
+ }
+ }
+
+ if (shared) {
+ /* Set up tracking for this call. This wraps around if called many times,
+ * so we need to initialize the call entry each time. */
+ shcall = shared->lk_calls + (shared->lk_call_count
+ & (VBSD_MAX_KERNEL_CALLS - 1));
+ Memset(shcall, 0, sizeof(VbSharedDataKernelCall));
+ shcall->boot_flags = (uint32_t)params->boot_flags;
+ shcall->boot_mode = boot_mode;
+ shcall->sector_size = (uint32_t)params->bytes_per_lba;
+ shcall->sector_count = params->ending_lba + 1;
+ shared->lk_call_count++;
+ }
+
/* Handle test errors */
VbNvGet(vnc, VBNV_TEST_ERROR_FUNC, &test_err);
if (VBNV_TEST_ERROR_LOAD_KERNEL == test_err) {
/* Get error code */
VbNvGet(vnc, VBNV_TEST_ERROR_NUM, &test_err);
+ if (shcall)
+ shcall->test_error_num = (uint8_t)test_err;
/* Clear test params so we don't repeat the error */
VbNvSet(vnc, VBNV_TEST_ERROR_FUNC, 0);
VbNvSet(vnc, VBNV_TEST_ERROR_NUM, 0);
@@ -192,43 +233,25 @@
goto LoadKernelExit;
}
- rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0);
- dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0);
-
- if (rec_switch)
- boot_mode = kBootRecovery;
- else if (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags) {
- if (!dev_switch) {
+ if (kBootDev == boot_mode && !dev_switch) {
/* Dev firmware should be signed such that it never boots with the dev
* switch is off; so something is terribly wrong. */
VBDEBUG(("LoadKernel() called with dev firmware but dev switch off\n"));
+ if (shcall)
+ shcall->check_result = VBSD_LKC_CHECK_DEV_SWITCH_MISMATCH;
recovery = VBNV_RECOVERY_RW_DEV_MISMATCH;
goto LoadKernelExit;
}
- boot_mode = kBootDev;
- } else {
- /* Normal firmware */
- boot_mode = kBootNormal;
- dev_switch = 0; /* Always do a fully verified boot */
- }
if (kBootRecovery == boot_mode) {
- /* Initialize the shared data structure, since LoadFirmware() didn't do it
- * for us. */
- if (0 != VbSharedDataInit(shared, params->shared_data_size)) {
- /* Error initializing the shared data, but we can keep going. We just
- * can't use the shared data. */
- VBDEBUG(("Shared data init error\n"));
- params->shared_data_size = 0;
- shared = NULL;
- }
-
/* Use the recovery key to verify the kernel */
kernel_subkey = (VbPublicKey*)((uint8_t*)gbb + gbb->recovery_key_offset);
/* Let the TPM know if we're in recovery mode */
if (0 != RollbackKernelRecovery(dev_switch)) {
VBDEBUG(("Error setting up TPM for recovery kernel\n"));
+ if (shcall)
+ shcall->flags |= VBSD_LK_FLAG_REC_TPM_INIT_ERROR;
/* Ignore return code, since we need to boot recovery mode to
* fix the TPM. */
}
@@ -263,12 +286,16 @@
gpt.drive_sectors = params->ending_lba + 1;
if (0 != AllocAndReadGptData(&gpt)) {
VBDEBUG(("Unable to read GPT data\n"));
+ if (shcall)
+ shcall->check_result = VBSD_LKC_CHECK_GPT_READ_ERROR;
break;
}
/* Initialize GPT library */
if (GPT_SUCCESS != GptInit(&gpt)) {
VBDEBUG(("Error parsing GPT\n"));
+ if (shcall)
+ shcall->check_result = VBSD_LKC_CHECK_GPT_PARSE_ERROR;
break;
}
@@ -279,6 +306,7 @@
/* Loop over candidate kernel partitions */
while (GPT_SUCCESS == GptNextKernelEntry(&gpt, &part_start, &part_size)) {
+ VbSharedDataKernelPart* shpart = NULL;
VbKeyBlockHeader* key_block;
VbKernelPreambleHeader* preamble;
RSAPublicKey* data_key = NULL;
@@ -292,17 +320,35 @@
VBDEBUG(("Found kernel entry at %" PRIu64 " size %" PRIu64 "\n",
part_start, part_size));
+ if (shcall) {
+ /* Set up tracking for this partition. This wraps around if called
+ * many times, so initialize the partition entry each time. */
+ shpart = shcall->parts + (shcall->kernel_parts_found
+ & (VBSD_MAX_KERNEL_PARTS - 1));
+ Memset(shpart, 0, sizeof(VbSharedDataKernelPart));
+ shpart->sector_start = part_start;
+ shpart->sector_count = part_size;
+ /* TODO: GPT partitions start at 1, but cgptlib starts them at 0.
+ * Adjust here, until cgptlib is fixed. */
+ shpart->gpt_index = (uint8_t)(gpt.current_kernel + 1);
+ shcall->kernel_parts_found++;
+ }
+
/* Found at least one kernel partition. */
found_partitions++;
/* Read the first part of the kernel partition. */
if (part_size < kbuf_sectors) {
VBDEBUG(("Partition too small to hold kernel.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_TOO_SMALL;
goto bad_kernel;
}
if (0 != BootDeviceReadLBA(part_start, kbuf_sectors, kbuf)) {
VBDEBUG(("Unable to read start of partition.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_READ_START;
goto bad_kernel;
}
@@ -310,6 +356,9 @@
key_block = (VbKeyBlockHeader*)kbuf;
if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 0)) {
VBDEBUG(("Verifying key block signature failed.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_SIG;
+
key_block_valid = 0;
/* If we're not in developer mode, this kernel is bad. */
@@ -320,6 +369,8 @@
* block is valid. */
if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 1)) {
VBDEBUG(("Verifying key block hash failed.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_HASH;
goto bad_kernel;
}
}
@@ -329,12 +380,16 @@
(dev_switch ? KEY_BLOCK_FLAG_DEVELOPER_1 :
KEY_BLOCK_FLAG_DEVELOPER_0))) {
VBDEBUG(("Key block developer flag mismatch.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_DEV_MISMATCH;
key_block_valid = 0;
}
if (!(key_block->key_block_flags &
(rec_switch ? KEY_BLOCK_FLAG_RECOVERY_1 :
KEY_BLOCK_FLAG_RECOVERY_0))) {
VBDEBUG(("Key block recovery flag mismatch.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_REC_MISMATCH;
key_block_valid = 0;
}
@@ -343,6 +398,8 @@
if (kBootRecovery != boot_mode) {
if (key_version < (tpm_version >> 16)) {
VBDEBUG(("Key version too old.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_KEY_ROLLBACK;
key_block_valid = 0;
}
}
@@ -357,6 +414,8 @@
data_key = PublicKeyToRSA(&key_block->data_key);
if (!data_key) {
VBDEBUG(("Data key bad.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_DATA_KEY_PARSE;
goto bad_kernel;
}
@@ -366,6 +425,8 @@
KBUF_SIZE - key_block->key_block_size,
data_key))) {
VBDEBUG(("Preamble verification failed.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_VERIFY_PREAMBLE;
goto bad_kernel;
}
@@ -373,9 +434,13 @@
* rollback of the kernel version. */
combined_version = ((key_version << 16) |
(preamble->kernel_version & 0xFFFF));
+ if (shpart)
+ shpart->combined_version = (uint32_t)combined_version;
if (key_block_valid && kBootRecovery != boot_mode) {
if (combined_version < tpm_version) {
VBDEBUG(("Kernel version too low.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_KERNEL_ROLLBACK;
/* If we're not in developer mode, kernel version must be valid. */
if (kBootDev != boot_mode)
goto bad_kernel;
@@ -383,6 +448,8 @@
}
VBDEBUG(("Kernel preamble is good.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_PREAMBLE_VALID;
/* Check for lowest version from a valid header. */
if (key_block_valid && lowest_version > combined_version)
@@ -402,6 +469,8 @@
if ((preamble->body_load_address != (size_t)params->kernel_buffer) &&
!(params->boot_flags & BOOT_FLAG_SKIP_ADDR_CHECK)) {
VBDEBUG(("Wrong body load address.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_BODY_ADDRESS;
goto bad_kernel;
}
@@ -409,6 +478,8 @@
body_offset = key_block->key_block_size + preamble->preamble_size;
if (0 != body_offset % blba) {
VBDEBUG(("Kernel body not at multiple of sector size.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_BODY_OFFSET;
goto bad_kernel;
}
body_offset_sectors = body_offset / blba;
@@ -417,12 +488,16 @@
body_sectors = (preamble->body_signature.data_size + blba - 1) / blba;
if (body_sectors * blba > params->kernel_buffer_size) {
VBDEBUG(("Kernel body doesn't fit in memory.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_BODY_EXCEEDS_MEM;
goto bad_kernel;
}
/* Verify kernel body fits in the partition */
if (body_offset_sectors + body_sectors > part_size) {
VBDEBUG(("Kernel body doesn't fit in partition.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_BODY_EXCEEDS_PART;
goto bad_kernel;
}
@@ -433,6 +508,8 @@
params->kernel_buffer)) {
VBDEBUG(("Unable to read kernel data.\n"));
VBPERFEND("VB_RKD");
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_READ_DATA;
goto bad_kernel;
}
VBPERFEND("VB_RKD");
@@ -442,6 +519,8 @@
params->kernel_buffer_size,
&preamble->body_signature, data_key)) {
VBDEBUG(("Kernel data verification failed.\n"));
+ if (shpart)
+ shpart->check_result = VBSD_LKP_CHECK_VERIFY_DATA;
goto bad_kernel;
}
@@ -452,6 +531,12 @@
/* If we're still here, the kernel is valid. */
/* Save the first good partition we find; that's the one we'll boot */
VBDEBUG(("Partition is good.\n"));
+ if (shpart) {
+ shpart->check_result = VBSD_LKP_CHECK_KERNEL_GOOD;
+ if (key_block_valid)
+ shpart->flags |= VBSD_LKP_FLAG_KEY_BLOCK_VALID;
+ }
+
good_partition_key_block_valid = key_block_valid;
/* TODO: GPT partitions start at 1, but cgptlib starts them at 0.
* Adjust here, until cgptlib is fixed. */
@@ -508,6 +593,8 @@
/* Handle finding a good partition */
if (good_partition >= 0) {
VBDEBUG(("Good_partition >= 0\n"));
+ if (shcall)
+ shcall->check_result = VBSD_LKC_CHECK_GOOD_PARTITION;
/* See if we need to update the TPM */
if (kBootRecovery != boot_mode && good_partition_key_block_valid) {
@@ -551,6 +638,11 @@
/* Success! */
retval = LOAD_KERNEL_SUCCESS;
} else {
+ if (shcall)
+ shcall->check_result = (found_partitions > 0
+ ? VBSD_LKC_CHECK_INVALID_PARTITIONS
+ : VBSD_LKC_CHECK_NO_PARTITIONS);
+
/* TODO: differentiate between finding an invalid kernel
* (found_partitions>0) and not finding one at all. Right now we
* treat them the same, and return LOAD_KERNEL_INVALID for both. */
@@ -568,6 +660,13 @@
VbNvTeardown(vnc);
if (shared) {
+ if (shcall)
+ shcall->return_code = (uint8_t)retval;
+
+ /* Save whether the good partition's key block was fully verified */
+ if (good_partition_key_block_valid)
+ shared->flags |= VBSD_KERNEL_KEY_VERIFIED;
+
/* Save timer values */
shared->timer_load_kernel_enter = timer_enter;
shared->timer_load_kernel_exit = VbGetTimer();