Files
OpenCellular/common/rollback.c
Nicolas Boichat ccad39d1b8 rollback: Add option to store secret in rollback info
For pairing purpose, we want to store some secret random number in
the base. The most convenient location for this is the rollback
region.

Since the rollback region can now be updated without incrementing
rollback_min_version (when we add entropy to the secret), we need
to add an increasing id to tell the code which rollback region is
the latest.

We also add console commands to manually add entropy.

BRANCH=none
BUG=b:38486828
TEST=Flash hammer (with or without CONFIG_ROLLBACK_ENTROPY_SIZE set)
     rollbackinfo => 1 version 0 block, 1 empty block, RW verifies
           correctly.
     rollbackupdate 0; rollbackinfo => No change
     rollbackupdate 1; reboot => RO refuses to jump to RW
     only when CONFIG_ROLLBACK_ENTROPY_SIZE is set:
       rollbackinfo => Secret is [00..00] on both blocks (so the data
                     was copied correctly)
     rollbackupdate 2, 3, 4; rollbackinfo => Writes alternate
           between the 2 blocks.
     rollbackupdate 2 => Refuses to downgrade version
TEST=From blank secret [00..00], 'rollbackaddent Hello' updates it
         to [ba..fa], which matches the output of:
         (dd if=/dev/zero bs=1 count=32; echo -n Hello) | sha256sum

Change-Id: I79c3e790e56e21958cc1b4ba05bd4e5f359d3090
Reviewed-on: https://chromium-review.googlesource.com/511985
Commit-Ready: Nicolas Boichat <drinkcat@chromium.org>
Tested-by: Nicolas Boichat <drinkcat@chromium.org>
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
2017-05-25 04:27:41 -07:00

323 lines
7.9 KiB
C

/* Copyright 2017 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.
*/
/* Rollback protection logic. */
#include "common.h"
#include "console.h"
#include "flash.h"
#include "rollback.h"
#include "sha256.h"
#include "system.h"
#include "util.h"
/* Console output macros */
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
/* Number of rollback regions */
#define ROLLBACK_REGIONS 2
/*
* Note: Do not change this structure without also updating
* common/firmware_image.S .image.ROLLBACK section.
*/
struct rollback_data {
int32_t id; /* Incrementing number to indicate which region to use. */
int32_t rollback_min_version;
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
uint8_t secret[CONFIG_ROLLBACK_SECRET_SIZE];
#endif
/* cookie must always be last, as it validates the rest of the data. */
uint32_t cookie;
};
/* We need at least 2 erasable blocks in the rollback region. */
BUILD_ASSERT(CONFIG_ROLLBACK_SIZE >= ROLLBACK_REGIONS*CONFIG_FLASH_ERASE_SIZE);
BUILD_ASSERT(sizeof(struct rollback_data) <= CONFIG_FLASH_ERASE_SIZE);
static uintptr_t get_rollback_offset(int region)
{
return CONFIG_ROLLBACK_OFF + region * CONFIG_FLASH_ERASE_SIZE;
}
static int read_rollback(int region, struct rollback_data *data)
{
uintptr_t offset;
offset = get_rollback_offset(region);
if (flash_read(offset, sizeof(*data), (char *)data))
return EC_ERROR_UNKNOWN;
return EC_SUCCESS;
}
/*
* Get the most recent rollback information.
*
* @data: Returns most recent rollback data block. The data is filled
* with zeros if no valid rollback block is present
*
* Return most recent region index on success (>= 0, or 0 if no rollback
* region is valid), negative value on error.
*/
static int get_latest_rollback(struct rollback_data *data)
{
int region;
int min_region = -1;
int max_id = -1;
for (region = 0; region < ROLLBACK_REGIONS; region++) {
struct rollback_data tmp_data;
if (read_rollback(region, &tmp_data))
return -1;
/* Check if not initialized or invalid cookie. */
if (tmp_data.cookie != CROS_EC_ROLLBACK_COOKIE)
continue;
if (tmp_data.id > max_id) {
min_region = region;
max_id = tmp_data.id;
}
}
if (min_region >= 0) {
if (read_rollback(min_region, data))
return -1;
} else {
min_region = 0;
memset(data, 0, sizeof(*data));
}
return min_region;
}
int32_t rollback_get_minimum_version(void)
{
struct rollback_data data;
if (get_latest_rollback(&data) < 0)
return -1;
return data.rollback_min_version;
}
int rollback_lock(void)
{
int ret;
/* Already locked */
if (flash_get_protect() & EC_FLASH_PROTECT_ROLLBACK_NOW)
return EC_SUCCESS;
CPRINTS("Protecting rollback");
/* This may do nothing if WP is not enabled, or RO is not protected. */
ret = flash_set_protect(EC_FLASH_PROTECT_ROLLBACK_AT_BOOT, -1);
if (!(flash_get_protect() & EC_FLASH_PROTECT_ROLLBACK_NOW) &&
flash_get_protect() & EC_FLASH_PROTECT_ROLLBACK_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);
}
return ret;
}
#ifdef CONFIG_ROLLBACK_UPDATE
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
static void add_entropy(uint8_t *dst, const uint8_t *src,
uint8_t *add, unsigned int add_len)
{
#ifdef CONFIG_SHA256
BUILD_ASSERT(SHA256_DIGEST_SIZE == CONFIG_ROLLBACK_SECRET_SIZE);
struct sha256_ctx ctx;
uint8_t *hash;
SHA256_init(&ctx);
SHA256_update(&ctx, src, CONFIG_ROLLBACK_SECRET_SIZE);
SHA256_update(&ctx, add, add_len);
/* TODO(b:38486828): Add other sources of entropy (e.g. device id) */
hash = SHA256_final(&ctx);
memcpy(dst, hash, CONFIG_ROLLBACK_SECRET_SIZE);
#else
#error "Adding entropy to secret in rollback region requires SHA256."
#endif
}
#endif /* CONFIG_ROLLBACK_SECRET_SIZE */
/**
* Update rollback block.
*
* @param next_min_version Minimum version to update in rollback block. Can
* be a negative value if entropy is provided (in
* that case the current minimum version is kept).
* @param entropy Entropy to be added to rollback block secret
* (can be NULL, in that case no entropy is added).
* @param len entropy length
*
* @return EC_SUCCESS on success, EC_ERROR_* on error.
*/
static int rollback_update(int32_t next_min_version,
uint8_t *entropy, unsigned int length)
{
struct rollback_data data;
uintptr_t offset;
int region;
if (flash_get_protect() & EC_FLASH_PROTECT_ROLLBACK_NOW)
return EC_ERROR_ACCESS_DENIED;
region = get_latest_rollback(&data);
if (region < 0)
return EC_ERROR_UNKNOWN;
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
if (entropy) {
/* Do not accept to decrease the value. */
if (next_min_version < data.rollback_min_version)
next_min_version = data.rollback_min_version;
} else
#endif
{
/* Do not accept to decrease the value. */
if (next_min_version < data.rollback_min_version)
return EC_ERROR_INVAL;
/* No need to update if version is already correct. */
if (next_min_version == data.rollback_min_version)
return EC_SUCCESS;
}
/* Use the other region. */
region = (region + 1) % ROLLBACK_REGIONS;
offset = get_rollback_offset(region);
data.id = data.id + 1;
data.rollback_min_version = next_min_version;
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
/*
* If we are provided with some entropy, add it to secret. Otherwise,
* data.secret is left untouched and written back to the other region.
*/
if (entropy)
add_entropy(data.secret, data.secret, entropy, length);
#endif
data.cookie = CROS_EC_ROLLBACK_COOKIE;
/* Offset should never be part of active image. */
if (system_unsafe_to_overwrite(offset, CONFIG_FLASH_ERASE_SIZE))
return EC_ERROR_UNKNOWN;
if (flash_erase(offset, CONFIG_FLASH_ERASE_SIZE))
return EC_ERROR_UNKNOWN;
if (flash_write(offset, sizeof(data), (char *)&data))
return EC_ERROR_UNKNOWN;
return EC_SUCCESS;
}
int rollback_update_version(int32_t next_min_version)
{
return rollback_update(next_min_version, NULL, 0);
}
int rollback_add_entropy(uint8_t *data, unsigned int len)
{
return rollback_update(-1, data, len);
}
static int command_rollback_update(int argc, char **argv)
{
int32_t min_version;
char *e;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
min_version = strtoi(argv[1], &e, 0);
if (*e || min_version < 0)
return EC_ERROR_PARAM1;
return rollback_update_version(min_version);
}
DECLARE_CONSOLE_COMMAND(rollbackupdate, command_rollback_update,
"min_version",
"Update rollback info");
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
static int command_rollback_add_entropy(int argc, char **argv)
{
int len;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
len = strlen(argv[1]);
return rollback_add_entropy(argv[1], len);
}
DECLARE_CONSOLE_COMMAND(rollbackaddent, command_rollback_add_entropy,
"data",
"Add entropy to rollback block");
#endif
#endif /* CONFIG_ROLLBACK_UPDATE */
static int command_rollback_info(int argc, char **argv)
{
int region, ret, min_region;
int32_t rw_rollback_version;
struct rollback_data data;
min_region = get_latest_rollback(&data);
if (min_region < 0)
return EC_ERROR_UNKNOWN;
rw_rollback_version = system_get_rollback_version(SYSTEM_IMAGE_RW);
ccprintf("rollback minimum version: %d\n", data.rollback_min_version);
ccprintf("RW rollback version: %d\n", rw_rollback_version);
for (region = 0; region < ROLLBACK_REGIONS; region++) {
struct rollback_data data;
ret = read_rollback(region, &data);
if (ret)
return ret;
ccprintf("rollback %d: %08x %08x %08x",
region, data.id, data.rollback_min_version,
data.cookie);
#ifdef CONFIG_ROLLBACK_SECRET_SIZE
if (!system_is_locked()) {
/* If system is unlocked, show some of the secret. */
ccprintf(" [%02x..%02x]", data.secret[0],
data.secret[CONFIG_ROLLBACK_SECRET_SIZE-1]);
}
#endif
if (min_region == region)
ccprintf(" *");
ccprintf("\n");
}
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(rollbackinfo, command_rollback_info,
NULL,
"Print rollback info");