text: /* 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 "common.h"
  #include "console.h"
  #include "flash.h"
  #include "hooks.h"
  #include "host_command.h"
  #include "sha256.h"
  #include "shared_mem.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;
  
  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;
  	}
  }
  
  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
  
  /**
   * 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;
  		vboot_hash_abort();
  		return;
  	}
  
  	/* Compute the next chunk of hash */
  	size = MIN(CHUNK_SIZE, data_size - curr_pos);
  
  #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
  
  	curr_pos += size;
  	if (curr_pos >= data_size) {
  		/* Store the final hash */
  		hash = SHA256_final(&ctx);
  		CPRINTS("hash done %.*h", SHA256_PRINT_SIZE, hash);
  
  		in_progress = 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);
  }
  
  /**
   * 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(uint32_t offset, uint32_t size,
  			    const uint8_t *nonce, int nonce_size)
  {
  	/* 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;
  	}
  
  	/* 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);
  
  	hook_call_deferred(&vboot_hash_next_chunk_data, 0);
  
  	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
  	return CONFIG_RW_SIZE;
  #else
  	return system_get_image_used(SYSTEM_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);
  	}
  }
  DECLARE_HOOK(HOOK_INIT, vboot_hash_init, HOOK_PRIO_INIT_VBOOT_HASH);
  
  #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("%.*h\n", SHA256_DIGEST_SIZE, hash);
  		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);
  		} else if (!strcasecmp(argv[1], "ro")) {
  			return vboot_hash_start(
  				CONFIG_EC_PROTECTED_STORAGE_OFF +
  				CONFIG_RO_STORAGE_OFF,
  				system_get_image_used(SYSTEM_IMAGE_RO),
  				NULL, 0);
  		}
  		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));
  	} else
  		return vboot_hash_start(offset, size, NULL, 0);
  }
  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 sanity 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;
  
  	/* Sanity-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(SYSTEM_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);
  
  	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 int 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));
  