/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * High-level firmware wrapper API - entry points for kernel selection */ #include "2api.h" #include "2common.h" #include "2misc.h" #include "2nvstorage.h" #include "2rsa.h" #include "2secdata.h" #include "2sysincludes.h" #include "load_kernel_fw.h" #include "utility.h" #include "vb2_common.h" #include "vboot_api.h" #include "vboot_kernel.h" #include "vboot_struct.h" #include "vboot_test.h" /* Global variables */ static LoadKernelParams lkp; #ifdef CHROMEOS_ENVIRONMENT /* Global variable accessor for unit tests */ struct LoadKernelParams *VbApiKernelGetParams(void) { return &lkp; } #endif static vb2_error_t handle_battery_cutoff(struct vb2_context *ctx) { vb2_error_t rv; /* * Check if we need to cut-off battery. This should be done after EC * FW and Aux FW are updated, and before the kernel is started. This * is to make sure all firmware is up-to-date before shipping (which * is the typical use-case for cutoff). */ if (vb2_nv_get(ctx, VB2_NV_BATTERY_CUTOFF_REQUEST)) { VB2_DEBUG("Request to cut-off battery\n"); vb2_nv_set(ctx, VB2_NV_BATTERY_CUTOFF_REQUEST, 0); /* May lose power immediately, so commit our update now. */ rv = vb2_commit_data(ctx); if (rv) return rv; vb2ex_ec_battery_cutoff(); return VBERROR_SHUTDOWN_REQUESTED; } return VB2_SUCCESS; } vb2_error_t VbTryLoadKernel(struct vb2_context *ctx, uint32_t get_info_flags) { vb2_error_t rv = VB2_ERROR_LK_NO_DISK_FOUND; VbDiskInfo* disk_info = NULL; uint32_t disk_count = 0; uint32_t i; lkp.disk_handle = NULL; /* Find disks */ if (VB2_SUCCESS != VbExDiskGetInfo(&disk_info, &disk_count, get_info_flags)) disk_count = 0; /* Loop over disks */ for (i = 0; i < disk_count; i++) { VB2_DEBUG("trying disk %d\n", (int)i); /* * Sanity-check what we can. FWIW, VbTryLoadKernel() is always * called with only a single bit set in get_info_flags. * * Ensure that we got a partition with only the flags we asked * for. */ if (disk_info[i].bytes_per_lba < 512 || (disk_info[i].bytes_per_lba & (disk_info[i].bytes_per_lba - 1)) != 0 || 16 > disk_info[i].lba_count || get_info_flags != (disk_info[i].flags & ~VB_DISK_FLAG_EXTERNAL_GPT)) { VB2_DEBUG(" skipping: bytes_per_lba=%" PRIu64 " lba_count=%" PRIu64 " flags=%#x\n", disk_info[i].bytes_per_lba, disk_info[i].lba_count, disk_info[i].flags); continue; } lkp.disk_handle = disk_info[i].handle; lkp.bytes_per_lba = disk_info[i].bytes_per_lba; lkp.gpt_lba_count = disk_info[i].lba_count; lkp.streaming_lba_count = disk_info[i].streaming_lba_count ?: lkp.gpt_lba_count; lkp.boot_flags |= disk_info[i].flags & VB_DISK_FLAG_EXTERNAL_GPT ? BOOT_FLAG_EXTERNAL_GPT : 0; vb2_error_t new_rv = LoadKernel(ctx, &lkp); VB2_DEBUG("LoadKernel() = %#x\n", new_rv); /* Stop now if we found a kernel. */ if (VB2_SUCCESS == new_rv) { VbExDiskFreeInfo(disk_info, lkp.disk_handle); return VB2_SUCCESS; } /* Don't update error if we already have a more specific one. */ if (VB2_ERROR_LK_INVALID_KERNEL_FOUND != rv) rv = new_rv; } /* If we drop out of the loop, we didn't find any usable kernel. */ if (get_info_flags & VB_DISK_FLAG_FIXED) { switch (rv) { case VB2_ERROR_LK_INVALID_KERNEL_FOUND: vb2api_fail(ctx, VB2_RECOVERY_RW_INVALID_OS, rv); break; case VB2_ERROR_LK_NO_KERNEL_FOUND: vb2api_fail(ctx, VB2_RECOVERY_RW_NO_KERNEL, rv); break; case VB2_ERROR_LK_NO_DISK_FOUND: vb2api_fail(ctx, VB2_RECOVERY_RW_NO_DISK, rv); break; default: vb2api_fail(ctx, VB2_RECOVERY_LK_UNSPECIFIED, rv); break; } } /* If we didn't find any good kernels, don't return a disk handle. */ VbExDiskFreeInfo(disk_info, NULL); return rv; } /** * Reset any NVRAM requests. * * @param ctx Vboot context * @return 1 if a reboot is required, 0 otherwise. */ static int vb2_reset_nv_requests(struct vb2_context *ctx) { int need_reboot = 0; if (vb2_nv_get(ctx, VB2_NV_DISPLAY_REQUEST)) { VB2_DEBUG("Unset display request (undo display init)\n"); vb2_nv_set(ctx, VB2_NV_DISPLAY_REQUEST, 0); need_reboot = 1; } if (vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST)) { VB2_DEBUG("Unset diagnostic request (undo display init)\n"); vb2_nv_set(ctx, VB2_NV_DIAG_REQUEST, 0); need_reboot = 1; } return need_reboot; } vb2_error_t VbBootNormal(struct vb2_context *ctx) { struct vb2_shared_data *sd = vb2_get_sd(ctx); VbSharedDataHeader *shared = sd->vbsd; uint32_t max_rollforward = vb2_nv_get(ctx, VB2_NV_KERNEL_MAX_ROLLFORWARD); /* Boot from fixed disk only */ VB2_DEBUG("Entering\n"); if (vb2_reset_nv_requests(ctx)) { VB2_DEBUG("Normal mode: reboot to reset NVRAM requests\n"); return VBERROR_REBOOT_REQUIRED; } vb2_error_t rv = VbTryLoadKernel(ctx, VB_DISK_FLAG_FIXED); VB2_DEBUG("Checking if TPM kernel version needs advancing\n"); /* * Special case for when we're trying a slot with new firmware. * Firmware updates also usually change the kernel key, which means * that the new firmware can only boot a new kernel, and the old * firmware in the previous slot can only boot the previous kernel. * * Don't roll-forward the kernel version, because we don't yet know if * the new kernel will successfully boot. */ if (vb2_nv_get(ctx, VB2_NV_FW_RESULT) == VB2_FW_RESULT_TRYING) { VB2_DEBUG("Trying new FW; skip kernel version roll-forward.\n"); return rv; } /* * Limit kernel version rollforward if needed. Can't limit kernel * version to less than the version currently in the TPM. That is, * we're limiting rollforward, not allowing rollback. */ if (max_rollforward < shared->kernel_version_tpm_start) max_rollforward = shared->kernel_version_tpm_start; if (shared->kernel_version_tpm > max_rollforward) { VB2_DEBUG("Limiting TPM kernel version roll-forward " "to %#x < %#x\n", max_rollforward, shared->kernel_version_tpm); shared->kernel_version_tpm = max_rollforward; } if (shared->kernel_version_tpm > shared->kernel_version_tpm_start) { vb2_secdata_kernel_set(ctx, VB2_SECDATA_KERNEL_VERSIONS, shared->kernel_version_tpm); } return rv; } static vb2_error_t vb2_kernel_setup(struct vb2_context *ctx, VbSharedDataHeader *shared, VbSelectAndLoadKernelParams *kparams) { struct vb2_shared_data *sd = vb2_get_sd(ctx); /* Translate vboot2 flags and fields into vboot1. */ if (ctx->flags & VB2_CONTEXT_EC_SYNC_SUPPORTED) shared->flags |= VBSD_EC_SOFTWARE_SYNC; if (ctx->flags & VB2_CONTEXT_NVDATA_V2) shared->flags |= VBSD_NVDATA_V2; if (sd->flags & VB2_SD_FLAG_DEV_MODE_ENABLED) shared->flags |= VBSD_BOOT_DEV_SWITCH_ON; /* Translate recovery reason-related fields into vboot1 */ shared->recovery_reason = sd->recovery_reason; if (sd->recovery_reason) shared->firmware_index = 0xff; if (sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY) shared->flags |= VBSD_BOOT_REC_SWITCH_ON; /* * Save a pointer to the old vboot1 shared data, since we haven't * finished porting the library to use the new vb2 context and shared * data. * * TODO: replace this with fields directly in vb2 shared data. */ sd->vbsd = shared; /* Fill in params for calls to LoadKernel() */ memset(&lkp, 0, sizeof(lkp)); lkp.kernel_buffer = kparams->kernel_buffer; lkp.kernel_buffer_size = kparams->kernel_buffer_size; /* Clear output params in case we fail */ kparams->disk_handle = NULL; kparams->partition_number = 0; kparams->bootloader_address = 0; kparams->bootloader_size = 0; kparams->flags = 0; memset(kparams->partition_guid, 0, sizeof(kparams->partition_guid)); return VB2_SUCCESS; } static void vb2_kernel_fill_kparams(struct vb2_context *ctx, VbSelectAndLoadKernelParams *kparams) { /* Save disk parameters */ kparams->disk_handle = lkp.disk_handle; kparams->partition_number = lkp.partition_number; kparams->bootloader_address = lkp.bootloader_address; kparams->bootloader_size = lkp.bootloader_size; kparams->flags = lkp.flags; kparams->kernel_buffer = lkp.kernel_buffer; kparams->kernel_buffer_size = lkp.kernel_buffer_size; memcpy(kparams->partition_guid, lkp.partition_guid, sizeof(kparams->partition_guid)); } vb2_error_t vb2_commit_data(struct vb2_context *ctx) { vb2_error_t rv = vb2ex_commit_data(ctx); switch (rv) { case VB2_SUCCESS: break; case VB2_ERROR_SECDATA_FIRMWARE_WRITE: if (!(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { vb2api_fail(ctx, VB2_RECOVERY_RW_TPM_W_ERROR, rv); /* Run again to set recovery reason in nvdata. */ vb2ex_commit_data(ctx); return rv; } break; case VB2_ERROR_SECDATA_KERNEL_WRITE: if (!(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { vb2api_fail(ctx, VB2_RECOVERY_RW_TPM_W_ERROR, rv); /* Run again to set recovery reason in nvdata. */ vb2ex_commit_data(ctx); return rv; } break; default: VB2_DEBUG("unknown commit error: %#x\n", rv); __attribute__ ((fallthrough)); case VB2_ERROR_NV_WRITE: /* * We can't write to nvdata, so it's impossible to * trigger recovery mode. Skip calling vb2api_fail * and just die (unless already in recovery). */ VB2_REC_OR_DIE(ctx, "write nvdata failed\n"); break; } return VB2_SUCCESS; } vb2_error_t VbSelectAndLoadKernel(struct vb2_context *ctx, VbSharedDataHeader *shared, VbSelectAndLoadKernelParams *kparams) { struct vb2_shared_data *sd = vb2_get_sd(ctx); vb2_error_t rv, call_rv; /* Init nvstorage space. TODO(kitching): Remove once we add assertions to vb2_nv_get and vb2_nv_set. */ vb2_nv_init(ctx); rv = vb2_kernel_setup(ctx, shared, kparams); if (rv) goto VbSelectAndLoadKernel_exit; rv = vb2api_kernel_phase1(ctx); if (rv) goto VbSelectAndLoadKernel_exit; VB2_DEBUG("GBB flags are %#x\n", vb2_get_gbb(ctx)->flags); /* * Do EC and Aux FW software sync unless we're in recovery mode. This * has UI but it's just a single non-interactive WAIT screen. */ if (!(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { rv = vb2api_ec_sync(ctx); if (rv) goto VbSelectAndLoadKernel_exit; rv = vb2api_auxfw_sync(ctx); if (rv) goto VbSelectAndLoadKernel_exit; rv = handle_battery_cutoff(ctx); if (rv) goto VbSelectAndLoadKernel_exit; } /* Select boot path */ if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE) { vb2_clear_recovery(ctx); /* * Need to commit nvdata changes immediately, since we will be * entering either manual recovery UI or BROKEN screen shortly. */ vb2_commit_data(ctx); /* If we're in recovery mode just to do memory retraining, all we need to do is reboot. */ if (sd->recovery_reason == VB2_RECOVERY_TRAIN_AND_REBOOT) { VB2_DEBUG("Reboot after retraining in recovery\n"); rv = VBERROR_REBOOT_REQUIRED; goto VbSelectAndLoadKernel_exit; } /* Recovery boot. This has UI. */ if (LEGACY_MENU_UI) rv = VbBootRecoveryLegacyMenu(ctx); else rv = VbBootRecoveryLegacyClamshell(ctx); } else if (DIAGNOSTIC_UI && vb2_nv_get(ctx, VB2_NV_DIAG_REQUEST)) { vb2_nv_set(ctx, VB2_NV_DIAG_REQUEST, 0); /* * Diagnostic boot. This has a UI but only power button * is used for input so no detachable-specific UI is * needed. This mode is also 1-shot so it's placed * before developer mode. */ rv = VbBootDiagnosticLegacyClamshell(ctx); /* * The diagnostic menu should either boot a rom, or * return either of reboot or shutdown. The following * check is a safety precaution. */ if (!rv) rv = VBERROR_REBOOT_REQUIRED; } else if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) { /* Developer boot. This has UI. */ if (LEGACY_MENU_UI) rv = VbBootDeveloperLegacyMenu(ctx); else rv = VbBootDeveloperLegacyClamshell(ctx); } else { /* Normal boot */ rv = VbBootNormal(ctx); } VbSelectAndLoadKernel_exit: if (rv == VB2_SUCCESS) vb2_kernel_fill_kparams(ctx, kparams); /* Translate vboot2 flags and fields into vboot1. */ if (sd->flags & VB2_SD_FLAG_KERNEL_SIGNED) sd->vbsd->flags |= VBSD_KERNEL_KEY_VERIFIED; /* Commit data, but retain any previous errors */ call_rv = vb2_commit_data(ctx); if (rv == VB2_SUCCESS) rv = call_rv; /* Pass through return value from boot path */ VB2_DEBUG("Returning %#x\n", rv); return rv; }