Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
484 lines (414 sloc) 12.7 KB
/* Copyright 2016 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.
*/
#include "config.h"
#include "board_id.h"
#include "byteorder.h"
#include "compile_time_macros.h"
#include "console.h"
#include "cryptoc/sha.h"
#include "dcrypto/dcrypto.h"
#include "extension.h"
#include "flash.h"
#include "flash_info.h"
#include "hooks.h"
#include "registers.h"
#include "signed_header.h"
#include "system.h"
#include "system_chip.h"
#include "uart.h"
#include "upgrade_fw.h"
#define CPRINTF(format, args...) cprintf(CC_EXTENSION, format, ## args)
/*
* This structure defines flash offset ranges of the RO and RW images which
* are not currently active and as such could be overwritten with an update.
*/
struct {
uint32_t ro_base_offset;
uint32_t ro_top_offset;
uint32_t rw_base_offset;
uint32_t rw_top_offset;
} valid_sections;
/* Pick sections where updates can go to based on current code addresses. */
static void set_valid_sections(void)
{
switch (system_get_ro_image_copy()) {
case SYSTEM_IMAGE_RO:
valid_sections.ro_base_offset = CHIP_RO_B_MEM_OFF;
break;
case SYSTEM_IMAGE_RO_B:
valid_sections.ro_base_offset = CONFIG_RO_MEM_OFF;
break;
default:
CPRINTF("Failed to set RO image offsets\n");
return;
}
switch (system_get_image_copy()) {
case SYSTEM_IMAGE_RW:
valid_sections.rw_base_offset = CONFIG_RW_B_MEM_OFF;
break;
case SYSTEM_IMAGE_RW_B:
valid_sections.rw_base_offset = CONFIG_RW_MEM_OFF;
break;
default:
CPRINTF("Failed to set RW image offsets\n");
return;
}
valid_sections.ro_top_offset = valid_sections.ro_base_offset +
CONFIG_RO_SIZE - 0x800; /* 2K for certs! */
valid_sections.rw_top_offset = valid_sections.rw_base_offset +
CONFIG_RW_SIZE;
}
/*
* Verify that the passed in block fits into the valid area. If it does, and
* is destined to the base address of the area - erase the area contents.
*
* Return success, or indication of an erase failure or chunk not fitting into
* valid area.
*/
static uint8_t check_update_chunk(uint32_t block_offset, size_t body_size)
{
uint32_t base;
uint32_t size;
/* Is this an RW chunk? */
if (valid_sections.rw_top_offset &&
(block_offset >= valid_sections.rw_base_offset) &&
((block_offset + body_size) <= valid_sections.rw_top_offset)) {
base = valid_sections.rw_base_offset;
size = valid_sections.rw_top_offset -
valid_sections.rw_base_offset;
/*
* If this is the first chunk for this section, it needs to
* be erased.
*/
if (block_offset == valid_sections.rw_base_offset) {
if (flash_physical_erase(base, size) != EC_SUCCESS) {
CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
__func__, __LINE__, base, size);
return UPGRADE_ERASE_FAILURE;
}
}
return UPGRADE_SUCCESS;
}
/* Is this an RO chunk? */
if (valid_sections.ro_top_offset &&
(block_offset >= valid_sections.ro_base_offset) &&
((block_offset + body_size) <= valid_sections.ro_top_offset)) {
/*
* If this is the first chunk for this section, it needs to
* be erased.
*/
if (block_offset == valid_sections.ro_base_offset) {
uint32_t base;
uint32_t size;
base = valid_sections.ro_base_offset;
size = valid_sections.ro_top_offset -
valid_sections.ro_base_offset;
/* backup RO area write access needs to be enabled. */
flash_open_ro_window(base, size);
if (flash_physical_erase(base, size) != EC_SUCCESS) {
CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
__func__, __LINE__, base, size);
return UPGRADE_ERASE_FAILURE;
}
}
return UPGRADE_SUCCESS;
}
CPRINTF("%s:%d %x, %d ro base %x top %x, rw base %x top %x\n",
__func__, __LINE__,
block_offset, body_size,
valid_sections.ro_base_offset,
valid_sections.ro_top_offset,
valid_sections.rw_base_offset,
valid_sections.rw_top_offset);
return UPGRADE_BAD_ADDR;
}
int usb_pdu_valid(struct upgrade_command *cmd_body, size_t cmd_size)
{
uint8_t sha1_digest[SHA_DIGEST_SIZE];
size_t body_size = cmd_size - offsetof(struct update_frame_header,
cmd.block_base);
/* Check if the block was received properly. */
DCRYPTO_SHA1_hash((uint8_t *)&cmd_body->block_base,
body_size + sizeof(cmd_body->block_base),
sha1_digest);
if (memcmp(sha1_digest, &cmd_body->block_digest,
sizeof(cmd_body->block_digest))) {
CPRINTF("%s:%d sha1 %x not equal received %x\n",
__func__, __LINE__,
*(uint32_t *)sha1_digest, cmd_body->block_digest);
return 0;
}
return 1;
}
#ifdef CR50_DEV
#ifndef CONFIG_IGNORE_G_UPDATE_CHECKS
#define CONFIG_IGNORE_G_UPDATE_CHECKS
#endif
#endif
#ifndef CONFIG_IGNORE_G_UPDATE_CHECKS
/* Compare two versions, return True if the new version is older. */
static int new_is_older(const struct SignedHeader *new,
const struct SignedHeader *old)
{
if (new->epoch_ != old->epoch_)
return new->epoch_ < old->epoch_;
if (new->major_ != old->major_)
return new->major_ < old->major_;
return new->minor_ < old->minor_;
}
/*
* Check if this chunk of data is a rollback attempt, or is unaligned,
* overlaps RO or RW header, or would cause a board ID mismatch if attempted
* to run.
*
* Return False if there is any of the above problems and set the passed in
* error_code pointer to the proper error_code.
*/
static int contents_allowed(uint32_t block_offset,
size_t body_size, void *upgrade_data,
uint8_t *error_code)
{
/* Pointer to RO or RW header in flash, to compare against. */
const struct SignedHeader *header;
int is_rw_header = 0;
if (block_offset == valid_sections.ro_base_offset) {
header = (const struct SignedHeader *)
get_program_memory_addr(system_get_ro_image_copy());
} else if (block_offset == valid_sections.rw_base_offset) {
header = (const struct SignedHeader *)
get_program_memory_addr(system_get_image_copy());
is_rw_header = 1;
} else {
/*
* The received block is not destined to a header directly,
* but does it overlap with a header by any chance?
*/
int i;
/* Base offsets of valid headers in flash. */
uint32_t bases[] = { valid_sections.ro_base_offset,
valid_sections.rw_base_offset };
/* Range of offsets this block is covering. */
uint32_t range[] = { block_offset, block_offset + body_size };
for (i = 0; i < ARRAY_SIZE(bases); i++) {
int j;
for (j = 0; j < ARRAY_SIZE(range); j++) {
if ((range[j] >= bases[i]) &&
(range[j] <
(bases[i] +
sizeof(struct SignedHeader)))) {
CPRINTF("%s:"
" unaligned block overlaps\n",
__func__);
*error_code =
UPGRADE_UNALIGNED_BLOCK_ERROR;
return 0;
}
}
}
return 1;
}
/* This block is a header (ro or rw) of the new image. */
if (body_size < sizeof(struct SignedHeader)) {
CPRINTF("%s: block too short\n", __func__);
*error_code = UPGRADE_TRUNCATED_HEADER_ERROR;
return 0;
}
/* upgrade_data is the new header. */
if (new_is_older(upgrade_data, header)) {
CPRINTF("%s: rejecting an older header.\n", __func__);
*error_code = UPGRADE_ROLLBACK_ERROR;
return 0;
}
if (is_rw_header && board_id_mismatch(upgrade_data)) {
CPRINTF("%s: rejecting Board ID mismatch.\n", __func__);
*error_code = UPGRADE_BOARD_ID_ERROR;
return 0;
}
return 1;
}
static uint32_t prev_offset;
static uint64_t prev_timestamp;
#define BACKOFF_TIME (60 * SECOND)
static int chunk_came_too_soon(uint32_t block_offset)
{
int hard_reset = system_get_reset_flags() & RESET_FLAG_HARD;
/*
* If it has been BACKOFF_TIME since the last time we wrote to a block
* or since the last boot, the write is ok.
*/
if ((get_time().val - prev_timestamp) > BACKOFF_TIME)
return 0;
if (!prev_timestamp) {
/*
* If we just recovered from a hard reset, we have to wait until
* backoff time to accept an update. All other resets can accept
* updates immediately.
*/
if (hard_reset)
CPRINTF("%s: rejecting a write after hard reset\n",
__func__);
return hard_reset;
}
if (!prev_offset ||
(block_offset >= (prev_offset + SIGNED_TRANSFER_SIZE)))
return 0;
CPRINTF("%s: rejecting a write to the same block\n", __func__);
return 1;
}
static void new_chunk_written(uint32_t block_offset)
{
prev_timestamp = get_time().val;
prev_offset = block_offset;
}
#else
static int chunk_came_too_soon(uint32_t block_offset)
{
return 0;
}
static void new_chunk_written(uint32_t block_offset)
{
}
static int contents_allowed(uint32_t block_offset,
size_t body_size, void *upgrade_data,
uint8_t *error_code)
{
#ifndef CR50_DEV
#ifdef CONFIG_BOARD_ID_SUPPORT
if (block_offset == valid_sections.rw_base_offset) {
/* This block is a rw header of the new image. */
if (body_size < sizeof(struct SignedHeader)) {
CPRINTF("%s: block too short\n", __func__);
*error_code = UPGRADE_TRUNCATED_HEADER_ERROR;
return 0;
}
if (board_id_mismatch(upgrade_data)) {
CPRINTF("%s: rejecting Board ID mismatch.\n", __func__);
*error_code = UPGRADE_BOARD_ID_ERROR;
return 0;
}
}
#endif
#endif
return 1;
}
#endif
void fw_upgrade_command_handler(void *body,
size_t cmd_size,
size_t *response_size)
{
struct upgrade_command *cmd_body = body;
void *upgrade_data;
uint8_t *error_code = body; /* Cache the address for code clarity. */
size_t body_size;
uint32_t block_offset;
*response_size = 1; /* One byte response unless this is a start PDU. */
if (cmd_size < sizeof(struct upgrade_command)) {
CPRINTF("%s:%d\n", __func__, __LINE__);
*error_code = UPGRADE_GEN_ERROR;
return;
}
body_size = cmd_size - sizeof(struct upgrade_command);
if (!cmd_body->block_base && !body_size) {
struct first_response_pdu *rpdu = body;
const struct SignedHeader *header;
/*
* This is the connection establishment request, the response
* allows the server to decide what sections of the image to
* send to program into the flash.
*/
/* First, prepare the response structure. */
memset(rpdu, 0, sizeof(*rpdu));
*response_size = sizeof(*rpdu);
rpdu->protocol_version = htobe32(UPGRADE_PROTOCOL_VERSION);
/*
* Determine the valid upgrade sections.
*/
set_valid_sections();
/*
* If there have been any problems when determining the valid
* Sections offsets/sizes - return an error code.
*/
if (!valid_sections.ro_top_offset ||
!valid_sections.rw_top_offset) {
CPRINTF("%s:%d\n", __func__, __LINE__);
rpdu->return_value = htobe32(UPGRADE_GEN_ERROR);
return;
}
rpdu->backup_ro_offset =
htobe32(valid_sections.ro_base_offset);
rpdu->backup_rw_offset =
htobe32(valid_sections.rw_base_offset);
/* RO header information. */
header = (const struct SignedHeader *)
get_program_memory_addr(system_get_ro_image_copy());
rpdu->shv[0].minor = htobe32(header->minor_);
rpdu->shv[0].major = htobe32(header->major_);
rpdu->shv[0].epoch = htobe32(header->epoch_);
/* New with protocol version 5 */
rpdu->keyid[0] = htobe32(header->keyid);
/* RW header information. */
header = (const struct SignedHeader *)
get_program_memory_addr(system_get_image_copy());
rpdu->shv[1].minor = htobe32(header->minor_);
rpdu->shv[1].major = htobe32(header->major_);
rpdu->shv[1].epoch = htobe32(header->epoch_);
/* New with protocol version 5 */
rpdu->keyid[1] = htobe32(header->keyid);
return;
}
block_offset = be32toh(cmd_body->block_base);
if (!usb_pdu_valid(cmd_body, cmd_size)) {
*error_code = UPGRADE_DATA_ERROR;
return;
}
upgrade_data = cmd_body + 1;
if (!contents_allowed(block_offset, body_size,
upgrade_data, error_code))
return;
/* Check if the block will fit into the valid area. */
*error_code = check_update_chunk(block_offset, body_size);
if (*error_code)
return;
if (chunk_came_too_soon(block_offset)) {
*error_code = UPGRADE_RATE_LIMIT_ERROR;
return;
}
if ((block_offset == valid_sections.ro_base_offset) ||
(block_offset == valid_sections.rw_base_offset)) {
/*
* This is the header coming, let's corrupt it so that it does
* not run until it's time to switch.
*/
struct SignedHeader *header;
header = (struct SignedHeader *) upgrade_data;
/*
* Set the top bit of the size field. It will be impossible to
* run this image until this bit is erased.
*/
header->image_size |= TOP_IMAGE_SIZE_BIT;
}
CPRINTF("at 0x%x\n", block_offset + CONFIG_PROGRAM_MEMORY_BASE);
if (flash_physical_write(block_offset, body_size, upgrade_data)
!= EC_SUCCESS) {
*error_code = UPGRADE_WRITE_FAILURE;
CPRINTF("%s:%d upgrade write error\n", __func__, __LINE__);
return;
}
new_chunk_written(block_offset);
/* Verify that data was written properly. */
if (memcmp(upgrade_data, (void *)
(block_offset + CONFIG_PROGRAM_MEMORY_BASE),
body_size)) {
*error_code = UPGRADE_VERIFY_ERROR;
CPRINTF("%s:%d upgrade verification error\n",
__func__, __LINE__);
return;
}
*error_code = UPGRADE_SUCCESS;
}
void fw_upgrade_complete(void)
{
system_clear_retry_counter();
}