667 lines
21 KiB
C
667 lines
21 KiB
C
/* 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.
|
|
*
|
|
* Functions for loading a kernel from disk.
|
|
* (Firmware portion)
|
|
*/
|
|
|
|
#include "2common.h"
|
|
#include "2misc.h"
|
|
#include "2nvstorage.h"
|
|
#include "2rsa.h"
|
|
#include "2sha.h"
|
|
#include "2secdata.h"
|
|
#include "2sysincludes.h"
|
|
#include "cgptlib.h"
|
|
#include "cgptlib_internal.h"
|
|
#include "gpt_misc.h"
|
|
#include "load_kernel_fw.h"
|
|
#include "secdata_tpm.h"
|
|
#include "utility.h"
|
|
#include "vb2_common.h"
|
|
#include "vboot_api.h"
|
|
#include "vboot_common.h"
|
|
#include "vboot_kernel.h"
|
|
|
|
#define LOWEST_TPM_VERSION 0xffffffff
|
|
|
|
enum vboot_mode {
|
|
kBootRecovery = 0, /* Recovery firmware, any dev switch position */
|
|
kBootNormal = 1, /* Normal boot - kernel must be verified */
|
|
kBootDev = 2 /* Developer boot - self-signed kernel ok */
|
|
};
|
|
|
|
/**
|
|
* Return the boot mode based on the parameters.
|
|
*
|
|
* @param params Load kernel parameters
|
|
* @return The current boot mode.
|
|
*/
|
|
static enum vboot_mode get_kernel_boot_mode(struct vb2_context *ctx)
|
|
{
|
|
if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE)
|
|
return kBootRecovery;
|
|
|
|
if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE)
|
|
return kBootDev;
|
|
|
|
return kBootNormal;
|
|
};
|
|
|
|
/**
|
|
* Check if the parameters require an officially signed OS.
|
|
*
|
|
* @param params Load kernel parameters
|
|
* @return 1 if official OS required; 0 if self-signed kernels are ok
|
|
*/
|
|
static int require_official_os(struct vb2_context *ctx,
|
|
const LoadKernelParams *params)
|
|
{
|
|
/* Normal and recovery modes always require official OS */
|
|
if (get_kernel_boot_mode(ctx) != kBootDev)
|
|
return 1;
|
|
|
|
/* FWMP can require developer mode to use official OS */
|
|
if (params->fwmp &&
|
|
(params->fwmp->flags & FWMP_DEV_ENABLE_OFFICIAL_ONLY))
|
|
return 1;
|
|
|
|
/* Developer can request official OS via nvstorage */
|
|
return vb2_nv_get(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY);
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to the keyblock inside a vblock.
|
|
*
|
|
* Must only be called during or after vb2_verify_kernel_vblock().
|
|
*
|
|
* @param kbuf Buffer containing vblock
|
|
* @return The keyblock pointer.
|
|
*/
|
|
static struct vb2_keyblock *get_keyblock(uint8_t *kbuf)
|
|
{
|
|
return (struct vb2_keyblock *)kbuf;
|
|
}
|
|
|
|
/**
|
|
* Return a pointer to the kernel preamble inside a vblock.
|
|
*
|
|
* Must only be called during or after vb2_verify_kernel_vblock().
|
|
*
|
|
* @param kbuf Buffer containing vblock
|
|
* @return The kernel preamble pointer.
|
|
*/
|
|
static struct vb2_kernel_preamble *get_preamble(uint8_t *kbuf)
|
|
{
|
|
return (struct vb2_kernel_preamble *)
|
|
(kbuf + get_keyblock(kbuf)->keyblock_size);
|
|
}
|
|
|
|
/**
|
|
* Return the offset of the kernel body from the start of the vblock.
|
|
*
|
|
* Must only be called during or after vb2_verify_kernel_vblock().
|
|
*
|
|
* @param kbuf Buffer containing vblock
|
|
* @return The offset of the kernel body from the vblock start, in bytes.
|
|
*/
|
|
static uint32_t get_body_offset(uint8_t *kbuf)
|
|
{
|
|
return (get_keyblock(kbuf)->keyblock_size +
|
|
get_preamble(kbuf)->preamble_size);
|
|
}
|
|
|
|
/**
|
|
* Verify a kernel vblock.
|
|
*
|
|
* @param kbuf Buffer containing the vblock
|
|
* @param kbuf_size Size of the buffer in bytes
|
|
* @param kernel_subkey Packed kernel subkey to use in validating keyblock
|
|
* @param params Load kernel parameters
|
|
* @param min_version Minimum kernel version
|
|
* @param shpart Destination for verification results
|
|
* @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,
|
|
const struct vb2_packed_key *kernel_subkey,
|
|
const LoadKernelParams *params, uint32_t min_version,
|
|
VbSharedDataKernelPart *shpart, struct vb2_workbuf *wb)
|
|
{
|
|
/* Unpack kernel subkey */
|
|
struct vb2_public_key kernel_subkey2;
|
|
if (VB2_SUCCESS != vb2_unpack_key(&kernel_subkey2, kernel_subkey)) {
|
|
VB2_DEBUG("Unable to unpack kernel subkey\n");
|
|
return VB2_ERROR_VBLOCK_KERNEL_SUBKEY;
|
|
}
|
|
|
|
/* Verify the key block. */
|
|
int keyblock_valid = 1; /* Assume valid */
|
|
struct vb2_keyblock *keyblock = get_keyblock(kbuf);
|
|
if (VB2_SUCCESS != vb2_verify_keyblock(keyblock, kbuf_size,
|
|
&kernel_subkey2, wb)) {
|
|
VB2_DEBUG("Verifying key block signature failed.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_SIG;
|
|
keyblock_valid = 0;
|
|
|
|
/* Check if we must have an officially signed kernel */
|
|
if (require_official_os(ctx, params)) {
|
|
VB2_DEBUG("Self-signed kernels not enabled.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_SELF_SIGNED;
|
|
return VB2_ERROR_VBLOCK_SELF_SIGNED;
|
|
}
|
|
|
|
/* Otherwise, allow the kernel if the key block hash is valid */
|
|
if (VB2_SUCCESS !=
|
|
vb2_verify_keyblock_hash(keyblock, kbuf_size, wb)) {
|
|
VB2_DEBUG("Verifying key block hash failed.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_HASH;
|
|
return VB2_ERROR_VBLOCK_KEYBLOCK_HASH;
|
|
}
|
|
}
|
|
|
|
/* Check the key block flags against boot flags. */
|
|
if (!(keyblock->keyblock_flags &
|
|
((ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) ?
|
|
KEY_BLOCK_FLAG_DEVELOPER_1 : KEY_BLOCK_FLAG_DEVELOPER_0))) {
|
|
VB2_DEBUG("Key block developer flag mismatch.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_DEV_MISMATCH;
|
|
keyblock_valid = 0;
|
|
}
|
|
if (!(keyblock->keyblock_flags &
|
|
((ctx->flags & VB2_CONTEXT_RECOVERY_MODE) ?
|
|
KEY_BLOCK_FLAG_RECOVERY_1 : KEY_BLOCK_FLAG_RECOVERY_0))) {
|
|
VB2_DEBUG("Key block recovery flag mismatch.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_REC_MISMATCH;
|
|
keyblock_valid = 0;
|
|
}
|
|
|
|
/* Check for rollback of key version except in recovery mode. */
|
|
enum vboot_mode boot_mode = get_kernel_boot_mode(ctx);
|
|
uint32_t key_version = keyblock->data_key.key_version;
|
|
if (kBootRecovery != boot_mode) {
|
|
if (key_version < (min_version >> 16)) {
|
|
VB2_DEBUG("Key version too old.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KEY_ROLLBACK;
|
|
keyblock_valid = 0;
|
|
}
|
|
if (key_version > 0xFFFF) {
|
|
/*
|
|
* Key version is stored in 16 bits in the TPM, so key
|
|
* versions greater than 0xFFFF can't be stored
|
|
* properly.
|
|
*/
|
|
VB2_DEBUG("Key version > 0xFFFF.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KEY_ROLLBACK;
|
|
keyblock_valid = 0;
|
|
}
|
|
}
|
|
|
|
/* If not in developer mode, key block required to be valid. */
|
|
if (kBootDev != boot_mode && !keyblock_valid) {
|
|
VB2_DEBUG("Key block is invalid.\n");
|
|
return VB2_ERROR_VBLOCK_KEYBLOCK;
|
|
}
|
|
|
|
/* If in developer mode and using key hash, check it */
|
|
if ((kBootDev == boot_mode) &&
|
|
params->fwmp && (params->fwmp->flags & FWMP_DEV_USE_KEY_HASH)) {
|
|
struct vb2_packed_key *key = &keyblock->data_key;
|
|
uint8_t *buf = ((uint8_t *)key) + key->key_offset;
|
|
uint32_t buflen = key->key_size;
|
|
uint8_t digest[VB2_SHA256_DIGEST_SIZE];
|
|
|
|
VB2_DEBUG("Checking developer key hash.\n");
|
|
vb2_digest_buffer(buf, buflen, VB2_HASH_SHA256,
|
|
digest, sizeof(digest));
|
|
if (0 != vb2_safe_memcmp(digest, params->fwmp->dev_key_hash,
|
|
VB2_SHA256_DIGEST_SIZE)) {
|
|
int i;
|
|
|
|
VB2_DEBUG("Wrong developer key hash.\n");
|
|
VB2_DEBUG("Want: ");
|
|
for (i = 0; i < VB2_SHA256_DIGEST_SIZE; i++)
|
|
VB2_DEBUG("%02x",
|
|
params->fwmp->dev_key_hash[i]);
|
|
VB2_DEBUG("\nGot: ");
|
|
for (i = 0; i < VB2_SHA256_DIGEST_SIZE; i++)
|
|
VB2_DEBUG("%02x", digest[i]);
|
|
VB2_DEBUG("\n");
|
|
|
|
return VB2_ERROR_VBLOCK_DEV_KEY_HASH;
|
|
}
|
|
}
|
|
|
|
/* Get key for preamble verification from the key block. */
|
|
struct vb2_public_key data_key;
|
|
if (VB2_SUCCESS != vb2_unpack_key(&data_key, &keyblock->data_key)) {
|
|
VB2_DEBUG("Unable to unpack kernel data key\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_DATA_KEY_PARSE;
|
|
return VB2_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/* Verify the preamble, which follows the key block */
|
|
struct vb2_kernel_preamble *preamble = get_preamble(kbuf);
|
|
if (VB2_SUCCESS !=
|
|
vb2_verify_kernel_preamble(preamble,
|
|
kbuf_size - keyblock->keyblock_size,
|
|
&data_key,
|
|
wb)) {
|
|
VB2_DEBUG("Preamble verification failed.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_VERIFY_PREAMBLE;
|
|
return VB2_ERROR_UNKNOWN;
|
|
}
|
|
|
|
/*
|
|
* If the key block is valid and we're not in recovery mode, check for
|
|
* rollback of the kernel version.
|
|
*/
|
|
uint32_t combined_version = (key_version << 16) |
|
|
(preamble->kernel_version & 0xFFFF);
|
|
shpart->combined_version = combined_version;
|
|
if (keyblock_valid && kBootRecovery != boot_mode) {
|
|
if (combined_version < min_version) {
|
|
VB2_DEBUG("Kernel version too low.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KERNEL_ROLLBACK;
|
|
/*
|
|
* If not in developer mode, kernel version
|
|
* must be valid.
|
|
*/
|
|
if (kBootDev != boot_mode)
|
|
return VB2_ERROR_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
VB2_DEBUG("Kernel preamble is good.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_PREAMBLE_VALID;
|
|
if (keyblock_valid)
|
|
shpart->flags |= VBSD_LKP_FLAG_KEY_BLOCK_VALID;
|
|
|
|
return VB2_SUCCESS;
|
|
}
|
|
|
|
enum vb2_load_partition_flags {
|
|
/* Only check the vblock to */
|
|
VB2_LOAD_PARTITION_VBLOCK_ONLY = (1 << 0),
|
|
};
|
|
|
|
#define KBUF_SIZE 65536 /* Bytes to read at start of kernel partition */
|
|
|
|
/* Minimum context work buffer size needed for vb2_load_partition() */
|
|
#define VB2_LOAD_PARTITION_WORKBUF_BYTES \
|
|
(VB2_VERIFY_KERNEL_PREAMBLE_WORKBUF_BYTES + KBUF_SIZE)
|
|
|
|
/**
|
|
* Load and verify a partition from the stream.
|
|
*
|
|
* @param ctx Vboot context
|
|
* @param stream Stream to load kernel from
|
|
* @param kernel_subkey Key to use to verify vblock
|
|
* @param flags Flags (one or more of vb2_load_partition_flags)
|
|
* @param params Load-kernel parameters
|
|
* @param min_version Minimum kernel version from TPM
|
|
* @param shpart Destination for verification results
|
|
* @param wb Workbuf for data storage
|
|
* @return VB2_SUCCESS, or non-zero error code.
|
|
*/
|
|
static vb2_error_t vb2_load_partition(
|
|
struct vb2_context *ctx, VbExStream_t stream,
|
|
const struct vb2_packed_key *kernel_subkey, uint32_t flags,
|
|
LoadKernelParams *params, uint32_t min_version,
|
|
VbSharedDataKernelPart *shpart, struct vb2_workbuf *wb)
|
|
{
|
|
uint64_t read_us = 0, start_ts;
|
|
struct vb2_workbuf wblocal = *wb;
|
|
|
|
/* Allocate kernel header buffer in workbuf */
|
|
uint8_t *kbuf = vb2_workbuf_alloc(&wblocal, KBUF_SIZE);
|
|
if (!kbuf)
|
|
return VB2_ERROR_LOAD_PARTITION_WORKBUF;
|
|
|
|
start_ts = VbExGetTimer();
|
|
if (VbExStreamRead(stream, KBUF_SIZE, kbuf)) {
|
|
VB2_DEBUG("Unable to read start of partition.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_READ_START;
|
|
return VB2_ERROR_LOAD_PARTITION_READ_VBLOCK;
|
|
}
|
|
read_us += VbExGetTimer() - start_ts;
|
|
|
|
if (VB2_SUCCESS !=
|
|
vb2_verify_kernel_vblock(ctx, kbuf, KBUF_SIZE, kernel_subkey,
|
|
params, min_version, shpart, &wblocal)) {
|
|
return VB2_ERROR_LOAD_PARTITION_VERIFY_VBLOCK;
|
|
}
|
|
|
|
if (flags & VB2_LOAD_PARTITION_VBLOCK_ONLY)
|
|
return VB2_SUCCESS;
|
|
|
|
struct vb2_keyblock *keyblock = get_keyblock(kbuf);
|
|
struct vb2_kernel_preamble *preamble = get_preamble(kbuf);
|
|
|
|
/*
|
|
* Make sure the kernel starts at or before what we already read into
|
|
* kbuf.
|
|
*
|
|
* We could deal with a larger offset by reading and discarding the
|
|
* data in between the vblock and the kernel data.
|
|
*/
|
|
uint32_t body_offset = get_body_offset(kbuf);
|
|
if (body_offset > KBUF_SIZE) {
|
|
shpart->check_result = VBSD_LKP_CHECK_BODY_OFFSET;
|
|
VB2_DEBUG("Kernel body offset is %u > 64KB.\n", body_offset);
|
|
return VB2_ERROR_LOAD_PARTITION_BODY_OFFSET;
|
|
}
|
|
|
|
uint8_t *kernbuf = params->kernel_buffer;
|
|
uint32_t kernbuf_size = params->kernel_buffer_size;
|
|
if (!kernbuf) {
|
|
/* Get kernel load address and size from the header. */
|
|
kernbuf = (uint8_t *)((long)preamble->body_load_address);
|
|
kernbuf_size = preamble->body_signature.data_size;
|
|
} else if (preamble->body_signature.data_size > kernbuf_size) {
|
|
VB2_DEBUG("Kernel body doesn't fit in memory.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_BODY_EXCEEDS_MEM;
|
|
return VB2_ERROR_LOAD_PARTITION_BODY_SIZE;
|
|
}
|
|
|
|
uint32_t body_toread = preamble->body_signature.data_size;
|
|
uint8_t *body_readptr = kernbuf;
|
|
|
|
/*
|
|
* If we've already read part of the kernel, copy that to the beginning
|
|
* of the kernel buffer.
|
|
*/
|
|
uint32_t body_copied = KBUF_SIZE - body_offset;
|
|
if (body_copied > body_toread)
|
|
body_copied = body_toread; /* Don't over-copy tiny kernel */
|
|
memcpy(body_readptr, kbuf + body_offset, body_copied);
|
|
body_toread -= body_copied;
|
|
body_readptr += body_copied;
|
|
|
|
/* Read the kernel data */
|
|
start_ts = VbExGetTimer();
|
|
if (body_toread && VbExStreamRead(stream, body_toread, body_readptr)) {
|
|
VB2_DEBUG("Unable to read kernel data.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_READ_DATA;
|
|
return VB2_ERROR_LOAD_PARTITION_READ_BODY;
|
|
}
|
|
read_us += VbExGetTimer() - start_ts;
|
|
VB2_DEBUG("read %" PRIu32 " KB in %" PRIu64 " ms at %" PRIu64 " KB/s.\n",
|
|
(body_toread + KBUF_SIZE) / 1024, read_us / 1000,
|
|
((uint64_t)(body_toread + KBUF_SIZE) * 1000 * 1000) /
|
|
(read_us * 1024));
|
|
|
|
/* Get key for preamble/data verification from the key block. */
|
|
struct vb2_public_key data_key;
|
|
if (VB2_SUCCESS != vb2_unpack_key(&data_key, &keyblock->data_key)) {
|
|
VB2_DEBUG("Unable to unpack kernel data key\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_DATA_KEY_PARSE;
|
|
return VB2_ERROR_LOAD_PARTITION_DATA_KEY;
|
|
}
|
|
|
|
/* Verify kernel data */
|
|
if (VB2_SUCCESS != vb2_verify_data(kernbuf, kernbuf_size,
|
|
&preamble->body_signature,
|
|
&data_key, &wblocal)) {
|
|
VB2_DEBUG("Kernel data verification failed.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_VERIFY_DATA;
|
|
return VB2_ERROR_LOAD_PARTITION_VERIFY_BODY;
|
|
}
|
|
|
|
/* If we're still here, the kernel is valid */
|
|
VB2_DEBUG("Partition is good.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_KERNEL_GOOD;
|
|
|
|
/* Save kernel data back to parameters */
|
|
params->bootloader_address = preamble->bootloader_address;
|
|
params->bootloader_size = preamble->bootloader_size;
|
|
params->flags = vb2_kernel_get_flags(preamble);
|
|
if (!params->kernel_buffer) {
|
|
params->kernel_buffer = kernbuf;
|
|
params->kernel_buffer_size = kernbuf_size;
|
|
}
|
|
|
|
return VB2_SUCCESS;
|
|
}
|
|
|
|
vb2_error_t LoadKernel(struct vb2_context *ctx, LoadKernelParams *params)
|
|
{
|
|
struct vb2_shared_data *sd = vb2_get_sd(ctx);
|
|
struct vb2_workbuf wb;
|
|
VbSharedDataHeader *shared = sd->vbsd;
|
|
VbSharedDataKernelCall *shcall = NULL;
|
|
int found_partitions = 0;
|
|
uint32_t lowest_version = LOWEST_TPM_VERSION;
|
|
vb2_error_t retval = VB2_ERROR_UNKNOWN;
|
|
int recovery = VB2_RECOVERY_LK_UNSPECIFIED;
|
|
vb2_error_t rv;
|
|
|
|
vb2_workbuf_from_ctx(ctx, &wb);
|
|
|
|
/* Clear output params in case we fail */
|
|
params->partition_number = 0;
|
|
params->bootloader_address = 0;
|
|
params->bootloader_size = 0;
|
|
params->flags = 0;
|
|
|
|
/*
|
|
* 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(*shcall));
|
|
shcall->boot_flags = (uint32_t)params->boot_flags;
|
|
shcall->boot_mode = get_kernel_boot_mode(ctx);
|
|
shcall->sector_size = (uint32_t)params->bytes_per_lba;
|
|
shcall->sector_count = params->streaming_lba_count;
|
|
shared->lk_call_count++;
|
|
|
|
/* Choose key to verify kernel */
|
|
struct vb2_packed_key *kernel_subkey;
|
|
if (kBootRecovery == shcall->boot_mode) {
|
|
/* Use the recovery key to verify the kernel */
|
|
rv = vb2_gbb_read_recovery_key(ctx, &kernel_subkey, NULL, &wb);
|
|
if (VB2_SUCCESS != rv) {
|
|
VB2_DEBUG("GBB read recovery key failed.\n");
|
|
retval = VBERROR_INVALID_GBB;
|
|
goto load_kernel_exit;
|
|
}
|
|
} else {
|
|
/* Use the kernel subkey passed from firmware verification */
|
|
kernel_subkey = (struct vb2_packed_key *)&shared->kernel_subkey;
|
|
}
|
|
|
|
/* Read GPT data */
|
|
GptData gpt;
|
|
gpt.sector_bytes = (uint32_t)params->bytes_per_lba;
|
|
gpt.streaming_drive_sectors = params->streaming_lba_count;
|
|
gpt.gpt_drive_sectors = params->gpt_lba_count;
|
|
gpt.flags = params->boot_flags & BOOT_FLAG_EXTERNAL_GPT
|
|
? GPT_FLAG_EXTERNAL : 0;
|
|
if (0 != AllocAndReadGptData(params->disk_handle, &gpt)) {
|
|
VB2_DEBUG("Unable to read GPT data\n");
|
|
shcall->check_result = VBSD_LKC_CHECK_GPT_READ_ERROR;
|
|
goto gpt_done;
|
|
}
|
|
|
|
/* Initialize GPT library */
|
|
if (GPT_SUCCESS != GptInit(&gpt)) {
|
|
VB2_DEBUG("Error parsing GPT\n");
|
|
shcall->check_result = VBSD_LKC_CHECK_GPT_PARSE_ERROR;
|
|
goto gpt_done;
|
|
}
|
|
|
|
/* Loop over candidate kernel partitions */
|
|
uint64_t part_start, part_size;
|
|
while (GPT_SUCCESS ==
|
|
GptNextKernelEntry(&gpt, &part_start, &part_size)) {
|
|
|
|
VB2_DEBUG("Found kernel entry at %"
|
|
PRIu64 " size %" PRIu64 "\n",
|
|
part_start, part_size);
|
|
|
|
/*
|
|
* Set up tracking for this partition. This wraps around if
|
|
* called many times, so initialize the partition entry each
|
|
* time.
|
|
*/
|
|
VbSharedDataKernelPart *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++;
|
|
|
|
/* Set up the stream */
|
|
VbExStream_t stream = NULL;
|
|
if (VbExStreamOpen(params->disk_handle,
|
|
part_start, part_size, &stream)) {
|
|
VB2_DEBUG("Partition error getting stream.\n");
|
|
shpart->check_result = VBSD_LKP_CHECK_TOO_SMALL;
|
|
VB2_DEBUG("Marking kernel as invalid.\n");
|
|
GptUpdateKernelEntry(&gpt, GPT_UPDATE_ENTRY_BAD);
|
|
continue;
|
|
}
|
|
|
|
uint32_t lpflags = 0;
|
|
if (params->partition_number > 0) {
|
|
/*
|
|
* If we already have a good kernel, we only needed to
|
|
* look at the vblock versions to check for rollback.
|
|
*/
|
|
lpflags |= VB2_LOAD_PARTITION_VBLOCK_ONLY;
|
|
}
|
|
|
|
rv = vb2_load_partition(ctx,
|
|
stream,
|
|
kernel_subkey,
|
|
lpflags,
|
|
params,
|
|
shared->kernel_version_tpm,
|
|
shpart,
|
|
&wb);
|
|
VbExStreamClose(stream);
|
|
|
|
if (rv != VB2_SUCCESS) {
|
|
VB2_DEBUG("Marking kernel as invalid.\n");
|
|
GptUpdateKernelEntry(&gpt, GPT_UPDATE_ENTRY_BAD);
|
|
continue;
|
|
}
|
|
|
|
int keyblock_valid = (shpart->flags &
|
|
VBSD_LKP_FLAG_KEY_BLOCK_VALID);
|
|
if (keyblock_valid) {
|
|
shared->flags |= VBSD_KERNEL_KEY_VERIFIED;
|
|
/* Track lowest version from a valid header. */
|
|
if (lowest_version > shpart->combined_version)
|
|
lowest_version = shpart->combined_version;
|
|
}
|
|
VB2_DEBUG("Key block valid: %d\n", keyblock_valid);
|
|
VB2_DEBUG("Combined version: %u\n", shpart->combined_version);
|
|
|
|
/*
|
|
* If we're only looking at headers, we're done with this
|
|
* partition.
|
|
*/
|
|
if (lpflags & VB2_LOAD_PARTITION_VBLOCK_ONLY)
|
|
continue;
|
|
|
|
/*
|
|
* Otherwise, we found a partition we like.
|
|
*
|
|
* TODO: GPT partitions start at 1, but cgptlib starts them at
|
|
* 0. Adjust here, until cgptlib is fixed.
|
|
*/
|
|
params->partition_number = gpt.current_kernel + 1;
|
|
|
|
/*
|
|
* TODO: GetCurrentKernelUniqueGuid() should take a destination
|
|
* size, or the dest should be a struct, so we know it's big
|
|
* enough.
|
|
*/
|
|
GetCurrentKernelUniqueGuid(&gpt, ¶ms->partition_guid);
|
|
|
|
/* Update GPT to note this is the kernel we're trying.
|
|
* But not when we assume that the boot process may
|
|
* not complete for valid reasons (eg. early shutdown).
|
|
*/
|
|
if (!(shared->flags & VBSD_NOFAIL_BOOT))
|
|
GptUpdateKernelEntry(&gpt, GPT_UPDATE_ENTRY_TRY);
|
|
|
|
/*
|
|
* If we're in recovery mode or we're about to boot a
|
|
* non-officially-signed kernel, there's no rollback
|
|
* protection, so we can stop at the first valid kernel.
|
|
*/
|
|
if (kBootRecovery == shcall->boot_mode || !keyblock_valid) {
|
|
VB2_DEBUG("In recovery mode or dev-signed kernel\n");
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, we do care about the key index in the TPM. If
|
|
* the good partition's key version is the same as the tpm,
|
|
* then the TPM doesn't need updating; we can stop now.
|
|
* Otherwise, we'll check all the other headers to see if they
|
|
* contain a newer key.
|
|
*/
|
|
if (shpart->combined_version == shared->kernel_version_tpm) {
|
|
VB2_DEBUG("Same kernel version\n");
|
|
break;
|
|
}
|
|
} /* while(GptNextKernelEntry) */
|
|
|
|
gpt_done:
|
|
/* Write and free GPT data */
|
|
WriteAndFreeGptData(params->disk_handle, &gpt);
|
|
|
|
/* Handle finding a good partition */
|
|
if (params->partition_number > 0) {
|
|
VB2_DEBUG("Good partition %d\n", params->partition_number);
|
|
shcall->check_result = VBSD_LKC_CHECK_GOOD_PARTITION;
|
|
shared->kernel_version_lowest = lowest_version;
|
|
/*
|
|
* Sanity check - only store a new TPM version if we found one.
|
|
* If lowest_version is still at its initial value, we didn't
|
|
* find one; for example, we're in developer mode and just
|
|
* didn't look.
|
|
*/
|
|
if (lowest_version != LOWEST_TPM_VERSION &&
|
|
lowest_version > shared->kernel_version_tpm)
|
|
shared->kernel_version_tpm = lowest_version;
|
|
|
|
/* Success! */
|
|
retval = VB2_SUCCESS;
|
|
} else if (found_partitions > 0) {
|
|
shcall->check_result = VBSD_LKC_CHECK_INVALID_PARTITIONS;
|
|
recovery = VB2_RECOVERY_RW_INVALID_OS;
|
|
retval = VBERROR_INVALID_KERNEL_FOUND;
|
|
} else {
|
|
shcall->check_result = VBSD_LKC_CHECK_NO_PARTITIONS;
|
|
recovery = VB2_RECOVERY_RW_NO_OS;
|
|
retval = VBERROR_NO_KERNEL_FOUND;
|
|
}
|
|
|
|
load_kernel_exit:
|
|
/* Store recovery request, if any */
|
|
vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST,
|
|
VB2_SUCCESS != retval ?
|
|
recovery : VB2_RECOVERY_NOT_REQUESTED);
|
|
|
|
shcall->return_code = (uint8_t)retval;
|
|
return retval;
|
|
}
|