mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-30 18:41:11 +00:00
There is a window where the rollback information in RW could
potentially be updated during RW signature verification. We make
sure this cannot happen by:
- Preventing update over USB while RWSIG is running
- When system is locked, only update rollback information if
RW region is locked: this guarantees that RW cannot be modified
from boot until RW is validated, and then until rollback
information is updated.
Also, remove rollback_lock() in rwsig_check_signature:
rwsig_jump_now() protects all flash, which also protects rollback.
This reduces the number of required reboots on rollback update.
BRANCH=none
BUG=b:35586219
BUG=b:35587171
TEST=Add long delay in rwsig_check_signature, make sure EC cannot
be updated while verification is in progress.
Change-Id: I7a51fad8a64b7e258b3a7e15d75b3dab64ce1c94
Reviewed-on: https://chromium-review.googlesource.com/479176
Commit-Ready: Nicolas Boichat <drinkcat@chromium.org>
Tested-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
256 lines
6.8 KiB
C
256 lines
6.8 KiB
C
/* 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 "byteorder.h"
|
|
#include "console.h"
|
|
#include "extension.h"
|
|
#include "flash.h"
|
|
#include "hooks.h"
|
|
#include "include/compile_time_macros.h"
|
|
#include "rollback.h"
|
|
#include "rwsig.h"
|
|
#include "system.h"
|
|
#include "uart.h"
|
|
#include "update_fw.h"
|
|
#include "util.h"
|
|
#include "vb21_struct.h"
|
|
|
|
#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)
|
|
|
|
/* Section to be updated (i.e. not the current section). */
|
|
struct {
|
|
uint32_t base_offset;
|
|
uint32_t top_offset;
|
|
} update_section;
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* TODO(b/36375666): Each board/chip should be able to re-define this.
|
|
*/
|
|
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 (update_section.base_offset != update_section.top_offset &&
|
|
(block_offset >= update_section.base_offset) &&
|
|
((block_offset + body_size) <= update_section.top_offset)) {
|
|
|
|
base = update_section.base_offset;
|
|
size = update_section.top_offset -
|
|
update_section.base_offset;
|
|
/*
|
|
* If this is the first chunk for this section, it needs to
|
|
* be erased.
|
|
*/
|
|
if (block_offset == base) {
|
|
if (flash_physical_erase(base, size) != EC_SUCCESS) {
|
|
CPRINTF("%s:%d erase failure of 0x%x..+0x%x\n",
|
|
__func__, __LINE__, base, size);
|
|
return UPDATE_ERASE_FAILURE;
|
|
}
|
|
}
|
|
|
|
return UPDATE_SUCCESS;
|
|
}
|
|
|
|
CPRINTF("%s:%d %x, %d section base %x top %x\n",
|
|
__func__, __LINE__,
|
|
block_offset, body_size,
|
|
update_section.base_offset,
|
|
update_section.top_offset);
|
|
|
|
return UPDATE_BAD_ADDR;
|
|
|
|
}
|
|
|
|
/* TODO(b/36375666): These need to be overridden for chip/g. */
|
|
int update_pdu_valid(struct update_command *cmd_body, size_t cmd_size)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
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 *update_data)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Setup internal state (e.g. valid sections, and fill first response).
|
|
*
|
|
* Assumes rpdu is already prefilled with 0, and that version has already
|
|
* been set. May set a return_value != 0 on error.
|
|
*/
|
|
void fw_update_start(struct first_response_pdu *rpdu)
|
|
{
|
|
const char *version;
|
|
#ifdef CONFIG_RWSIG_TYPE_RWSIG
|
|
const struct vb21_packed_key *vb21_key;
|
|
#endif
|
|
|
|
rpdu->header_type = htobe16(UPDATE_HEADER_TYPE_COMMON);
|
|
|
|
/* Determine the valid update section. */
|
|
switch (system_get_image_copy()) {
|
|
case SYSTEM_IMAGE_RO:
|
|
/* RO running, so update RW */
|
|
update_section.base_offset = CONFIG_RW_MEM_OFF;
|
|
update_section.top_offset = CONFIG_RW_MEM_OFF + CONFIG_RW_SIZE;
|
|
version = system_get_version(SYSTEM_IMAGE_RW);
|
|
break;
|
|
case SYSTEM_IMAGE_RW:
|
|
/* RW running, so update RO */
|
|
update_section.base_offset = CONFIG_RO_MEM_OFF;
|
|
update_section.top_offset = CONFIG_RO_MEM_OFF + CONFIG_RO_SIZE;
|
|
version = system_get_version(SYSTEM_IMAGE_RO);
|
|
break;
|
|
default:
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
rpdu->return_value = htobe32(UPDATE_GEN_ERROR);
|
|
return;
|
|
}
|
|
|
|
rpdu->common.maximum_pdu_size = htobe32(CONFIG_UPDATE_PDU_SIZE);
|
|
rpdu->common.flash_protection = htobe32(flash_get_protect());
|
|
rpdu->common.offset = htobe32(update_section.base_offset);
|
|
if (version)
|
|
memcpy(rpdu->common.version, version,
|
|
sizeof(rpdu->common.version));
|
|
|
|
#ifdef CONFIG_ROLLBACK
|
|
rpdu->common.min_rollback = htobe32(rollback_get_minimum_version());
|
|
#else
|
|
rpdu->common.min_rollback = htobe32(-1);
|
|
#endif
|
|
|
|
#ifdef CONFIG_RWSIG_TYPE_RWSIG
|
|
vb21_key = (const struct vb21_packed_key *)CONFIG_RO_PUBKEY_ADDR;
|
|
rpdu->common.key_version = htobe32(vb21_key->key_version);
|
|
#endif
|
|
|
|
#ifdef HAS_TASK_RWSIG
|
|
/* Do not allow the update to start if RWSIG is still running. */
|
|
if (rwsig_get_status() == RWSIG_IN_PROGRESS) {
|
|
CPRINTF("RWSIG in progress\n");
|
|
rpdu->return_value = htobe32(UPDATE_RWSIG_BUSY);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void fw_update_command_handler(void *body,
|
|
size_t cmd_size,
|
|
size_t *response_size)
|
|
{
|
|
struct update_command *cmd_body = body;
|
|
void *update_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 update_command)) {
|
|
CPRINTF("%s:%d\n", __func__, __LINE__);
|
|
*error_code = UPDATE_GEN_ERROR;
|
|
return;
|
|
}
|
|
body_size = cmd_size - sizeof(struct update_command);
|
|
|
|
if (!cmd_body->block_base && !body_size) {
|
|
struct first_response_pdu *rpdu = body;
|
|
|
|
/*
|
|
* 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));
|
|
/*
|
|
* TODO(b/36375666): The response size can be shorter depending
|
|
* on which board-specific type of response we provide. This
|
|
* may send trailing 0 bytes, which should be harmless.
|
|
*/
|
|
*response_size = sizeof(*rpdu);
|
|
rpdu->protocol_version = htobe16(UPDATE_PROTOCOL_VERSION);
|
|
|
|
/* Setup internal state (e.g. valid sections, and fill rpdu) */
|
|
fw_update_start(rpdu);
|
|
return;
|
|
}
|
|
|
|
block_offset = be32toh(cmd_body->block_base);
|
|
|
|
if (!update_pdu_valid(cmd_body, cmd_size)) {
|
|
*error_code = UPDATE_DATA_ERROR;
|
|
return;
|
|
}
|
|
|
|
update_data = cmd_body + 1;
|
|
if (!contents_allowed(block_offset, body_size, update_data)) {
|
|
*error_code = UPDATE_ROLLBACK_ERROR;
|
|
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 = UPDATE_RATE_LIMIT_ERROR;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* TODO(b/36375666): chip/g code has some cr50-specific stuff right
|
|
* here, which should probably be merged into contents_allowed...
|
|
*/
|
|
|
|
CPRINTF("update: 0x%x\n", block_offset + CONFIG_PROGRAM_MEMORY_BASE);
|
|
if (flash_physical_write(block_offset, body_size, update_data)
|
|
!= EC_SUCCESS) {
|
|
*error_code = UPDATE_WRITE_FAILURE;
|
|
CPRINTF("%s:%d update write error\n", __func__, __LINE__);
|
|
return;
|
|
}
|
|
|
|
new_chunk_written(block_offset);
|
|
|
|
/* Verify that data was written properly. */
|
|
if (memcmp(update_data, (void *)
|
|
(block_offset + CONFIG_PROGRAM_MEMORY_BASE),
|
|
body_size)) {
|
|
*error_code = UPDATE_VERIFY_ERROR;
|
|
CPRINTF("%s:%d update verification error\n",
|
|
__func__, __LINE__);
|
|
return;
|
|
}
|
|
|
|
*error_code = UPDATE_SUCCESS;
|
|
}
|
|
|
|
/* TODO(b/36375666): This need to be overridden for chip/g. */
|
|
void fw_update_complete(void)
|
|
{
|
|
}
|