| Index: firmware/lib/vboot_kernel.c
|
| diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
|
| index 204874f6e03c434f23d7e614595679e0366bd029..3d7d58572aa1eb950cf8d1b9b74ea53b682a0e35 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 @@ __pragma(warning(disable: 4127))
|
|
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
|
|
| /* 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| VBDEBUG(("Found kernel entry at %" PRIu64 " size %" PRIu64 "\n",
|
| part_start, part_size));
|
|
|
| + if (shcall) {
|
| + /* Set up tracking for this call. This wraps around if called many
|
| + * times, so we need to 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| * 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| (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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| * 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| }
|
|
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| /* 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| /* 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 @@ int LoadKernel(LoadKernelParams* params) {
|
| /* 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 @@ LoadKernelExit:
|
| 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();
|
|
|