| /* 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 "common.h" |
| #include "console.h" |
| #include "consumer.h" |
| #include "curve25519.h" |
| #include "extension.h" |
| #include "flash.h" |
| #include "queue_policies.h" |
| #include "host_command.h" |
| #include "rollback.h" |
| #include "rwsig.h" |
| #include "sha256.h" |
| #include "system.h" |
| #include "update_fw.h" |
| #include "usb-stream.h" |
| #include "util.h" |
| |
| #define CPRINTS(format, args...) cprints(CC_USB, format, ## args) |
| |
| /* |
| * This file is an adaptation layer between the USB interface and the firmware |
| * update engine. The engine expects to receive long blocks of data, 1K or so |
| * in size, prepended by the offset where the data needs to be programmed into |
| * the flash and a 4 byte integrity check value. |
| * |
| * The USB transfer, on the other hand, operates on much shorter chunks of |
| * data, typically 64 bytes in this case. This module reassembles firmware |
| * programming blocks from the USB chunks, and invokes the programmer passing |
| * it the full block. |
| * |
| * The programmer reports results by putting the return value into the same |
| * buffer where the block was passed in. This wrapper retrieves the |
| * programmer's return value, and sends it back to the host. The return value |
| * is usually one byte in size, the only exception is the connection |
| * establishment phase where the return value is 16 bytes in size. |
| * |
| * In the end of the successful image transfer and programming, the host sends |
| * the reset command, and the device reboots itself. |
| */ |
| |
| struct consumer const update_consumer; |
| struct usb_stream_config const usb_update; |
| |
| static struct queue const update_to_usb = QUEUE_DIRECT(64, uint8_t, |
| null_producer, |
| usb_update.consumer); |
| static struct queue const usb_to_update = QUEUE_DIRECT(64, uint8_t, |
| usb_update.producer, |
| update_consumer); |
| |
| USB_STREAM_CONFIG_FULL(usb_update, |
| USB_IFACE_UPDATE, |
| USB_CLASS_VENDOR_SPEC, |
| USB_SUBCLASS_GOOGLE_UPDATE, |
| USB_PROTOCOL_GOOGLE_UPDATE, |
| USB_STR_UPDATE_NAME, |
| USB_EP_UPDATE, |
| USB_MAX_PACKET_SIZE, |
| USB_MAX_PACKET_SIZE, |
| usb_to_update, |
| update_to_usb) |
| |
| |
| /* The receiver can be in one of the states below. */ |
| enum rx_state { |
| rx_idle, /* Nothing happened yet. */ |
| rx_inside_block, /* Assembling a block to pass to the programmer. */ |
| rx_outside_block, /* Waiting for the next block to start or for the |
| reset command. */ |
| }; |
| |
| enum rx_state rx_state_ = rx_idle; |
| static uint8_t block_buffer[sizeof(struct update_command) + |
| CONFIG_UPDATE_PDU_SIZE]; |
| static uint32_t block_size; |
| static uint32_t block_index; |
| |
| #ifdef CONFIG_USB_PAIRING |
| #define KEY_CONTEXT "device-identity" |
| |
| static int pair_challenge(struct pair_challenge *challenge) |
| { |
| uint8_t response; |
| |
| /* Scratchpad for device secret and x25519 public/shared key. */ |
| uint8_t tmp[32]; |
| BUILD_ASSERT(sizeof(tmp) >= X25519_PUBLIC_VALUE_LEN); |
| BUILD_ASSERT(sizeof(tmp) >= X25519_PRIVATE_KEY_LEN); |
| BUILD_ASSERT(sizeof(tmp) >= CONFIG_ROLLBACK_SECRET_SIZE); |
| |
| /* Scratchpad for device_private and authenticator. */ |
| uint8_t tmp2[32]; |
| BUILD_ASSERT(sizeof(tmp2) >= X25519_PRIVATE_KEY_LEN); |
| BUILD_ASSERT(sizeof(tmp2) >= SHA256_DIGEST_SIZE); |
| |
| /* tmp = device_secret */ |
| if (rollback_get_secret(tmp) != EC_SUCCESS) { |
| response = EC_RES_UNAVAILABLE; |
| QUEUE_ADD_UNITS(&update_to_usb, &response, sizeof(response)); |
| return 1; |
| } |
| |
| /* |
| * Nothing can fail from now on, let's push data to the queue as soon as |
| * possible to save some temporary variables. |
| */ |
| response = EC_RES_SUCCESS; |
| QUEUE_ADD_UNITS(&update_to_usb, &response, sizeof(response)); |
| |
| /* |
| * tmp2 = device_private |
| * = HMAC_SHA256(device_secret, "device-identity") |
| */ |
| hmac_SHA256(tmp2, tmp, CONFIG_ROLLBACK_SECRET_SIZE, |
| KEY_CONTEXT, sizeof(KEY_CONTEXT) - 1); |
| |
| /* tmp = device_public = x25519(device_private, x25519_base_point) */ |
| X25519_public_from_private(tmp, tmp2); |
| QUEUE_ADD_UNITS(&update_to_usb, tmp, sizeof(tmp)); |
| |
| /* tmp = shared_secret = x25519(device_private, host_public) */ |
| X25519(tmp, tmp2, challenge->host_public); |
| |
| /* tmp2 = authenticator = HMAC_SHA256(shared_secret, nonce) */ |
| hmac_SHA256(tmp2, tmp, sizeof(tmp), |
| challenge->nonce, sizeof(challenge->nonce)); |
| QUEUE_ADD_UNITS(&update_to_usb, tmp2, |
| member_size(struct pair_challenge_response, authenticator)); |
| return 1; |
| } |
| #endif |
| |
| /* |
| * Fetches a transfer start frame from the queue. This can be either an update |
| * start frame (block_size = 0, all of cmd = 0), or the beginning of a frame |
| * (block_size > 0, valid block_base in cmd). |
| */ |
| static int fetch_transfer_start(struct consumer const *consumer, size_t count, |
| struct update_frame_header *pupfr) |
| { |
| int i; |
| |
| /* |
| * Let's just make sure we drain the queue no matter what the contents |
| * are. This way they won't be in the way during next callback, even |
| * if these contents are not what's expected. |
| * |
| * Note: If count > sizeof(*pupfr), pupfr will be corrupted. This is |
| * ok as we will immediately fail after this. |
| */ |
| i = count; |
| while (i > 0) { |
| QUEUE_REMOVE_UNITS(consumer->queue, pupfr, |
| MIN(i, sizeof(*pupfr))); |
| i -= sizeof(*pupfr); |
| } |
| |
| if (count != sizeof(struct update_frame_header)) { |
| CPRINTS("FW update: wrong first block, size %d", count); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int try_vendor_command(struct consumer const *consumer, size_t count) |
| { |
| char buffer[USB_MAX_PACKET_SIZE]; |
| struct update_frame_header *cmd_buffer = (void *)buffer; |
| int rv = 0; |
| |
| /* Validate count (too short, or too long). */ |
| if (count < sizeof(*cmd_buffer) || count > sizeof(buffer)) |
| return 0; |
| |
| /* |
| * Let's copy off the queue the update frame header, to see if this |
| * is a channeled vendor command. |
| */ |
| queue_peek_units(consumer->queue, cmd_buffer, 0, sizeof(*cmd_buffer)); |
| if (be32toh(cmd_buffer->cmd.block_base) != UPDATE_EXTRA_CMD) |
| return 0; |
| |
| if (be32toh(cmd_buffer->block_size) != count) { |
| CPRINTS("%s: problem: block size and count mismatch (%d != %d)", |
| __func__, be32toh(cmd_buffer->block_size), count); |
| return 0; |
| } |
| |
| /* Get the entire command, don't remove it from the queue just yet. */ |
| queue_peek_units(consumer->queue, cmd_buffer, 0, count); |
| |
| /* Looks like this is a vendor command, let's verify it. */ |
| if (update_pdu_valid(&cmd_buffer->cmd, |
| count - offsetof(struct update_frame_header, cmd))) { |
| enum update_extra_command subcommand; |
| uint8_t response; |
| size_t response_size = sizeof(response); |
| int __attribute__((unused)) header_size; |
| int __attribute__((unused)) data_count; |
| |
| /* looks good, let's process it. */ |
| rv = 1; |
| |
| /* Now remove it from the queue. */ |
| queue_advance_head(consumer->queue, count); |
| |
| subcommand = be16toh(*((uint16_t *)(cmd_buffer + 1))); |
| |
| /* |
| * header size: update frame header + 2 bytes for subcommand |
| * data_count: Some commands take in extra data as parameter |
| */ |
| header_size = sizeof(*cmd_buffer) + sizeof(uint16_t); |
| data_count = count - header_size; |
| |
| switch (subcommand) { |
| case UPDATE_EXTRA_CMD_IMMEDIATE_RESET: |
| CPRINTS("Rebooting!\n\n\n"); |
| cflush(); |
| system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED); |
| /* Unreachable, unless something bad happens. */ |
| response = EC_RES_ERROR; |
| break; |
| case UPDATE_EXTRA_CMD_JUMP_TO_RW: |
| #ifdef CONFIG_RWSIG |
| /* |
| * Tell rwsig task to jump to RW. This does nothing if |
| * verification failed, and will only jump later on if |
| * verification is still in progress. |
| */ |
| rwsig_continue(); |
| |
| switch (rwsig_get_status()) { |
| case RWSIG_VALID: |
| response = EC_RES_SUCCESS; |
| break; |
| case RWSIG_INVALID: |
| response = EC_RES_INVALID_CHECKSUM; |
| break; |
| case RWSIG_IN_PROGRESS: |
| response = EC_RES_IN_PROGRESS; |
| break; |
| default: |
| response = EC_RES_ERROR; |
| } |
| #else |
| system_run_image_copy(SYSTEM_IMAGE_RW); |
| #endif |
| break; |
| #ifdef CONFIG_RWSIG |
| case UPDATE_EXTRA_CMD_STAY_IN_RO: |
| rwsig_abort(); |
| response = EC_RES_SUCCESS; |
| break; |
| #endif |
| case UPDATE_EXTRA_CMD_UNLOCK_RW: |
| flash_set_protect(EC_FLASH_PROTECT_RW_AT_BOOT, 0); |
| response = EC_RES_SUCCESS; |
| break; |
| #ifdef CONFIG_ROLLBACK |
| case UPDATE_EXTRA_CMD_UNLOCK_ROLLBACK: |
| flash_set_protect(EC_FLASH_PROTECT_ROLLBACK_AT_BOOT, 0); |
| response = EC_RES_SUCCESS; |
| break; |
| #ifdef CONFIG_ROLLBACK_SECRET_SIZE |
| #ifdef CONFIG_ROLLBACK_UPDATE |
| case UPDATE_EXTRA_CMD_INJECT_ENTROPY: { |
| if (data_count < CONFIG_ROLLBACK_SECRET_SIZE) { |
| CPRINTS("Entropy too short"); |
| response = EC_RES_INVALID_PARAM; |
| break; |
| } |
| |
| CPRINTS("Adding %db of entropy", data_count); |
| /* Add the entropy to secret. */ |
| rollback_add_entropy(buffer + header_size, data_count); |
| break; |
| } |
| #endif /* CONFIG_ROLLBACK_UPDATE */ |
| #ifdef CONFIG_USB_PAIRING |
| case UPDATE_EXTRA_CMD_PAIR_CHALLENGE: { |
| if (data_count < sizeof(struct pair_challenge)) { |
| CPRINTS("Challenge data too short"); |
| response = EC_RES_INVALID_PARAM; |
| break; |
| } |
| |
| /* pair_challenge takes care of answering */ |
| return pair_challenge((struct pair_challenge *) |
| (buffer + header_size)); |
| } |
| #endif |
| #endif /* CONFIG_ROLLBACK_SECRET_SIZE */ |
| #endif /* CONFIG_ROLLBACK */ |
| #ifdef CONFIG_TOUCHPAD |
| case UPDATE_EXTRA_CMD_TOUCHPAD_INFO: { |
| struct touchpad_info tp = { 0 }; |
| |
| if (data_count != 0) { |
| response = EC_RES_INVALID_PARAM; |
| break; |
| } |
| |
| response_size = touchpad_get_info(&tp); |
| if (response_size < 1) { |
| response = EC_RES_ERROR; |
| break; |
| } |
| |
| #ifdef CONFIG_TOUCHPAD_VIRTUAL_OFF |
| tp.fw_address = CONFIG_TOUCHPAD_VIRTUAL_OFF; |
| tp.fw_size = CONFIG_TOUCHPAD_VIRTUAL_SIZE; |
| |
| #ifdef CONFIG_TOUCHPAD_HASH_FW |
| memcpy(tp.allowed_fw_hash, touchpad_fw_full_hash, |
| sizeof(tp.allowed_fw_hash)); |
| #endif |
| #endif /* CONFIG_TOUCHPAD_VIRTUAL_OFF */ |
| QUEUE_ADD_UNITS(&update_to_usb, |
| &tp, response_size); |
| return 1; |
| } |
| case UPDATE_EXTRA_CMD_TOUCHPAD_DEBUG: { |
| uint8_t *data = NULL; |
| unsigned int write_count = 0; |
| |
| /* |
| * Let the touchpad driver decide what it wants to do |
| * with the payload data, and put the response in data. |
| */ |
| response = touchpad_debug(buffer + header_size, |
| data_count, &data, &write_count); |
| |
| /* |
| * On error, or if there is no data to write back, just |
| * write back response. |
| */ |
| if (response != EC_RES_SUCCESS || write_count == 0) |
| break; |
| |
| /* Check that we can write all the data to the queue. */ |
| if (write_count > queue_space(&update_to_usb)) |
| return EC_RES_BUSY; |
| |
| QUEUE_ADD_UNITS(&update_to_usb, data, write_count); |
| return 1; |
| } |
| #endif |
| default: |
| response = EC_RES_INVALID_COMMAND; |
| } |
| |
| QUEUE_ADD_UNITS(&update_to_usb, &response, response_size); |
| } |
| |
| return rv; |
| } |
| |
| /* |
| * When was last time a USB callback was called, in microseconds, free running |
| * timer. |
| */ |
| static uint64_t prev_activity_timestamp; |
| |
| /* |
| * A flag indicating that at least one valid PDU containing flash update block |
| * has been received in the current transfer session. |
| */ |
| static uint8_t data_was_transferred; |
| |
| /* Reply with an error to remote side, reset state. */ |
| static void send_error_reset(uint8_t resp_value) |
| { |
| QUEUE_ADD_UNITS(&update_to_usb, &resp_value, 1); |
| rx_state_ = rx_idle; |
| data_was_transferred = 0; |
| } |
| |
| /* Called to deal with data from the host */ |
| static void update_out_handler(struct consumer const *consumer, size_t count) |
| { |
| struct update_frame_header upfr; |
| size_t resp_size; |
| uint8_t resp_value; |
| uint64_t delta_time; |
| |
| /* How much time since the previous USB callback? */ |
| delta_time = get_time().val - prev_activity_timestamp; |
| prev_activity_timestamp += delta_time; |
| |
| /* If timeout exceeds 5 seconds - let's start over. */ |
| if ((delta_time > 5000000) && (rx_state_ != rx_idle)) { |
| rx_state_ = rx_idle; |
| CPRINTS("FW update: recovering after timeout"); |
| } |
| |
| if (rx_state_ == rx_idle) { |
| /* |
| * The payload must be an update initiating PDU. |
| * |
| * The size of the response returned in the same buffer will |
| * exceed the received frame size; Let's make sure there is |
| * enough room for the response in the buffer. |
| */ |
| union { |
| struct update_frame_header upfr; |
| struct { |
| uint32_t unused; |
| struct first_response_pdu startup_resp; |
| }; |
| } u; |
| |
| /* Check is this is a channeled TPM extension command. */ |
| if (try_vendor_command(consumer, count)) |
| return; |
| |
| /* |
| * An update start PDU is a command without any payload, with |
| * digest = 0, and base = 0. |
| */ |
| if (!fetch_transfer_start(consumer, count, &u.upfr) || |
| be32toh(u.upfr.block_size) != |
| sizeof(struct update_frame_header) || |
| u.upfr.cmd.block_digest != 0 || |
| u.upfr.cmd.block_base != 0) { |
| /* |
| * Something is wrong, this payload is not a valid |
| * update start PDU. Let'w indicate this by returning |
| * a single byte error code. |
| */ |
| CPRINTS("FW update: invalid start."); |
| send_error_reset(UPDATE_GEN_ERROR); |
| return; |
| } |
| |
| CPRINTS("FW update: starting..."); |
| fw_update_command_handler(&u.upfr.cmd, count - |
| offsetof(struct update_frame_header, |
| cmd), |
| &resp_size); |
| |
| if (!u.startup_resp.return_value) { |
| rx_state_ = rx_outside_block; /* We're in business. */ |
| data_was_transferred = 0; /* No data received yet. */ |
| } |
| |
| /* Let the host know what updater had to say. */ |
| QUEUE_ADD_UNITS(&update_to_usb, &u.startup_resp, resp_size); |
| return; |
| } |
| |
| if (rx_state_ == rx_outside_block) { |
| /* |
| * Expecting to receive the beginning of the block or the |
| * reset command if all data blocks have been processed. |
| */ |
| if (count == 4) { |
| uint32_t command; |
| |
| QUEUE_REMOVE_UNITS(consumer->queue, &command, |
| sizeof(command)); |
| command = be32toh(command); |
| if (command == UPDATE_DONE) { |
| CPRINTS("FW update: done"); |
| |
| if (data_was_transferred) { |
| fw_update_complete(); |
| data_was_transferred = 0; |
| } |
| |
| resp_value = 0; |
| QUEUE_ADD_UNITS(&update_to_usb, |
| &resp_value, 1); |
| rx_state_ = rx_idle; |
| return; |
| } |
| } |
| |
| /* |
| * At this point we expect a block start message. It is |
| * sizeof(upfr) bytes in size. |
| */ |
| if (!fetch_transfer_start(consumer, count, &upfr)) { |
| CPRINTS("Invalid block start."); |
| send_error_reset(UPDATE_GEN_ERROR); |
| return; |
| } |
| |
| /* Let's allocate a large enough buffer. */ |
| block_size = be32toh(upfr.block_size) - |
| offsetof(struct update_frame_header, cmd); |
| |
| /* |
| * Only update start PDU is allowed to have a size 0 payload. |
| */ |
| if (block_size <= sizeof(struct update_command) || |
| block_size > sizeof(block_buffer)) { |
| CPRINTS("Invalid block size (%d).", block_size); |
| send_error_reset(UPDATE_GEN_ERROR); |
| return; |
| } |
| |
| /* |
| * Copy the rest of the message into the block buffer to pass |
| * to the updater. |
| */ |
| block_index = sizeof(upfr) - |
| offsetof(struct update_frame_header, cmd); |
| memcpy(block_buffer, &upfr.cmd, block_index); |
| block_size -= block_index; |
| rx_state_ = rx_inside_block; |
| return; |
| } |
| |
| /* Must be inside block. */ |
| QUEUE_REMOVE_UNITS(consumer->queue, block_buffer + block_index, count); |
| block_index += count; |
| block_size -= count; |
| |
| if (block_size) { |
| if (count <= sizeof(upfr)) { |
| /* |
| * A block header size instead of chunk size message |
| * has been received, let's abort the transfer. |
| */ |
| CPRINTS("Unexpected header"); |
| send_error_reset(UPDATE_GEN_ERROR); |
| return; |
| } |
| return; /* More to come. */ |
| } |
| |
| /* |
| * Ok, the entire block has been received and reassembled, pass it to |
| * the updater for verification and programming. |
| */ |
| fw_update_command_handler(block_buffer, block_index, &resp_size); |
| |
| /* |
| * There was at least an attempt to program the flash, set the |
| * flag. |
| */ |
| data_was_transferred = 1; |
| resp_value = block_buffer[0]; |
| QUEUE_ADD_UNITS(&update_to_usb, &resp_value, sizeof(resp_value)); |
| rx_state_ = rx_outside_block; |
| } |
| |
| struct consumer const update_consumer = { |
| .queue = &usb_to_update, |
| .ops = &((struct consumer_ops const) { |
| .written = update_out_handler, |
| }), |
| }; |