| /* Copyright 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 "clock.h" |
| #include "common.h" |
| #include "console.h" |
| #include "flash.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "sha256.h" |
| #include "shared_mem.h" |
| #include "stdbool.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 CPRINTS(format, args...) cprints(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 /* Bytes to hash per deferred call */ |
| #define WORK_INTERVAL_US 100 /* Delay between deferred calls */ |
| |
| /* Check that CHUNK_SIZE fits in shared memory. */ |
| SHARED_MEM_CHECK_SIZE(CHUNK_SIZE); |
| |
| static uint32_t data_offset; |
| static uint32_t data_size; |
| static uint32_t curr_pos; |
| static const uint8_t *hash; /* Hash, or NULL if not valid */ |
| static int want_abort; |
| static int in_progress; |
| #define VBOOT_HASH_DEFERRED true |
| #define VBOOT_HASH_BLOCKING false |
| |
| static struct sha256_ctx ctx; |
| |
| int vboot_hash_in_progress(void) |
| { |
| return in_progress; |
| } |
| |
| /** |
| * Abort hash currently in progress, and invalidate any completed hash. |
| */ |
| void vboot_hash_abort(void) |
| { |
| if (in_progress) { |
| want_abort = 1; |
| } else { |
| CPRINTS("hash abort"); |
| want_abort = 0; |
| data_size = 0; |
| hash = NULL; |
| #ifdef CONFIG_SHA256_HW_ACCELERATE |
| SHA256_abort(&ctx); |
| #endif |
| } |
| } |
| |
| static void vboot_hash_next_chunk(void); |
| DECLARE_DEFERRED(vboot_hash_next_chunk); |
| |
| #ifndef CONFIG_MAPPED_STORAGE |
| |
| static int read_and_hash_chunk(int offset, int size) |
| { |
| char *buf; |
| int rv; |
| |
| if (size == 0) |
| return EC_SUCCESS; |
| |
| rv = shared_mem_acquire(size, &buf); |
| if (rv == EC_ERROR_BUSY) { |
| /* Couldn't update hash right now; try again later */ |
| hook_call_deferred(&vboot_hash_next_chunk_data, |
| WORK_INTERVAL_US); |
| return rv; |
| } else if (rv != EC_SUCCESS) { |
| vboot_hash_abort(); |
| return rv; |
| } |
| |
| rv = flash_read(offset, size, buf); |
| if (rv == EC_SUCCESS) |
| SHA256_update(&ctx, (const uint8_t *)buf, size); |
| else |
| vboot_hash_abort(); |
| |
| shared_mem_release(buf); |
| return rv; |
| } |
| |
| #endif |
| |
| #ifdef CONFIG_CONSOLE_VERBOSE |
| #define SHA256_PRINT_SIZE SHA256_DIGEST_SIZE |
| #else |
| #define SHA256_PRINT_SIZE 4 |
| #endif |
| |
| static void hash_next_chunk(size_t size) |
| { |
| #ifdef CONFIG_MAPPED_STORAGE |
| flash_lock_mapped_storage(1); |
| SHA256_update(&ctx, (const uint8_t *)(CONFIG_MAPPED_STORAGE_BASE + |
| data_offset + curr_pos), size); |
| flash_lock_mapped_storage(0); |
| #else |
| if (read_and_hash_chunk(data_offset + curr_pos, size) != EC_SUCCESS) |
| return; |
| #endif |
| } |
| |
| static void vboot_hash_all_chunks(void) |
| { |
| do { |
| size_t size = MIN(CHUNK_SIZE, data_size - curr_pos); |
| hash_next_chunk(size); |
| curr_pos += size; |
| } while (curr_pos < data_size); |
| |
| hash = SHA256_final(&ctx); |
| CPRINTS("hash done %ph", HEX_BUF(hash, SHA256_PRINT_SIZE)); |
| in_progress = 0; |
| clock_enable_module(MODULE_FAST_CPU, 0); |
| |
| return; |
| } |
| |
| /** |
| * Do next chunk of hashing work, if any. |
| */ |
| static void vboot_hash_next_chunk(void) |
| { |
| int size; |
| |
| /* Handle abort */ |
| if (want_abort) { |
| in_progress = 0; |
| clock_enable_module(MODULE_FAST_CPU, 0); |
| vboot_hash_abort(); |
| return; |
| } |
| |
| /* Compute the next chunk of hash */ |
| size = MIN(CHUNK_SIZE, data_size - curr_pos); |
| hash_next_chunk(size); |
| |
| curr_pos += size; |
| if (curr_pos >= data_size) { |
| /* Store the final hash */ |
| hash = SHA256_final(&ctx); |
| CPRINTS("hash done %ph", HEX_BUF(hash, SHA256_PRINT_SIZE)); |
| |
| in_progress = 0; |
| |
| clock_enable_module(MODULE_FAST_CPU, 0); |
| |
| /* Handle receiving abort during finalize */ |
| if (want_abort) |
| vboot_hash_abort(); |
| |
| return; |
| } |
| |
| /* If we're still here, more work to do; come back later */ |
| hook_call_deferred(&vboot_hash_next_chunk_data, WORK_INTERVAL_US); |
| } |
| |
| /** |
| * |
| * If nonce_size is non-zero, prefixes the <nonce> onto the data to be hashed. |
| * Returns non-zero if error. |
| */ |
| /** |
| * Start computing a hash of <size> bytes of data at flash offset <offset>. |
| * |
| * @param offset start address of data on flash to compute hash for. |
| * @param size size of data to compute hash for. |
| * @param nonce nonce to differentiate hash. |
| * @param nonce_size size of nonce. |
| * @param deferred True to hash progressively through deferred calls. |
| * False to hash with a blocking single call. |
| * @return ec_error_list. |
| */ |
| static int vboot_hash_start(uint32_t offset, uint32_t size, |
| const uint8_t *nonce, int nonce_size, bool deferred) |
| { |
| /* Fail if hash computation is already in progress */ |
| if (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 || nonce_size < 0) { |
| return EC_ERROR_INVAL; |
| } |
| |
| clock_enable_module(MODULE_FAST_CPU, 1); |
| /* Save new hash request */ |
| data_offset = offset; |
| data_size = size; |
| curr_pos = 0; |
| hash = NULL; |
| want_abort = 0; |
| in_progress = 1; |
| |
| /* Restart the hash computation */ |
| CPRINTS("hash start 0x%08x 0x%08x", offset, size); |
| SHA256_init(&ctx); |
| if (nonce_size) |
| SHA256_update(&ctx, nonce, nonce_size); |
| |
| if (deferred) |
| hook_call_deferred(&vboot_hash_next_chunk_data, 0); |
| else |
| vboot_hash_all_chunks(); |
| |
| return EC_SUCCESS; |
| } |
| |
| int vboot_hash_invalidate(int offset, int size) |
| { |
| /* Don't invalidate if passed an invalid region */ |
| if (offset < 0 || size <= 0 || offset + size < 0) |
| return 0; |
| |
| /* Don't invalidate if hash is already invalid */ |
| if (!hash) |
| return 0; |
| |
| /* |
| * Always invalidate zero-size hash. No overlap if passed region is off |
| * either end of hashed region. |
| */ |
| if (data_size > 0 && |
| (offset + size <= data_offset || offset >= data_offset + data_size)) |
| return 0; |
| |
| /* Invalidate the hash */ |
| CPRINTS("hash invalidated 0x%08x 0x%08x", offset, size); |
| vboot_hash_abort(); |
| return 1; |
| } |
| |
| /*****************************************************************************/ |
| /* Hooks */ |
| |
| /** |
| * Returns the size of a RW copy to be hashed as expected by Softsync. |
| */ |
| static uint32_t get_rw_size(void) |
| { |
| #ifdef CONFIG_VBOOT_EFS /* Only needed for EFS, which signs and verifies |
| * entire RW, thus not needed for EFS2, which |
| * verifies only the used image size. */ |
| return CONFIG_RW_SIZE; |
| #else |
| return system_get_image_used(EC_IMAGE_RW); |
| #endif |
| } |
| |
| static void vboot_hash_init(void) |
| { |
| #ifdef CONFIG_SAVE_VBOOT_HASH |
| 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 */ |
| CPRINTS("hash precomputed"); |
| hash = tag->hash; |
| data_offset = tag->offset; |
| data_size = tag->size; |
| } else |
| #endif |
| #ifdef CONFIG_HOSTCMD_EVENTS |
| /* |
| * Don't auto-start hash computation if we've asked the host to enter |
| * recovery mode since we probably won't need the hash. Although |
| * the host is capable of clearing this host event, the host is |
| * likely not even up and running yet in the case of cold boot, due to |
| * the power sequencing task not having run yet. |
| */ |
| if (!(host_get_events() & |
| EC_HOST_EVENT_MASK(EC_HOST_EVENT_KEYBOARD_RECOVERY))) |
| #endif |
| { |
| /* Start computing the hash of RW firmware */ |
| vboot_hash_start(flash_get_rw_offset(system_get_active_copy()), |
| get_rw_size(), NULL, 0, VBOOT_HASH_DEFERRED); |
| } |
| } |
| DECLARE_HOOK(HOOK_INIT, vboot_hash_init, HOOK_PRIO_INIT_VBOOT_HASH); |
| |
| int vboot_get_rw_hash(const uint8_t **dst) |
| { |
| int rv = vboot_hash_start(flash_get_rw_offset(system_get_active_copy()), |
| get_rw_size(), NULL, 0, VBOOT_HASH_BLOCKING); |
| *dst = hash; |
| return rv; |
| } |
| |
| #ifdef CONFIG_SAVE_VBOOT_HASH |
| |
| 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); |
| |
| #endif |
| |
| /** |
| * Returns the offset of RO or RW image if the either region is specifically |
| * requested otherwise return the current hash offset. |
| */ |
| static int get_offset(int offset) |
| { |
| if (offset == EC_VBOOT_HASH_OFFSET_RO) |
| return CONFIG_EC_PROTECTED_STORAGE_OFF + CONFIG_RO_STORAGE_OFF; |
| if (offset == EC_VBOOT_HASH_OFFSET_ACTIVE) |
| return flash_get_rw_offset(system_get_active_copy()); |
| if (offset == EC_VBOOT_HASH_OFFSET_UPDATE) |
| return flash_get_rw_offset(system_get_update_copy()); |
| return offset; |
| } |
| |
| /****************************************************************************/ |
| /* Console commands */ |
| #ifdef CONFIG_CMD_HASH |
| static int command_hash(int argc, char **argv) |
| { |
| uint32_t offset = CONFIG_EC_WRITABLE_STORAGE_OFF + |
| CONFIG_RW_STORAGE_OFF; |
| uint32_t size = CONFIG_RW_SIZE; |
| char *e; |
| |
| if (argc == 1) { |
| ccprintf("Offset: 0x%08x\n", data_offset); |
| ccprintf("Size: 0x%08x (%d)\n", data_size, data_size); |
| ccprintf("Digest: "); |
| if (want_abort) |
| ccprintf("(aborting)\n"); |
| else if (in_progress) |
| ccprintf("(in progress)\n"); |
| else if (hash) |
| ccprintf("%ph\n", HEX_BUF(hash, SHA256_DIGEST_SIZE)); |
| else |
| ccprintf("(invalid)\n"); |
| |
| return EC_SUCCESS; |
| } |
| |
| if (argc == 2) { |
| if (!strcasecmp(argv[1], "abort")) { |
| vboot_hash_abort(); |
| return EC_SUCCESS; |
| } else if (!strcasecmp(argv[1], "rw")) { |
| return vboot_hash_start( |
| get_offset(EC_VBOOT_HASH_OFFSET_ACTIVE), |
| get_rw_size(), |
| NULL, 0, VBOOT_HASH_DEFERRED); |
| } else if (!strcasecmp(argv[1], "ro")) { |
| return vboot_hash_start( |
| CONFIG_EC_PROTECTED_STORAGE_OFF + |
| CONFIG_RO_STORAGE_OFF, |
| system_get_image_used(EC_IMAGE_RO), |
| NULL, 0, VBOOT_HASH_DEFERRED); |
| } |
| return EC_ERROR_PARAM2; |
| } |
| |
| 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), VBOOT_HASH_DEFERRED); |
| } else |
| return vboot_hash_start(offset, size, |
| NULL, 0, VBOOT_HASH_DEFERRED); |
| } |
| DECLARE_CONSOLE_COMMAND(hash, command_hash, |
| "[abort | ro | rw] | [<offset> <size> [<nonce>]]", |
| "Request hash recomputation"); |
| #endif /* CONFIG_CMD_HASH */ |
| /****************************************************************************/ |
| /* Host commands */ |
| |
| /* Fill in the response with the current hash status */ |
| static void fill_response(struct ec_response_vboot_hash *r, |
| int request_offset) |
| { |
| if (in_progress) |
| r->status = EC_VBOOT_HASH_STATUS_BUSY; |
| else if (get_offset(request_offset) == data_offset && hash && |
| !want_abort) { |
| 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; |
| } |
| |
| /** |
| * Start computing a hash, with validity checking on params. |
| * |
| * @return EC_RES_SUCCESS if success, or other result code on error. |
| */ |
| static int host_start_hash(const struct ec_params_vboot_hash *p) |
| { |
| int offset = p->offset; |
| int size = p->size; |
| int rv; |
| |
| /* Validity-check input params */ |
| 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; |
| |
| /* Handle special offset values */ |
| if (offset == EC_VBOOT_HASH_OFFSET_RO) |
| size = system_get_image_used(EC_IMAGE_RO); |
| else if ((offset == EC_VBOOT_HASH_OFFSET_ACTIVE) || |
| (offset == EC_VBOOT_HASH_OFFSET_UPDATE)) |
| size = get_rw_size(); |
| offset = get_offset(offset); |
| rv = vboot_hash_start(offset, size, p->nonce_data, p->nonce_size, |
| VBOOT_HASH_DEFERRED); |
| |
| if (rv == EC_SUCCESS) |
| return EC_RES_SUCCESS; |
| else if (rv == EC_ERROR_INVAL) |
| return EC_RES_INVALID_PARAM; |
| else |
| return EC_RES_ERROR; |
| } |
| |
| static enum ec_status |
| host_command_vboot_hash(struct host_cmd_handler_args *args) |
| { |
| const struct ec_params_vboot_hash *p = args->params; |
| struct ec_response_vboot_hash *r = args->response; |
| int rv; |
| |
| switch (p->cmd) { |
| case EC_VBOOT_HASH_GET: |
| if (p->offset || p->size) |
| fill_response(r, p->offset); |
| else |
| fill_response(r, data_offset); |
| |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| |
| case EC_VBOOT_HASH_ABORT: |
| vboot_hash_abort(); |
| return EC_RES_SUCCESS; |
| |
| case EC_VBOOT_HASH_START: |
| case EC_VBOOT_HASH_RECALC: |
| rv = host_start_hash(p); |
| if (rv != EC_RES_SUCCESS) |
| return rv; |
| |
| /* Wait for hash to finish if command is RECALC */ |
| if (p->cmd == EC_VBOOT_HASH_RECALC) |
| while (in_progress) |
| usleep(1000); |
| |
| fill_response(r, p->offset); |
| args->response_size = sizeof(*r); |
| return EC_RES_SUCCESS; |
| |
| default: |
| return EC_RES_INVALID_PARAM; |
| } |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_VBOOT_HASH, |
| host_command_vboot_hash, |
| EC_VER_MASK(0)); |