diff options
author | Randall Spangler <rspangler@chromium.org> | 2012-06-22 12:56:56 -0700 |
---|---|---|
committer | Gerrit <chrome-bot@google.com> | 2012-06-25 15:37:42 -0700 |
commit | 900c0215b4f47551c850259ef7c716285c46e8b5 (patch) | |
tree | d467eb3c50b9a3def174db18b7dc6dcfb110ab43 | |
parent | aab92d552d7274f00e624bc8bc74ec41d3101b6d (diff) | |
download | chrome-ec-900c0215b4f47551c850259ef7c716285c46e8b5.tar.gz |
Add hash support
EC computes a SHA-256 hash of its RW code on boot. Also adds host and
console commands to tell the EC to recompute the hash, or hash a
different section of flash memory.
BUG=chrome-os-partner:10777
TEST=manual
1) ectool echash -> should match what the EC precomputed
2a) ectool echash recalc 0 0x10000 5
2b) on EC console, 'hash 0 0x10000 5'
2c) results should agree
3a) on ec console, 'hash 0 0x3e000' then quickly 'hash abort'
3b) ectool echash -> status should be unavailable
4) ectool echash start 0 0x3e000 6 && ectool echash && ectool echash abort && sleep 2 && ectool echash
status should be busy, then unavailable
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Change-Id: I6806d7b4d4dca3a74f476092551b4dba875d558e
Reviewed-on: https://gerrit.chromium.org/gerrit/26023
-rw-r--r-- | board/bds/ec.tasklist | 1 | ||||
-rw-r--r-- | board/link/ec.tasklist | 1 | ||||
-rw-r--r-- | common/build.mk | 2 | ||||
-rw-r--r-- | common/system_common.c | 5 | ||||
-rw-r--r-- | common/vboot_hash.c | 305 | ||||
-rw-r--r-- | include/ec_commands.h | 45 | ||||
-rw-r--r-- | util/ectool.c | 123 |
7 files changed, 478 insertions, 4 deletions
diff --git a/board/bds/ec.tasklist b/board/bds/ec.tasklist index beaaec5552..c8cf1637e0 100644 --- a/board/bds/ec.tasklist +++ b/board/bds/ec.tasklist @@ -15,5 +15,6 @@ */ #define CONFIG_TASK_LIST \ TASK(WATCHDOG, watchdog_task, NULL) \ + TASK(VBOOTHASH, vboot_hash_task, NULL) \ TASK(LIGHTBAR, lightbar_task, NULL) \ TASK(CONSOLE, console_task, NULL) diff --git a/board/link/ec.tasklist b/board/link/ec.tasklist index 2aee1d9be2..b31a575bf9 100644 --- a/board/link/ec.tasklist +++ b/board/link/ec.tasklist @@ -15,6 +15,7 @@ */ #define CONFIG_TASK_LIST \ TASK(WATCHDOG, watchdog_task, NULL) \ + TASK(VBOOTHASH, vboot_hash_task, NULL) \ TASK(LIGHTBAR, lightbar_task, NULL) \ TASK(POWERSTATE, charge_state_machine_task, NULL) \ TASK(TEMPSENSOR, temp_sensor_task, NULL) \ diff --git a/common/build.mk b/common/build.mk index 8c282b9d84..ab44f4dddd 100644 --- a/common/build.mk +++ b/common/build.mk @@ -39,7 +39,7 @@ VBOOT_DEVKEYS?=/usr/share/vboot/devkeys CFLAGS_$(CONFIG_VBOOT)+= -DCHROMEOS_ENVIRONMENT -DCHROMEOS_EC # CFLAGS_$(CONFIG_VBOOT)+= -DVBOOT_DEBUG -common-$(CONFIG_VBOOT)+= vboot.o vboot_stub.o +common-$(CONFIG_VBOOT)+= vboot.o vboot_stub.o vboot_hash.o includes-$(CONFIG_VBOOT)+= \ $(VBOOT_SOURCE)/include \ diff --git a/common/system_common.c b/common/system_common.c index 573995f1f8..4fb05422e7 100644 --- a/common/system_common.c +++ b/common/system_common.c @@ -521,7 +521,8 @@ DECLARE_CONSOLE_COMMAND(sysinfo, command_sysinfo, NULL); -#ifdef CONSOLE_COMMAND_SCRATCHPAD +#define CONFIG_CONSOLE_CMD_SCRATCHPAD +#ifdef CONFIG_CONSOLE_CMD_SCRATCHPAD static int command_scratchpad(int argc, char **argv) { int rv = EC_SUCCESS; @@ -541,7 +542,7 @@ DECLARE_CONSOLE_COMMAND(scratchpad, command_scratchpad, "[val]", "Get or set scratchpad value", NULL); -#endif +#endif /* CONFIG_CONSOLE_CMD_SCRATCHPAD */ static int command_hibernate(int argc, char **argv) diff --git a/common/vboot_hash.c b/common/vboot_hash.c new file mode 100644 index 0000000000..fc801a2e98 --- /dev/null +++ b/common/vboot_hash.c @@ -0,0 +1,305 @@ +/* Copyright (c) 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 "config.h" +#include "console.h" +#include "cryptolib.h" +#include "hooks.h" +#include "host_command.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 CPRINTF(format, args...) cprintf(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 + +static int data_offset; +static int data_size; +static int curr_pos; +static const uint8_t *hash; /* Hash, or NULL if not valid */ +static int want_abort; + +static SHA256_CTX ctx; + + +/* Return non-zero if a hash operation is in progress */ +static int vboot_hash_in_progress(void) +{ + if (hash) + return 0; /* Already done */ + return data_size ? 1 : 0; /* Nothing to hash */ +} + + +/* + * 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(int offset, int size, const uint8_t *nonce, + int nonce_size) +{ + /* Fail if hash computation is already in progress */ + if (vboot_hash_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) { + return EC_ERROR_INVAL; + } + + /* Save new hash request */ + data_offset = offset; + data_size = size; + curr_pos = 0; + hash = NULL; + want_abort = 0; + + /* Restart the hash computation */ + CPRINTF("[%T hash start 0x%08x 0x%08x]\n", offset, size); + SHA256_init(&ctx); + if (nonce_size) + SHA256_update(&ctx, nonce, nonce_size); + + /* Wake the hash task */ + task_wake(TASK_ID_VBOOTHASH); + + return EC_SUCCESS; +} + + +/* Abort hash currently in progress, if any. */ +static void vboot_hash_abort(void) +{ + if (vboot_hash_in_progress()) + want_abort = 1; +} + + +static void vboot_hash_init(void) +{ + 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 */ + CPRINTF("[%T hash precomputed]\n"); + hash = tag->hash; + data_offset = tag->offset; + data_size = tag->size; + } else { + /* Start computing the hash of firmware A */ + vboot_hash_start(CONFIG_FW_A_OFF - CONFIG_FLASH_BASE, + CONFIG_FW_A_SIZE, NULL, 0); + } +} + + +void vboot_hash_task(void) +{ + vboot_hash_init(); + + while (1) { + if (!vboot_hash_in_progress()) { + /* Nothing to do, so go back to sleep */ + task_wait_event(-1); + } else if (want_abort) { + /* Abort hash computation currently in progress */ + CPRINTF("[%T hash abort]\n"); + data_size = 0; + want_abort = 0; + } else { + /* Compute the next chunk of hash */ + int size = MIN(CHUNK_SIZE, data_size - curr_pos); + + SHA256_update(&ctx, + (const uint8_t *)(CONFIG_FLASH_BASE + + data_offset + curr_pos), + size); + curr_pos += size; + if (curr_pos >= data_size) { + int i; + + hash = SHA256_final(&ctx); + + CPRINTF("[%T hash done "); + for (i = 0; i < SHA256_DIGEST_SIZE; i++) + CPRINTF("%02x", hash[i]); + CPUTS("]\n"); + } + + /* + * Let other tasks (really, just the watchdog task) + * run for a bit. + */ + usleep(100); + } + } +} + +/*****************************************************************************/ +/* Hooks */ + +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); + +/****************************************************************************/ +/* Console commands */ + +static int command_hash(int argc, char **argv) +{ + int offset = CONFIG_FW_A_OFF - CONFIG_FLASH_BASE; + int size = CONFIG_FW_A_SIZE; + char *e; + + if (argc == 2 && !strcasecmp(argv[1], "abort")) { + vboot_hash_abort(); + return EC_SUCCESS; + } + + 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] | [<offset> <size> [<nonce>]]", + "Request hash recomputation", + NULL); + +/****************************************************************************/ +/* Host commands */ + +/* Fill in the response with the current hash status */ +static void fill_response(struct ec_response_vboot_hash *r) +{ + if (vboot_hash_in_progress()) + r->status = EC_VBOOT_HASH_STATUS_BUSY; + else if (hash) { + 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; +} + + +static int host_command_vboot_hash(uint8_t *data, int *resp_size) +{ + struct ec_params_vboot_hash *p = (struct ec_params_vboot_hash *)data; + struct ec_response_vboot_hash *r = + (struct ec_response_vboot_hash *)data; + int rv; + + switch (p->cmd) { + case EC_VBOOT_HASH_GET: + fill_response(r); + *resp_size = sizeof(struct ec_response_vboot_hash); + return EC_RES_SUCCESS; + + case EC_VBOOT_HASH_ABORT: + vboot_hash_abort(); + return EC_RES_SUCCESS; + + case EC_VBOOT_HASH_START: + 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; + + rv = vboot_hash_start(p->offset, p->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; + + case EC_VBOOT_HASH_RECALC: + 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; + + rv = vboot_hash_start(p->offset, p->size, p->nonce_data, + p->nonce_size); + if (rv == EC_ERROR_INVAL) + return EC_RES_INVALID_PARAM; + else if (rv != EC_SUCCESS) + return EC_RES_ERROR; + + /* Wait for hash to finish */ + while (vboot_hash_in_progress()) + usleep(1000); + + fill_response(r); + *resp_size = sizeof(struct ec_response_vboot_hash); + return EC_RES_SUCCESS; + + default: + return EC_RES_INVALID_PARAM; + } +} +DECLARE_HOST_COMMAND(EC_CMD_VBOOT_HASH, host_command_vboot_hash); diff --git a/include/ec_commands.h b/include/ec_commands.h index 3c8b8cb11c..570fc9fa5f 100644 --- a/include/ec_commands.h +++ b/include/ec_commands.h @@ -433,7 +433,9 @@ struct ec_params_lightbar_cmd { } __attribute__ ((packed)); /*****************************************************************************/ -/* Verified boot commands. Details still evolving. */ +/* Verified boot commands */ + +/* Verified boot command. Details still evolving. */ #define EC_CMD_VBOOT_CMD 0x29 struct ec_params_vboot_cmd { union { @@ -459,6 +461,47 @@ struct ec_params_vboot_cmd { }; } __attribute__ ((packed)); +/* Verified boot hash command */ +#define EC_CMD_VBOOT_HASH 0x2A + +struct ec_params_vboot_hash { + uint8_t cmd; /* enum ec_vboot_hash_cmd */ + uint8_t hash_type; /* enum ec_vboot_hash_type */ + uint8_t nonce_size; /* Nonce size; may be 0 */ + uint8_t reserved0; /* Reserved; set 0 */ + uint32_t offset; /* Offset in flash to hash */ + uint32_t size; /* Number of bytes to hash */ + uint8_t nonce_data[64]; /* Nonce data; ignored if nonce_size=0 */ +} __attribute__ ((packed)); + +struct ec_response_vboot_hash { + uint8_t status; /* enum ec_vboot_hash_status */ + uint8_t hash_type; /* enum ec_vboot_hash_type */ + uint8_t digest_size; /* Size of hash digest in bytes */ + uint8_t reserved0; /* Ignore; will be 0 */ + uint32_t offset; /* Offset in flash which was hashed */ + uint32_t size; /* Number of bytes hashed */ + uint8_t hash_digest[64]; /* Hash digest data */ +} __attribute__ ((packed)); + +enum ec_vboot_hash_cmd { + EC_VBOOT_HASH_GET, /* Get current hash status */ + EC_VBOOT_HASH_ABORT, /* Abort calculating current hash */ + EC_VBOOT_HASH_START, /* Start computing a new hash */ + EC_VBOOT_HASH_RECALC, /* Synchronously compute a new hash */ +}; + +enum ec_vboot_hash_type { + EC_VBOOT_HASH_TYPE_SHA256, /* SHA-256 */ +}; + +enum ec_vboot_hash_status { + EC_VBOOT_HASH_STATUS_NONE, /* No hash (not started, or aborted) */ + EC_VBOOT_HASH_STATUS_DONE, /* Finished computing a hash */ + EC_VBOOT_HASH_STATUS_BUSY, /* Busy computing a hash */ +}; + + /*****************************************************************************/ /* USB charging control commands */ diff --git a/util/ectool.c b/util/ectool.c index f608d952cb..4fb953b1fe 100644 --- a/util/ectool.c +++ b/util/ectool.c @@ -36,6 +36,8 @@ const char help_str[] = " Prints battery info\n" " chipinfo\n" " Prints chip info\n" + " echash [CMDS]\n" + " Various EC hash commands\n" " eventclear <mask>\n" " Clears EC host events flags where mask has bits set\n" " eventget\n" @@ -1565,6 +1567,126 @@ int cmd_chipinfo(int argc, char *argv[]) return 0; } + +static int ec_hash_help(const char *cmd) +{ + printf("Usage:\n"); + printf(" %s - get last hash\n", cmd); + printf(" %s abort - abort hashing\n", cmd); + printf(" %s start [<offset> <size> [<nonce>]] - start hashing\n", cmd); + printf(" %s recalc [<offset> <size> [<nonce>]] - sync rehash\n", cmd); + return 0; +} + + +static int ec_hash_print(const struct ec_response_vboot_hash *r) +{ + int i; + + if (r->status == EC_VBOOT_HASH_STATUS_BUSY) { + printf("status: busy\n"); + return 0; + } else if (r->status == EC_VBOOT_HASH_STATUS_NONE) { + printf("status: unavailable\n"); + return 0; + } else if (r->status != EC_VBOOT_HASH_STATUS_DONE) { + printf("status: %d\n", r->status); + return 0; + } + + printf("status: done\n"); + if (r->hash_type == EC_VBOOT_HASH_TYPE_SHA256) + printf("type: SHA-256\n"); + else + printf("type: %d\n", r->hash_type); + + printf("offset: 0x%08x\n", r->offset); + printf("size: 0x%08x\n", r->size); + + printf("hash: "); + for (i = 0; i < r->digest_size; i++) + printf("%02x", r->hash_digest[i]); + printf("\n"); + return 0; +} + + +int cmd_ec_hash(int argc, char *argv[]) +{ + struct ec_params_vboot_hash p; + struct ec_response_vboot_hash r; + char *e; + + if (argc < 2) { + /* Get hash status */ + p.cmd = EC_VBOOT_HASH_GET; + if (ec_command(EC_CMD_VBOOT_HASH, &p, sizeof(p), &r, sizeof(r))) + return -1; + + return ec_hash_print(&r); + } + + if (argc == 2 && !strcasecmp(argv[1], "abort")) { + /* Abort hash calculation */ + p.cmd = EC_VBOOT_HASH_ABORT; + if (ec_command(EC_CMD_VBOOT_HASH, &p, sizeof(p), &r, sizeof(r))) + return -1; + return 0; + } + + /* The only other commands are start and recalc */ + if (!strcasecmp(argv[1], "start")) + p.cmd = EC_VBOOT_HASH_START; + else if (!strcasecmp(argv[1], "recalc")) + p.cmd = EC_VBOOT_HASH_RECALC; + else + return ec_hash_help(argv[0]); + + if (argc < 4) { + fprintf(stderr, "Must specify offset and size\n"); + return -1; + } + + p.hash_type = EC_VBOOT_HASH_TYPE_SHA256; + p.offset = strtol(argv[2], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad offset.\n"); + return -1; + } + p.size = strtol(argv[3], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad size.\n"); + return -1; + } + + if (argc == 5) { + /* + * Technically nonce can be any binary data up to 64 bytes, + * but this command only supports a 32-bit value. + */ + uint32_t nonce = strtol(argv[4], &e, 0); + if (e && *e) { + fprintf(stderr, "Bad nonce integer.\n"); + return -1; + } + memcpy(p.nonce_data, &nonce, sizeof(nonce)); + p.nonce_size = sizeof(nonce); + } else + p.nonce_size = 0; + + printf("Hashing %d bytes at offset %d...\n", p.size, p.offset); + if (ec_command(EC_CMD_VBOOT_HASH, &p, sizeof(p), &r, sizeof(r))) + return -1; + + /* Start command doesn't wait for hashing to finish */ + if (p.cmd == EC_VBOOT_HASH_START) + return 0; + + /* Recalc command does wait around, so a result is ready now */ + return ec_hash_print(&r); +} + + struct command { const char *name; int (*handler)(int argc, char *argv[]); @@ -1576,6 +1698,7 @@ const struct command commands[] = { {"backlight", cmd_lcd_backlight}, {"battery", cmd_battery}, {"chipinfo", cmd_chipinfo}, + {"echash", cmd_ec_hash}, {"eventclear", cmd_host_event_clear}, {"eventget", cmd_host_event_get_raw}, {"eventgetscimask", cmd_host_event_get_sci_mask}, |