329 lines
8.2 KiB
C
329 lines
8.2 KiB
C
|
/* Copyright 2014 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.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Implementation of the RW firmware signature verification and jump.
|
||
|
*/
|
||
|
|
||
|
#include "console.h"
|
||
|
#include "ec_commands.h"
|
||
|
#include "flash.h"
|
||
|
#include "host_command.h"
|
||
|
#include "rollback.h"
|
||
|
#include "rsa.h"
|
||
|
#include "rwsig.h"
|
||
|
#include "sha256.h"
|
||
|
#include "shared_mem.h"
|
||
|
#include "system.h"
|
||
|
#include "task.h"
|
||
|
#include "usb_pd.h"
|
||
|
#include "util.h"
|
||
|
#include "vb21_struct.h"
|
||
|
#include "version.h"
|
||
|
|
||
|
/* Console output macros */
|
||
|
#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args)
|
||
|
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
|
||
|
|
||
|
#if !defined(CONFIG_MAPPED_STORAGE)
|
||
|
#error rwsig implementation assumes mem-mapped storage.
|
||
|
#endif
|
||
|
|
||
|
/* RW firmware reset vector */
|
||
|
static uint32_t * const rw_rst =
|
||
|
(uint32_t *)(CONFIG_PROGRAM_MEMORY_BASE + CONFIG_RW_MEM_OFF + 4);
|
||
|
|
||
|
|
||
|
void rwsig_jump_now(void)
|
||
|
{
|
||
|
/* Protect all flash before jumping to RW. */
|
||
|
|
||
|
/* This may do nothing if WP is not enabled, RO is not protected. */
|
||
|
flash_set_protect(EC_FLASH_PROTECT_ALL_NOW, -1);
|
||
|
|
||
|
/*
|
||
|
* For chips that does not support EC_FLASH_PROTECT_ALL_NOW, use
|
||
|
* EC_FLASH_PROTECT_ALL_AT_BOOT.
|
||
|
*/
|
||
|
if (system_is_locked() &&
|
||
|
!(flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)) {
|
||
|
flash_set_protect(EC_FLASH_PROTECT_ALL_AT_BOOT, -1);
|
||
|
|
||
|
if (!(flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW) &&
|
||
|
flash_get_protect() & EC_FLASH_PROTECT_ALL_AT_BOOT) {
|
||
|
/*
|
||
|
* If flash protection is still not enabled (some chips
|
||
|
* may be able to enable it immediately), reboot.
|
||
|
*/
|
||
|
cflush();
|
||
|
system_reset(SYSTEM_RESET_HARD |
|
||
|
SYSTEM_RESET_PRESERVE_FLAGS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* When system is locked, only boot to RW if all flash is protected. */
|
||
|
if (!system_is_locked() ||
|
||
|
flash_get_protect() & EC_FLASH_PROTECT_ALL_NOW)
|
||
|
system_run_image_copy(SYSTEM_IMAGE_RW);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Check that memory between rwdata[start] and rwdata[len-1] is filled
|
||
|
* with ones. data, start and len must be aligned on 4-byte boundary.
|
||
|
*/
|
||
|
static int check_padding(const uint8_t *data,
|
||
|
unsigned int start, unsigned int len)
|
||
|
{
|
||
|
unsigned int i;
|
||
|
const uint32_t *data32 = (const uint32_t *)data;
|
||
|
|
||
|
if ((start % 4) != 0 || (len % 4) != 0)
|
||
|
return 0;
|
||
|
|
||
|
for (i = start/4; i < len/4; i++) {
|
||
|
if (data32[i] != 0xffffffff)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int rwsig_check_signature(void)
|
||
|
{
|
||
|
struct sha256_ctx ctx;
|
||
|
int res;
|
||
|
const struct rsa_public_key *key;
|
||
|
const uint8_t *sig;
|
||
|
uint8_t *hash;
|
||
|
uint32_t *rsa_workbuf = NULL;
|
||
|
const uint8_t *rwdata = (uint8_t *)CONFIG_PROGRAM_MEMORY_BASE
|
||
|
+ CONFIG_RW_MEM_OFF;
|
||
|
int good = 0;
|
||
|
|
||
|
unsigned int rwlen;
|
||
|
#ifdef CONFIG_RWSIG_TYPE_RWSIG
|
||
|
const struct vb21_packed_key *vb21_key;
|
||
|
const struct vb21_signature *vb21_sig;
|
||
|
#endif
|
||
|
#ifdef CONFIG_ROLLBACK
|
||
|
int32_t rw_rollback_version;
|
||
|
int32_t min_rollback_version;
|
||
|
#endif
|
||
|
|
||
|
/* Check if we have a RW firmware flashed */
|
||
|
if (*rw_rst == 0xffffffff)
|
||
|
goto out;
|
||
|
|
||
|
CPRINTS("Verifying RW image...");
|
||
|
|
||
|
#ifdef CONFIG_ROLLBACK
|
||
|
rw_rollback_version = system_get_rollback_version(SYSTEM_IMAGE_RW);
|
||
|
min_rollback_version = rollback_get_minimum_version();
|
||
|
|
||
|
if (rw_rollback_version < 0 || min_rollback_version < 0 ||
|
||
|
rw_rollback_version < min_rollback_version) {
|
||
|
CPRINTS("Rollback error (%d < %d)",
|
||
|
rw_rollback_version, min_rollback_version);
|
||
|
goto out;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Large buffer for RSA computation : could be re-use afterwards... */
|
||
|
res = SHARED_MEM_ACQUIRE_CHECK(3 * RSANUMBYTES, (char **)&rsa_workbuf);
|
||
|
if (res) {
|
||
|
CPRINTS("No memory for RW verification");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_RWSIG_TYPE_USBPD1
|
||
|
key = (const struct rsa_public_key *)CONFIG_RO_PUBKEY_ADDR;
|
||
|
sig = (const uint8_t *)CONFIG_RW_SIG_ADDR;
|
||
|
rwlen = CONFIG_RW_SIZE - CONFIG_RW_SIG_SIZE;
|
||
|
#elif defined(CONFIG_RWSIG_TYPE_RWSIG)
|
||
|
vb21_key = (const struct vb21_packed_key *)CONFIG_RO_PUBKEY_ADDR;
|
||
|
vb21_sig = (const struct vb21_signature *)CONFIG_RW_SIG_ADDR;
|
||
|
|
||
|
if (vb21_key->c.magic != VB21_MAGIC_PACKED_KEY ||
|
||
|
vb21_key->key_size != sizeof(struct rsa_public_key)) {
|
||
|
CPRINTS("Invalid key.");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
key = (const struct rsa_public_key *)
|
||
|
((const uint8_t *)vb21_key + vb21_key->key_offset);
|
||
|
|
||
|
/*
|
||
|
* TODO(crbug.com/690773): We could verify other parameters such
|
||
|
* as sig_alg/hash_alg actually matches what we build for.
|
||
|
*/
|
||
|
if (vb21_sig->c.magic != VB21_MAGIC_SIGNATURE ||
|
||
|
vb21_sig->sig_size != RSANUMBYTES ||
|
||
|
vb21_key->sig_alg != vb21_sig->sig_alg ||
|
||
|
vb21_key->hash_alg != vb21_sig->hash_alg ||
|
||
|
/* Sanity check signature offset and data size. */
|
||
|
vb21_sig->sig_offset < sizeof(vb21_sig) ||
|
||
|
(vb21_sig->sig_offset + RSANUMBYTES) > CONFIG_RW_SIG_SIZE ||
|
||
|
vb21_sig->data_size > (CONFIG_RW_SIZE - CONFIG_RW_SIG_SIZE)) {
|
||
|
CPRINTS("Invalid signature.");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
sig = (const uint8_t *)vb21_sig + vb21_sig->sig_offset;
|
||
|
rwlen = vb21_sig->data_size;
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Check that unverified RW region is actually filled with ones.
|
||
|
*/
|
||
|
good = check_padding(rwdata, rwlen,
|
||
|
CONFIG_RW_SIZE - CONFIG_RW_SIG_SIZE);
|
||
|
if (!good) {
|
||
|
CPRINTS("Invalid padding.");
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
/* SHA-256 Hash of the RW firmware */
|
||
|
SHA256_init(&ctx);
|
||
|
SHA256_update(&ctx, rwdata, rwlen);
|
||
|
hash = SHA256_final(&ctx);
|
||
|
|
||
|
good = rsa_verify(key, sig, hash, rsa_workbuf);
|
||
|
if (!good)
|
||
|
goto out;
|
||
|
|
||
|
#ifdef CONFIG_ROLLBACK
|
||
|
/*
|
||
|
* Signature verified: we know that rw_rollback_version is valid, check
|
||
|
* if rollback information should be updated.
|
||
|
*
|
||
|
* If the RW region can be protected independently
|
||
|
* (CONFIG_FLASH_PROTECT_RW is defined), and system is locked, we only
|
||
|
* increment the rollback if RW is currently protected.
|
||
|
*
|
||
|
* Otherwise, we immediately increment the rollback version.
|
||
|
*/
|
||
|
if (rw_rollback_version != min_rollback_version
|
||
|
#ifdef CONFIG_FLASH_PROTECT_RW
|
||
|
&& ((!system_is_locked() ||
|
||
|
flash_get_protect() & EC_FLASH_PROTECT_RW_NOW))
|
||
|
#endif
|
||
|
) {
|
||
|
/*
|
||
|
* This will fail if the rollback block is protected (RW image
|
||
|
* will unprotect that block later on).
|
||
|
*/
|
||
|
int ret = rollback_update_version(rw_rollback_version);
|
||
|
|
||
|
if (ret == 0) {
|
||
|
CPRINTS("Rollback updated to %d",
|
||
|
rw_rollback_version);
|
||
|
} else if (ret != EC_ERROR_ACCESS_DENIED) {
|
||
|
CPRINTS("Rollback update error %d", ret);
|
||
|
good = 0;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
out:
|
||
|
CPRINTS("RW verify %s", good ? "OK" : "FAILED");
|
||
|
|
||
|
if (!good) {
|
||
|
pd_log_event(PD_EVENT_ACC_RW_FAIL, 0, 0, NULL);
|
||
|
/* RW firmware is invalid : do not jump there */
|
||
|
if (system_is_locked())
|
||
|
system_disable_jump();
|
||
|
}
|
||
|
if (rsa_workbuf)
|
||
|
shared_mem_release(rsa_workbuf);
|
||
|
|
||
|
return good;
|
||
|
}
|
||
|
|
||
|
#ifdef HAS_TASK_RWSIG
|
||
|
#define TASK_EVENT_ABORT TASK_EVENT_CUSTOM_BIT(0)
|
||
|
#define TASK_EVENT_CONTINUE TASK_EVENT_CUSTOM_BIT(1)
|
||
|
|
||
|
static enum rwsig_status rwsig_status;
|
||
|
|
||
|
enum rwsig_status rwsig_get_status(void)
|
||
|
{
|
||
|
return rwsig_status;
|
||
|
}
|
||
|
|
||
|
void rwsig_abort(void)
|
||
|
{
|
||
|
task_set_event(TASK_ID_RWSIG, TASK_EVENT_ABORT, 0);
|
||
|
}
|
||
|
|
||
|
void rwsig_continue(void)
|
||
|
{
|
||
|
task_set_event(TASK_ID_RWSIG, TASK_EVENT_CONTINUE, 0);
|
||
|
}
|
||
|
|
||
|
void rwsig_task(void *u)
|
||
|
{
|
||
|
uint32_t evt;
|
||
|
|
||
|
if (system_get_image_copy() != SYSTEM_IMAGE_RO)
|
||
|
goto exit;
|
||
|
|
||
|
rwsig_status = RWSIG_IN_PROGRESS;
|
||
|
if (!rwsig_check_signature()) {
|
||
|
rwsig_status = RWSIG_INVALID;
|
||
|
goto exit;
|
||
|
}
|
||
|
rwsig_status = RWSIG_VALID;
|
||
|
|
||
|
/* Jump to RW after a timeout */
|
||
|
evt = task_wait_event(CONFIG_RWSIG_JUMP_TIMEOUT);
|
||
|
|
||
|
/* Jump now if we timed out, or were told to continue. */
|
||
|
if (evt == TASK_EVENT_TIMER || evt == TASK_EVENT_CONTINUE)
|
||
|
rwsig_jump_now();
|
||
|
else
|
||
|
rwsig_status = RWSIG_ABORTED;
|
||
|
|
||
|
exit:
|
||
|
/* We're done, yield forever. */
|
||
|
while (1)
|
||
|
task_wait_event(-1);
|
||
|
}
|
||
|
|
||
|
enum ec_status rwsig_cmd_action(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
const struct ec_params_rwsig_action *p = args->params;
|
||
|
|
||
|
switch (p->action) {
|
||
|
case RWSIG_ACTION_ABORT:
|
||
|
rwsig_abort();
|
||
|
break;
|
||
|
case RWSIG_ACTION_CONTINUE:
|
||
|
rwsig_continue();
|
||
|
break;
|
||
|
default:
|
||
|
return EC_RES_INVALID_PARAM;
|
||
|
}
|
||
|
args->response_size = 0;
|
||
|
return EC_RES_SUCCESS;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_RWSIG_ACTION,
|
||
|
rwsig_cmd_action,
|
||
|
EC_VER_MASK(0));
|
||
|
|
||
|
#else /* !HAS_TASK_RWSIG */
|
||
|
enum ec_status rwsig_cmd_check_status(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
struct ec_response_rwsig_check_status *r = args->response;
|
||
|
|
||
|
memset(r, 0, sizeof(*r));
|
||
|
r->status = rwsig_check_signature();
|
||
|
args->response_size = sizeof(*r);
|
||
|
|
||
|
return EC_RES_SUCCESS;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_RWSIG_CHECK_STATUS,
|
||
|
rwsig_cmd_check_status,
|
||
|
EC_VER_MASK(0));
|
||
|
#endif
|