484 lines
12 KiB
C
484 lines
12 KiB
C
/* Copyright 2012 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.
|
|
*/
|
|
|
|
/* Verified boot hash computing module for Chrome EC */
|
|
|
|
#include "clock.h"
|
|
#include "common.h"
|
|
#include "console.h"
|
|
#include "flash.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "sha256.h"
|
|
#include "shared_mem.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
#include "watchdog.h"
|
|
|
|
/* Console output macros */
|
|
#define CPUTS(outstr) cputs(CC_VBOOT, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_VBOOT, format, ## args)
|
|
|
|
struct vboot_hash_tag {
|
|
uint8_t hash[SHA256_DIGEST_SIZE];
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
};
|
|
|
|
#define VBOOT_HASH_SYSJUMP_TAG 0x5648 /* "VH" */
|
|
#define VBOOT_HASH_SYSJUMP_VERSION 1
|
|
|
|
#define CHUNK_SIZE 1024 /* Bytes to hash per deferred call */
|
|
#define WORK_INTERVAL_US 100 /* Delay between deferred calls */
|
|
|
|
/* Check that CHUNK_SIZE fits in shared memory. */
|
|
SHARED_MEM_CHECK_SIZE(CHUNK_SIZE);
|
|
|
|
static uint32_t data_offset;
|
|
static uint32_t data_size;
|
|
static uint32_t curr_pos;
|
|
static const uint8_t *hash; /* Hash, or NULL if not valid */
|
|
static int want_abort;
|
|
static int in_progress;
|
|
|
|
static struct sha256_ctx ctx;
|
|
|
|
int vboot_hash_in_progress(void)
|
|
{
|
|
return in_progress;
|
|
}
|
|
|
|
/**
|
|
* Abort hash currently in progress, and invalidate any completed hash.
|
|
*/
|
|
void vboot_hash_abort(void)
|
|
{
|
|
if (in_progress) {
|
|
want_abort = 1;
|
|
} else {
|
|
CPRINTS("hash abort");
|
|
want_abort = 0;
|
|
data_size = 0;
|
|
hash = NULL;
|
|
}
|
|
}
|
|
|
|
static void vboot_hash_next_chunk(void);
|
|
DECLARE_DEFERRED(vboot_hash_next_chunk);
|
|
|
|
#ifndef CONFIG_MAPPED_STORAGE
|
|
|
|
static int read_and_hash_chunk(int offset, int size)
|
|
{
|
|
char *buf;
|
|
int rv;
|
|
|
|
if (size == 0)
|
|
return EC_SUCCESS;
|
|
|
|
rv = shared_mem_acquire(size, &buf);
|
|
if (rv == EC_ERROR_BUSY) {
|
|
/* Couldn't update hash right now; try again later */
|
|
hook_call_deferred(&vboot_hash_next_chunk_data,
|
|
WORK_INTERVAL_US);
|
|
return rv;
|
|
} else if (rv != EC_SUCCESS) {
|
|
vboot_hash_abort();
|
|
return rv;
|
|
}
|
|
|
|
rv = flash_read(offset, size, buf);
|
|
if (rv == EC_SUCCESS)
|
|
SHA256_update(&ctx, (const uint8_t *)buf, size);
|
|
else
|
|
vboot_hash_abort();
|
|
|
|
shared_mem_release(buf);
|
|
return rv;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_CONSOLE_VERBOSE
|
|
#define SHA256_PRINT_SIZE SHA256_DIGEST_SIZE
|
|
#else
|
|
#define SHA256_PRINT_SIZE 4
|
|
#endif
|
|
|
|
/**
|
|
* Do next chunk of hashing work, if any.
|
|
*/
|
|
static void vboot_hash_next_chunk(void)
|
|
{
|
|
int size;
|
|
|
|
/* Handle abort */
|
|
if (want_abort) {
|
|
in_progress = 0;
|
|
clock_enable_module(MODULE_FAST_CPU, 0);
|
|
vboot_hash_abort();
|
|
return;
|
|
}
|
|
|
|
/* Compute the next chunk of hash */
|
|
size = MIN(CHUNK_SIZE, data_size - curr_pos);
|
|
|
|
#ifdef CONFIG_MAPPED_STORAGE
|
|
flash_lock_mapped_storage(1);
|
|
SHA256_update(&ctx, (const uint8_t *)(CONFIG_MAPPED_STORAGE_BASE +
|
|
data_offset + curr_pos), size);
|
|
flash_lock_mapped_storage(0);
|
|
#else
|
|
if (read_and_hash_chunk(data_offset + curr_pos, size) != EC_SUCCESS)
|
|
return;
|
|
#endif
|
|
|
|
curr_pos += size;
|
|
if (curr_pos >= data_size) {
|
|
/* Store the final hash */
|
|
hash = SHA256_final(&ctx);
|
|
CPRINTS("hash done %.*h", SHA256_PRINT_SIZE, hash);
|
|
|
|
in_progress = 0;
|
|
|
|
clock_enable_module(MODULE_FAST_CPU, 0);
|
|
|
|
/* Handle receiving abort during finalize */
|
|
if (want_abort)
|
|
vboot_hash_abort();
|
|
|
|
return;
|
|
}
|
|
|
|
/* If we're still here, more work to do; come back later */
|
|
hook_call_deferred(&vboot_hash_next_chunk_data, WORK_INTERVAL_US);
|
|
}
|
|
|
|
/**
|
|
* Start computing a hash of <size> bytes of data at flash offset <offset>.
|
|
*
|
|
* If nonce_size is non-zero, prefixes the <nonce> onto the data to be hashed.
|
|
* Returns non-zero if error.
|
|
*/
|
|
static int vboot_hash_start(uint32_t offset, uint32_t size,
|
|
const uint8_t *nonce, int nonce_size)
|
|
{
|
|
/* Fail if hash computation is already in progress */
|
|
if (in_progress)
|
|
return EC_ERROR_BUSY;
|
|
|
|
/*
|
|
* Make sure request fits inside flash. That is, you can't use this
|
|
* command to peek at other memory.
|
|
*/
|
|
if (offset > CONFIG_FLASH_SIZE || size > CONFIG_FLASH_SIZE ||
|
|
offset + size > CONFIG_FLASH_SIZE || nonce_size < 0) {
|
|
return EC_ERROR_INVAL;
|
|
}
|
|
|
|
clock_enable_module(MODULE_FAST_CPU, 1);
|
|
/* Save new hash request */
|
|
data_offset = offset;
|
|
data_size = size;
|
|
curr_pos = 0;
|
|
hash = NULL;
|
|
want_abort = 0;
|
|
in_progress = 1;
|
|
|
|
/* Restart the hash computation */
|
|
CPRINTS("hash start 0x%08x 0x%08x", offset, size);
|
|
SHA256_init(&ctx);
|
|
if (nonce_size)
|
|
SHA256_update(&ctx, nonce, nonce_size);
|
|
|
|
hook_call_deferred(&vboot_hash_next_chunk_data, 0);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int vboot_hash_invalidate(int offset, int size)
|
|
{
|
|
/* Don't invalidate if passed an invalid region */
|
|
if (offset < 0 || size <= 0 || offset + size < 0)
|
|
return 0;
|
|
|
|
/* Don't invalidate if hash is already invalid */
|
|
if (!hash)
|
|
return 0;
|
|
|
|
/*
|
|
* Always invalidate zero-size hash. No overlap if passed region is off
|
|
* either end of hashed region.
|
|
*/
|
|
if (data_size > 0 &&
|
|
(offset + size <= data_offset || offset >= data_offset + data_size))
|
|
return 0;
|
|
|
|
/* Invalidate the hash */
|
|
CPRINTS("hash invalidated 0x%08x 0x%08x", offset, size);
|
|
vboot_hash_abort();
|
|
return 1;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hooks */
|
|
|
|
/**
|
|
* Returns the size of a RW copy to be hashed as expected by Softsync.
|
|
*/
|
|
static uint32_t get_rw_size(void)
|
|
{
|
|
#ifdef CONFIG_VBOOT_EFS
|
|
return CONFIG_RW_SIZE;
|
|
#else
|
|
return system_get_image_used(SYSTEM_IMAGE_RW);
|
|
#endif
|
|
}
|
|
|
|
static void vboot_hash_init(void)
|
|
{
|
|
#ifdef CONFIG_SAVE_VBOOT_HASH
|
|
const struct vboot_hash_tag *tag;
|
|
int version, size;
|
|
|
|
tag = (const struct vboot_hash_tag *)system_get_jump_tag(
|
|
VBOOT_HASH_SYSJUMP_TAG, &version, &size);
|
|
if (tag && version == VBOOT_HASH_SYSJUMP_VERSION &&
|
|
size == sizeof(*tag)) {
|
|
/* Already computed a hash, so don't recompute */
|
|
CPRINTS("hash precomputed");
|
|
hash = tag->hash;
|
|
data_offset = tag->offset;
|
|
data_size = tag->size;
|
|
} else
|
|
#endif
|
|
#ifdef CONFIG_HOSTCMD_EVENTS
|
|
/*
|
|
* Don't auto-start hash computation if we've asked the host to enter
|
|
* recovery mode since we probably won't need the hash. Although
|
|
* the host is capable of clearing this host event, the host is
|
|
* likely not even up and running yet in the case of cold boot, due to
|
|
* the power sequencing task not having run yet.
|
|
*/
|
|
if (!(host_get_events() &
|
|
EC_HOST_EVENT_MASK(EC_HOST_EVENT_KEYBOARD_RECOVERY)))
|
|
#endif
|
|
{
|
|
/* Start computing the hash of RW firmware */
|
|
vboot_hash_start(flash_get_rw_offset(system_get_active_copy()),
|
|
get_rw_size(), NULL, 0);
|
|
}
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, vboot_hash_init, HOOK_PRIO_INIT_VBOOT_HASH);
|
|
|
|
#ifdef CONFIG_SAVE_VBOOT_HASH
|
|
|
|
static int vboot_hash_preserve_state(void)
|
|
{
|
|
struct vboot_hash_tag tag;
|
|
|
|
/* If we haven't finished our hash, nothing to save */
|
|
if (!hash)
|
|
return EC_SUCCESS;
|
|
|
|
memcpy(tag.hash, hash, sizeof(tag.hash));
|
|
tag.offset = data_offset;
|
|
tag.size = data_size;
|
|
system_add_jump_tag(VBOOT_HASH_SYSJUMP_TAG,
|
|
VBOOT_HASH_SYSJUMP_VERSION,
|
|
sizeof(tag), &tag);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOOK(HOOK_SYSJUMP, vboot_hash_preserve_state, HOOK_PRIO_DEFAULT);
|
|
|
|
#endif
|
|
|
|
/**
|
|
* Returns the offset of RO or RW image if the either region is specifically
|
|
* requested otherwise return the current hash offset.
|
|
*/
|
|
static int get_offset(int offset)
|
|
{
|
|
if (offset == EC_VBOOT_HASH_OFFSET_RO)
|
|
return CONFIG_EC_PROTECTED_STORAGE_OFF + CONFIG_RO_STORAGE_OFF;
|
|
if (offset == EC_VBOOT_HASH_OFFSET_ACTIVE)
|
|
return flash_get_rw_offset(system_get_active_copy());
|
|
if (offset == EC_VBOOT_HASH_OFFSET_UPDATE)
|
|
return flash_get_rw_offset(system_get_update_copy());
|
|
return offset;
|
|
}
|
|
|
|
/****************************************************************************/
|
|
/* Console commands */
|
|
#ifdef CONFIG_CMD_HASH
|
|
static int command_hash(int argc, char **argv)
|
|
{
|
|
uint32_t offset = CONFIG_EC_WRITABLE_STORAGE_OFF +
|
|
CONFIG_RW_STORAGE_OFF;
|
|
uint32_t size = CONFIG_RW_SIZE;
|
|
char *e;
|
|
|
|
if (argc == 1) {
|
|
ccprintf("Offset: 0x%08x\n", data_offset);
|
|
ccprintf("Size: 0x%08x (%d)\n", data_size, data_size);
|
|
ccprintf("Digest: ");
|
|
if (want_abort)
|
|
ccprintf("(aborting)\n");
|
|
else if (in_progress)
|
|
ccprintf("(in progress)\n");
|
|
else if (hash)
|
|
ccprintf("%.*h\n", SHA256_DIGEST_SIZE, hash);
|
|
else
|
|
ccprintf("(invalid)\n");
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
if (argc == 2) {
|
|
if (!strcasecmp(argv[1], "abort")) {
|
|
vboot_hash_abort();
|
|
return EC_SUCCESS;
|
|
} else if (!strcasecmp(argv[1], "rw")) {
|
|
return vboot_hash_start(
|
|
get_offset(EC_VBOOT_HASH_OFFSET_ACTIVE),
|
|
get_rw_size(), NULL, 0);
|
|
} else if (!strcasecmp(argv[1], "ro")) {
|
|
return vboot_hash_start(
|
|
CONFIG_EC_PROTECTED_STORAGE_OFF +
|
|
CONFIG_RO_STORAGE_OFF,
|
|
system_get_image_used(SYSTEM_IMAGE_RO),
|
|
NULL, 0);
|
|
}
|
|
return EC_ERROR_PARAM2;
|
|
}
|
|
|
|
if (argc >= 3) {
|
|
offset = strtoi(argv[1], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
size = strtoi(argv[2], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
}
|
|
|
|
if (argc == 4) {
|
|
int nonce = strtoi(argv[3], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
return vboot_hash_start(offset, size,
|
|
(const uint8_t *)&nonce,
|
|
sizeof(nonce));
|
|
} else
|
|
return vboot_hash_start(offset, size, NULL, 0);
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(hash, command_hash,
|
|
"[abort | ro | rw] | [<offset> <size> [<nonce>]]",
|
|
"Request hash recomputation");
|
|
#endif /* CONFIG_CMD_HASH */
|
|
/****************************************************************************/
|
|
/* Host commands */
|
|
|
|
/* Fill in the response with the current hash status */
|
|
static void fill_response(struct ec_response_vboot_hash *r,
|
|
int request_offset)
|
|
{
|
|
if (in_progress)
|
|
r->status = EC_VBOOT_HASH_STATUS_BUSY;
|
|
else if (get_offset(request_offset) == data_offset && hash &&
|
|
!want_abort) {
|
|
r->status = EC_VBOOT_HASH_STATUS_DONE;
|
|
r->hash_type = EC_VBOOT_HASH_TYPE_SHA256;
|
|
r->digest_size = SHA256_DIGEST_SIZE;
|
|
r->reserved0 = 0;
|
|
r->offset = data_offset;
|
|
r->size = data_size;
|
|
ASSERT(SHA256_DIGEST_SIZE < sizeof(r->hash_digest));
|
|
memcpy(r->hash_digest, hash, SHA256_DIGEST_SIZE);
|
|
} else
|
|
r->status = EC_VBOOT_HASH_STATUS_NONE;
|
|
}
|
|
|
|
/**
|
|
* Start computing a hash, with sanity checking on params.
|
|
*
|
|
* @return EC_RES_SUCCESS if success, or other result code on error.
|
|
*/
|
|
static int host_start_hash(const struct ec_params_vboot_hash *p)
|
|
{
|
|
int offset = p->offset;
|
|
int size = p->size;
|
|
int rv;
|
|
|
|
/* Sanity-check input params */
|
|
if (p->hash_type != EC_VBOOT_HASH_TYPE_SHA256)
|
|
return EC_RES_INVALID_PARAM;
|
|
if (p->nonce_size > sizeof(p->nonce_data))
|
|
return EC_RES_INVALID_PARAM;
|
|
|
|
/* Handle special offset values */
|
|
if (offset == EC_VBOOT_HASH_OFFSET_RO)
|
|
size = system_get_image_used(SYSTEM_IMAGE_RO);
|
|
else if ((offset == EC_VBOOT_HASH_OFFSET_ACTIVE) ||
|
|
(offset == EC_VBOOT_HASH_OFFSET_UPDATE))
|
|
size = get_rw_size();
|
|
offset = get_offset(offset);
|
|
rv = vboot_hash_start(offset, size, p->nonce_data, p->nonce_size);
|
|
|
|
if (rv == EC_SUCCESS)
|
|
return EC_RES_SUCCESS;
|
|
else if (rv == EC_ERROR_INVAL)
|
|
return EC_RES_INVALID_PARAM;
|
|
else
|
|
return EC_RES_ERROR;
|
|
}
|
|
|
|
static enum ec_status
|
|
host_command_vboot_hash(struct host_cmd_handler_args *args)
|
|
{
|
|
const struct ec_params_vboot_hash *p = args->params;
|
|
struct ec_response_vboot_hash *r = args->response;
|
|
int rv;
|
|
|
|
switch (p->cmd) {
|
|
case EC_VBOOT_HASH_GET:
|
|
if (p->offset || p->size)
|
|
fill_response(r, p->offset);
|
|
else
|
|
fill_response(r, data_offset);
|
|
|
|
args->response_size = sizeof(*r);
|
|
return EC_RES_SUCCESS;
|
|
|
|
case EC_VBOOT_HASH_ABORT:
|
|
vboot_hash_abort();
|
|
return EC_RES_SUCCESS;
|
|
|
|
case EC_VBOOT_HASH_START:
|
|
case EC_VBOOT_HASH_RECALC:
|
|
rv = host_start_hash(p);
|
|
if (rv != EC_RES_SUCCESS)
|
|
return rv;
|
|
|
|
/* Wait for hash to finish if command is RECALC */
|
|
if (p->cmd == EC_VBOOT_HASH_RECALC)
|
|
while (in_progress)
|
|
usleep(1000);
|
|
|
|
fill_response(r, p->offset);
|
|
args->response_size = sizeof(*r);
|
|
return EC_RES_SUCCESS;
|
|
|
|
default:
|
|
return EC_RES_INVALID_PARAM;
|
|
}
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_VBOOT_HASH,
|
|
host_command_vboot_hash,
|
|
EC_VER_MASK(0));
|