summaryrefslogtreecommitdiff
path: root/futility/updater.c
diff options
context:
space:
mode:
Diffstat (limited to 'futility/updater.c')
-rw-r--r--futility/updater.c1981
1 files changed, 1981 insertions, 0 deletions
diff --git a/futility/updater.c b/futility/updater.c
new file mode 100644
index 00000000..8922e4ac
--- /dev/null
+++ b/futility/updater.c
@@ -0,0 +1,1981 @@
+/*
+ * Copyright 2018 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.
+ *
+ * A reference implementation for AP (and supporting images) firmware updater.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "2rsa.h"
+#include "crossystem.h"
+#include "futility.h"
+#include "host_misc.h"
+#include "updater.h"
+#include "utility.h"
+#include "util_misc.h"
+#include "vb2_common.h"
+#include "vb2_struct.h"
+
+#define COMMAND_BUFFER_SIZE 256
+#define RETURN_ON_FAILURE(x) do {int r = (x); if (r) return r;} while (0);
+#define FLASHROM_OUTPUT_WP_PATTERN "write protect is "
+
+/* System environment values. */
+static const char * const FWACT_A = "A",
+ * const FWACT_B = "B",
+ * const STR_REV = "rev",
+ * const FLASHROM_OUTPUT_WP_ENABLED =
+ FLASHROM_OUTPUT_WP_PATTERN "enabled",
+ * const FLASHROM_OUTPUT_WP_DISABLED =
+ FLASHROM_OUTPUT_WP_PATTERN "disabled";
+
+/* flashrom programmers. */
+static const char * const PROG_HOST = "host",
+ * const PROG_EC = "ec",
+ * const PROG_PD = "ec:dev=1";
+
+enum wp_state {
+ WP_DISABLED,
+ WP_ENABLED,
+};
+
+enum target_type {
+ TARGET_SELF,
+ TARGET_UPDATE,
+};
+
+enum active_slot {
+ SLOT_UNKNOWN = -1,
+ SLOT_A = 0,
+ SLOT_B,
+};
+
+enum flashrom_ops {
+ FLASHROM_READ,
+ FLASHROM_WRITE,
+ FLASHROM_WP_STATUS,
+};
+
+
+/*
+ * Helper function to create a new temporary file.
+ * All files created will be removed by updater_remove_all_temp_files().
+ * Returns the path of new file, or NULL on failure.
+ */
+const char *updater_create_temp_file(struct updater_config *cfg)
+{
+ struct tempfile *new_temp;
+ char new_path[] = P_tmpdir "/fwupdater.XXXXXX";
+ int fd;
+
+ fd = mkstemp(new_path);
+ if (fd < 0) {
+ ERROR("Failed to create new temp file in %s", new_path);
+ return NULL;
+ }
+ close(fd);
+ new_temp = (struct tempfile *)malloc(sizeof(*new_temp));
+ if (new_temp)
+ new_temp->filepath = strdup(new_path);
+ if (!new_temp || !new_temp->filepath) {
+ remove(new_path);
+ free(new_temp);
+ ERROR("Failed to allocate buffer for new temp file.");
+ return NULL;
+ }
+ DEBUG("Created new temporary file: %s.", new_path);
+ new_temp->next = cfg->tempfiles;
+ cfg->tempfiles = new_temp;
+ return new_temp->filepath;
+}
+
+/*
+ * Helper function to remove all files created by create_temp_file().
+ * This is intended to be called only once at end of program execution.
+ */
+static void updater_remove_all_temp_files(struct updater_config *cfg)
+{
+ struct tempfile *tempfiles = cfg->tempfiles;
+ while (tempfiles != NULL) {
+ struct tempfile *target = tempfiles;
+ DEBUG("Remove temporary file: %s.", target->filepath);
+ remove(target->filepath);
+ free(target->filepath);
+ tempfiles = target->next;
+ free(target);
+ }
+ cfg->tempfiles = NULL;
+}
+
+/*
+ * Strip a string (usually from shell execution output) by removing all the
+ * trailing space characters (space, new line, tab, ... etc).
+ */
+static void strip(char *s)
+{
+ int len;
+ assert(s);
+
+ len = strlen(s);
+ while (len-- > 0) {
+ if (!isascii(s[len]) || !isspace(s[len]))
+ break;
+ s[len] = '\0';
+ }
+}
+
+/*
+ * Executes a command on current host and returns stripped command output.
+ * If the command has failed (exit code is not zero), returns an empty string.
+ * The caller is responsible for releasing the returned string.
+ */
+char *host_shell(const char *command)
+{
+ /* Currently all commands we use do not have large output. */
+ char buf[COMMAND_BUFFER_SIZE];
+
+ int result;
+ FILE *fp = popen(command, "r");
+
+ DEBUG("%s", command);
+ buf[0] = '\0';
+ if (!fp) {
+ DEBUG("Execution error for %s.", command);
+ return strdup(buf);
+ }
+
+ if (fgets(buf, sizeof(buf), fp))
+ strip(buf);
+ result = pclose(fp);
+ if (!WIFEXITED(result) || WEXITSTATUS(result) != 0) {
+ DEBUG("Execution failure with exit code %d: %s",
+ WEXITSTATUS(result), command);
+ /*
+ * Discard all output if command failed, for example command
+ * syntax failure may lead to garbage in stdout.
+ */
+ buf[0] = '\0';
+ }
+ return strdup(buf);
+}
+
+
+/* An helper function to return "mainfw_act" system property. */
+static int host_get_mainfw_act()
+{
+ char buf[VB_MAX_STRING_PROPERTY];
+
+ if (!VbGetSystemPropertyString("mainfw_act", buf, sizeof(buf)))
+ return SLOT_UNKNOWN;
+
+ if (strcmp(buf, FWACT_A) == 0)
+ return SLOT_A;
+ else if (strcmp(buf, FWACT_B) == 0)
+ return SLOT_B;
+
+ return SLOT_UNKNOWN;
+}
+
+/* A helper function to return the "tpm_fwver" system property. */
+static int host_get_tpm_fwver()
+{
+ return VbGetSystemPropertyInt("tpm_fwver");
+}
+
+/* A helper function to return the "hardware write protection" status. */
+static int host_get_wp_hw()
+{
+ /* wpsw refers to write protection 'switch', not 'software'. */
+ int v = VbGetSystemPropertyInt("wpsw_cur");
+
+ /* wpsw_cur may be not available, especially in recovery mode. */
+ if (v < 0)
+ v = VbGetSystemPropertyInt("wpsw_boot");
+
+ return v;
+}
+
+/* A helper function to return "fw_vboot2" system property. */
+static int host_get_fw_vboot2()
+{
+ return VbGetSystemPropertyInt("fw_vboot2");
+}
+
+/* A help function to get $(mosys platform version). */
+static int host_get_platform_version()
+{
+ char *result = host_shell("mosys platform version");
+ int rev = -1;
+
+ /* Result should be 'revN' */
+ if (strncmp(result, STR_REV, strlen(STR_REV)) == 0)
+ rev = strtol(result + strlen(STR_REV), NULL, 0);
+ DEBUG("Raw data = [%s], parsed version is %d", result, rev);
+
+ free(result);
+ return rev;
+}
+
+/*
+ * A helper function to invoke flashrom(8) command.
+ * Returns 0 if success, non-zero if error.
+ */
+static int host_flashrom(enum flashrom_ops op, const char *image_path,
+ const char *programmer, int verbose,
+ const char *section_name)
+{
+ char *command, *result;
+ const char *op_cmd, *dash_i = "-i", *postfix = "", *ignore_lock = "";
+ int r;
+
+ switch (verbose) {
+ case 0:
+ postfix = " >/dev/null 2>&1";
+ break;
+ case 1:
+ break;
+ case 2:
+ postfix = "-V";
+ break;
+ case 3:
+ postfix = "-V -V";
+ break;
+ default:
+ postfix = "-V -V -V";
+ break;
+ }
+
+ if (!section_name || !*section_name) {
+ dash_i = "";
+ section_name = "";
+ }
+
+ switch (op) {
+ case FLASHROM_READ:
+ op_cmd = "-r";
+ assert(image_path);
+ break;
+
+ case FLASHROM_WRITE:
+ op_cmd = "-w";
+ assert(image_path);
+ break;
+
+ case FLASHROM_WP_STATUS:
+ op_cmd = "--wp-status";
+ assert(image_path == NULL);
+ image_path = "";
+ /* grep is needed because host_shell only returns 1 line. */
+ postfix = " 2>/dev/null | grep \"" \
+ FLASHROM_OUTPUT_WP_PATTERN "\"";
+ break;
+
+ default:
+ assert(0);
+ return -1;
+ }
+
+ /* TODO(hungte) In future we should link with flashrom directly. */
+ ASPRINTF(&command, "flashrom %s %s -p %s %s %s %s %s", op_cmd,
+ image_path, programmer, dash_i, section_name, ignore_lock,
+ postfix);
+
+ if (verbose)
+ printf("Executing: %s\n", command);
+
+ if (op != FLASHROM_WP_STATUS) {
+ r = system(command);
+ free(command);
+ return r;
+ }
+
+ result = host_shell(command);
+ strip(result);
+ free(command);
+ DEBUG("wp-status: %s", result);
+
+ if (strstr(result, FLASHROM_OUTPUT_WP_ENABLED))
+ r = WP_ENABLED;
+ else if (strstr(result, FLASHROM_OUTPUT_WP_DISABLED))
+ r = WP_DISABLED;
+ else
+ r = -1;
+ free(result);
+ return r;
+}
+
+/* Helper function to return software write protection switch status. */
+static int host_get_wp_sw()
+{
+ return host_flashrom(FLASHROM_WP_STATUS, NULL, PROG_HOST, 0, NULL);
+}
+
+/*
+ * Gets the system property by given type.
+ * If the property was not loaded yet, invoke the property getter function
+ * and cache the result.
+ * Returns the property value.
+ */
+int get_system_property(enum system_property_type property_type,
+ struct updater_config *cfg)
+{
+ struct system_property *prop;
+
+ assert(property_type < SYS_PROP_MAX);
+ prop = &cfg->system_properties[property_type];
+ if (!prop->initialized) {
+ prop->initialized = 1;
+ prop->value = prop->getter();
+ }
+ return prop->value;
+}
+
+static void print_system_properties(struct updater_config *cfg)
+{
+ int i;
+
+ /*
+ * There may be error messages when fetching properties from active
+ * system, so we want to peek at them first and then print out.
+ */
+ DEBUG("Scanning system properties...");
+ for (i = 0; i < SYS_PROP_MAX; i++) {
+ get_system_property((enum system_property_type)i, cfg);
+ }
+
+ printf("System properties: [");
+ for (i = 0; i < SYS_PROP_MAX; i++) {
+ printf("%d,",
+ get_system_property((enum system_property_type)i, cfg));
+ }
+ printf("]\n");
+}
+
+/*
+ * Overrides the return value of a system property.
+ * After invoked, next call to get_system_property(type, cfg) will return
+ * the given value.
+ */
+static void override_system_property(enum system_property_type property_type,
+ struct updater_config *cfg,
+ int value)
+{
+ struct system_property *prop;
+
+ assert(property_type < SYS_PROP_MAX);
+ prop = &cfg->system_properties[property_type];
+ prop->initialized = 1;
+ prop->value = value;
+}
+
+/*
+ * Overrides system properties from a given list.
+ * The list should be string of integers eliminated by comma and/or space.
+ * For example, "1 2 3" and "1,2,3" both overrides first 3 properties.
+ * To skip some properties you have to use comma, for example
+ * "1, , 3" will only override the first and 3rd properties.
+ * Invalid characters and fields will be ignored.
+ *
+ * The current implementation is only for unit testing.
+ * In future we may extend this with name=value so users can use it easily on
+ * actual systems.
+ */
+static void override_properties_from_list(const char *override_list,
+ struct updater_config *cfg)
+{
+ const char *s = override_list;
+ char *e, c;
+ int i = 0, wait_comma = 0;
+ long int v;
+
+ DEBUG("Input is <%s>", override_list);
+ for (c = *s; c; c = *++s) {
+ if (c == ',') {
+ if (!wait_comma)
+ i++;
+ wait_comma = 0;
+ }
+ if (!isascii(c) || !(isdigit(c) || c == '-'))
+ continue;
+ if (i >= SYS_PROP_MAX) {
+ ERROR("Too many fields (max is %d): %s.",
+ SYS_PROP_MAX, override_list);
+ return;
+ }
+ v = strtol(s, &e, 0);
+ s = e - 1;
+ DEBUG("property[%d].value = %ld", i, v);
+ override_system_property((enum system_property_type)i, cfg, v);
+ wait_comma = 1;
+ i++;
+ }
+}
+
+/* Gets the value (setting) of specified quirks from updater configuration. */
+int get_config_quirk(enum quirk_types quirk, const struct updater_config *cfg)
+{
+ assert(quirk < QUIRK_MAX);
+ return cfg->quirks[quirk].value;
+}
+
+/* Prints the name and description from all supported quirks. */
+void updater_list_config_quirks(const struct updater_config *cfg)
+{
+ const struct quirk_entry *entry = cfg->quirks;
+ int i;
+
+ printf("Supported quirks:\n");
+ for (i = 0; i < QUIRK_MAX; i++, entry++) {
+ printf(" '%s': %s (default: %d)\n", entry->name,
+ entry->help ? entry->help : "(no description)",
+ get_config_quirk((enum quirk_types)i, cfg));
+ }
+}
+
+/*
+ * Applies a quirk if applicable (the value should be non-zero).
+ * Returns 0 on success, otherwise failure.
+ */
+static int try_apply_quirk(enum quirk_types quirk, struct updater_config *cfg)
+{
+ const struct quirk_entry *entry = cfg->quirks + quirk;
+ assert(quirk < QUIRK_MAX);
+
+ if (!entry->value)
+ return 0;
+
+ if (!entry->apply) {
+ ERROR("<%s> not implemented.", entry->name);
+ return -1;
+ }
+ DEBUG("Applying quirk <%s>.", entry->name);
+ return entry->apply(cfg);
+}
+
+/*
+ * Initialize the updater_config quirks from a list of settings.
+ * Returns 0 on success, otherwise failure.
+ */
+static int setup_config_quirks(const char *quirks, struct updater_config *cfg)
+{
+ /*
+ * The list should be in NAME[=VALUE],...
+ * Value defaults to 1 if not specified.
+ */
+ int r = 0;
+ char *buf = strdup(quirks);
+ char *token;
+
+ token = strtok(buf, ", ");
+ for (; token; token = strtok(NULL, ", ")) {
+ const char *name = token;
+ char *equ = strchr(token, '=');
+ int i, value = 1;
+ struct quirk_entry *entry = cfg->quirks;
+
+ if (equ) {
+ *equ = '\0';
+ value = strtol(equ + 1, NULL, 0);
+ }
+
+ DEBUG("Looking for quirk <%s=%d>.", name, value);
+ for (i = 0; i < QUIRK_MAX; i++, entry++) {
+ if (strcmp(name, entry->name))
+ continue;
+ entry->value = value;
+ DEBUG("Set quirk %s to %d.", entry->name, value);
+ break;
+ }
+ if (i >= QUIRK_MAX) {
+ ERROR("Unknown quirk: %s", name);
+ r++;
+ }
+ }
+ free(buf);
+ return r;
+}
+
+/*
+ * Finds a firmware section by given name in the firmware image.
+ * If successful, return zero and *section argument contains the address and
+ * size of the section; otherwise failure.
+ */
+int find_firmware_section(struct firmware_section *section,
+ const struct firmware_image *image,
+ const char *section_name)
+{
+ FmapAreaHeader *fah = NULL;
+ uint8_t *ptr;
+
+ section->data = NULL;
+ section->size = 0;
+ ptr = fmap_find_by_name(
+ image->data, image->size, image->fmap_header,
+ section_name, &fah);
+ if (!ptr)
+ return -1;
+ section->data = (uint8_t *)ptr;
+ section->size = fah->area_size;
+ return 0;
+}
+
+/*
+ * Returns true if the given FMAP section exists in the firmware image.
+ */
+static int firmware_section_exists(const struct firmware_image *image,
+ const char *section_name)
+{
+ struct firmware_section section;
+ find_firmware_section(&section, image, section_name);
+ return section.data != NULL;
+}
+
+/*
+ * Checks if the section is filled with given character.
+ * If section size is 0, return 0. If section is not empty, return non-zero if
+ * the section is filled with same character c, otherwise 0.
+ */
+static int section_is_filled_with(const struct firmware_section *section,
+ uint8_t c)
+{
+ uint32_t i;
+ if (!section->size)
+ return 0;
+ for (i = 0; i < section->size; i++)
+ if (section->data[i] != c)
+ return 0;
+ return 1;
+}
+
+/*
+ * Loads the firmware information from an FMAP section in loaded firmware image.
+ * The section should only contain ASCIIZ string as firmware version.
+ * If successful, the return value is zero and *version points to a newly
+ * allocated string as firmware version (caller must free it); otherwise
+ * failure.
+ */
+static int load_firmware_version(struct firmware_image *image,
+ const char *section_name,
+ char **version)
+{
+ struct firmware_section fwid;
+ find_firmware_section(&fwid, image, section_name);
+ if (fwid.size) {
+ *version = strndup((const char*)fwid.data, fwid.size);
+ return 0;
+ }
+ *version = strdup("");
+ return -1;
+}
+
+/*
+ * Loads a firmware image from file.
+ * If archive is provided and file_name is a relative path, read the file from
+ * archive.
+ * Returns 0 on success, otherwise failure.
+ */
+int load_firmware_image(struct firmware_image *image, const char *file_name,
+ struct archive *archive)
+{
+ DEBUG("Load image file from %s...", file_name);
+
+ if (!archive_has_entry(archive, file_name)) {
+ ERROR("Does not exist: %s", file_name);
+ return -1;
+ }
+ if (archive_read_file(archive, file_name, &image->data, &image->size) !=
+ VB2_SUCCESS) {
+ ERROR("Failed to load %s", file_name);
+ return -1;
+ }
+
+ DEBUG("Image size: %d", image->size);
+ assert(image->data);
+ image->file_name = strdup(file_name);
+
+ image->fmap_header = fmap_find(image->data, image->size);
+ if (!image->fmap_header) {
+ ERROR("Invalid image file (missing FMAP): %s", file_name);
+ return -1;
+ }
+
+ if (!firmware_section_exists(image, FMAP_RO_FRID)) {
+ ERROR("Does not look like VBoot firmware image: %s", file_name);
+ return -1;
+ }
+
+ load_firmware_version(image, FMAP_RO_FRID, &image->ro_version);
+ if (firmware_section_exists(image, FMAP_RW_FWID_A)) {
+ char **a = &image->rw_version_a, **b = &image->rw_version_b;
+ load_firmware_version(image, FMAP_RW_FWID_A, a);
+ load_firmware_version(image, FMAP_RW_FWID_B, b);
+ } else if (firmware_section_exists(image, FMAP_RW_FWID)) {
+ char **a = &image->rw_version_a, **b = &image->rw_version_b;
+ load_firmware_version(image, FMAP_RW_FWID, a);
+ load_firmware_version(image, FMAP_RW_FWID, b);
+ } else {
+ ERROR("Unsupported VBoot firmware (no RW ID): %s", file_name);
+ }
+ return 0;
+}
+
+/*
+ * Loads the active system firmware image (usually from SPI flash chip).
+ * Returns 0 if success, non-zero if error.
+ */
+int load_system_firmware(struct updater_config *cfg,
+ struct firmware_image *image)
+{
+ const char *tmp_file = updater_create_temp_file(cfg);
+
+ if (!tmp_file)
+ return -1;
+ RETURN_ON_FAILURE(host_flashrom(
+ FLASHROM_READ, tmp_file, image->programmer,
+ cfg->verbosity, NULL));
+ return load_firmware_image(image, tmp_file, NULL);
+}
+
+/*
+ * Frees the allocated resource from a firmware image object.
+ */
+void free_firmware_image(struct firmware_image *image)
+{
+ free(image->data);
+ free(image->file_name);
+ free(image->ro_version);
+ free(image->rw_version_a);
+ free(image->rw_version_b);
+ memset(image, 0, sizeof(*image));
+}
+
+/*
+ * Decides which target in RW firmware to manipulate.
+ * The `target` argument specifies if we want to know "the section to be
+ * update" (TARGET_UPDATE), or "the (active) section * to check" (TARGET_SELF).
+ * Returns the section name if success, otherwise NULL.
+ */
+static const char *decide_rw_target(struct updater_config *cfg,
+ enum target_type target,
+ int is_vboot2)
+{
+ const char *a = FMAP_RW_SECTION_A, *b = FMAP_RW_SECTION_B;
+ int slot = get_system_property(SYS_PROP_MAINFW_ACT, cfg);
+
+ /* In vboot1, always update B and check content with A. */
+ if (!is_vboot2)
+ return target == TARGET_UPDATE ? b : a;
+
+ switch (slot) {
+ case SLOT_A:
+ return target == TARGET_UPDATE ? b : a;
+
+ case SLOT_B:
+ return target == TARGET_UPDATE ? a : b;
+ }
+
+ return NULL;
+}
+
+/*
+ * Sets any needed system properties to indicate system should try the new
+ * firmware on next boot.
+ * The `target` argument is an FMAP section name indicating which to try.
+ * Returns 0 if success, non-zero if error.
+ */
+static int set_try_cookies(struct updater_config *cfg, const char *target,
+ int is_vboot2)
+{
+ int tries = 6;
+ const char *slot;
+
+ /* EC Software Sync needs few more reboots. */
+ if (cfg->ec_image.data)
+ tries += 2;
+
+ /* Find new slot according to target (section) name. */
+ if (strcmp(target, FMAP_RW_SECTION_A) == 0)
+ slot = FWACT_A;
+ else if (strcmp(target, FMAP_RW_SECTION_B) == 0)
+ slot = FWACT_B;
+ else {
+ ERROR("Unknown target: %s", target);
+ return -1;
+ }
+
+ if (cfg->emulation) {
+ printf("(emulation) Setting try_next to %s, try_count to %d.\n",
+ slot, tries);
+ return 0;
+ }
+
+ if (is_vboot2 && VbSetSystemPropertyString("fw_try_next", slot)) {
+ ERROR("Failed to set fw_try_next to %s.", slot);
+ return -1;
+ }
+ if (VbSetSystemPropertyInt("fw_try_count", tries)) {
+ ERROR("Failed to set fw_try_count to %d.", tries);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Emulates writing to firmware.
+ * Returns 0 if success, non-zero if error.
+ */
+static int emulate_write_firmware(const char *filename,
+ const struct firmware_image *image,
+ const char *section_name)
+{
+ struct firmware_image to_image = {0};
+ struct firmware_section from, to;
+ int errorcnt = 0;
+
+ from.data = image->data;
+ from.size = image->size;
+
+ if (load_firmware_image(&to_image, filename, NULL)) {
+ ERROR("Cannot load image from %s.", filename);
+ return -1;
+ }
+
+ if (section_name) {
+ find_firmware_section(&from, image, section_name);
+ if (!from.data) {
+ ERROR("No section %s in source image %s.",
+ section_name, image->file_name);
+ errorcnt++;
+ }
+ find_firmware_section(&to, &to_image, section_name);
+ if (!to.data) {
+ ERROR("No section %s in destination image %s.",
+ section_name, filename);
+ errorcnt++;
+ }
+ } else if (image->size != to_image.size) {
+ ERROR("Image size is different (%s:%d != %s:%d)",
+ image->file_name, image->size, to_image.file_name,
+ to_image.size);
+ errorcnt++;
+ } else {
+ to.data = to_image.data;
+ to.size = to_image.size;
+ }
+
+ if (!errorcnt) {
+ size_t to_write = Min(to.size, from.size);
+
+ assert(from.data && to.data);
+ DEBUG("Writing %zu bytes", to_write);
+ memcpy(to.data, from.data, to_write);
+ }
+
+ if (!errorcnt && vb2_write_file(
+ filename, to_image.data, to_image.size)) {
+ ERROR("Failed writing to file: %s", filename);
+ errorcnt++;
+ }
+
+ free_firmware_image(&to_image);
+ return errorcnt;
+}
+
+/*
+ * Writes a section from given firmware image to system firmware.
+ * If section_name is NULL, write whole image.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_firmware(struct updater_config *cfg,
+ const struct firmware_image *image,
+ const char *section_name)
+{
+ const char *tmp_file = updater_create_temp_file(cfg);
+ const char *programmer = image->programmer;
+
+ if (!tmp_file)
+ return -1;
+
+ if (cfg->emulation) {
+ printf("%s: (emulation) Writing %s from %s to %s (emu=%s).\n",
+ __FUNCTION__,
+ section_name ? section_name : "whole image",
+ image->file_name, programmer, cfg->emulation);
+
+ return emulate_write_firmware(
+ cfg->emulation, image, section_name);
+
+ }
+ if (vb2_write_file(tmp_file, image->data, image->size) != VB2_SUCCESS) {
+ ERROR("Cannot write temporary file for output: %s", tmp_file);
+ return -1;
+ }
+ return host_flashrom(FLASHROM_WRITE, tmp_file, programmer,
+ cfg->verbosity + 1, section_name);
+}
+
+/*
+ * Write a section from given firmware image to system firmware if possible.
+ * If section_name is NULL, write whole image. If the image has no data or if
+ * the section does not exist, ignore and return success.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_optional_firmware(struct updater_config *cfg,
+ const struct firmware_image *image,
+ const char *section_name)
+{
+ if (!image->data) {
+ DEBUG("No data in <%s> image.", image->programmer);
+ return 0;
+ }
+ if (section_name && !firmware_section_exists(image, section_name)) {
+ DEBUG("Image %s<%s> does not have section %s.",
+ image->file_name, image->programmer, section_name);
+ return 0;
+ }
+
+ return write_firmware(cfg, image, section_name);
+}
+
+/*
+ * Preserves (copies) the given section (by name) from image_from to image_to.
+ * The offset may be different, and the section data will be directly copied.
+ * If the section does not exist on either images, return as failure.
+ * If the source section is larger, contents on destination be truncated.
+ * If the source section is smaller, the remaining area is not modified.
+ * Returns 0 if success, non-zero if error.
+ */
+int preserve_firmware_section(const struct firmware_image *image_from,
+ struct firmware_image *image_to,
+ const char *section_name)
+{
+ struct firmware_section from, to;
+
+ find_firmware_section(&from, image_from, section_name);
+ find_firmware_section(&to, image_to, section_name);
+ if (!from.data || !to.data) {
+ DEBUG("Cannot find section %s: from=%p, to=%p", section_name,
+ from.data, to.data);
+ return -1;
+ }
+ if (from.size > to.size) {
+ printf("WARNING: %s: Section %s is truncated after updated.\n",
+ __FUNCTION__, section_name);
+ }
+ /* Use memmove in case if we need to deal with sections that overlap. */
+ memmove(to.data, from.data, Min(from.size, to.size));
+ return 0;
+}
+
+/*
+ * Finds the GBB (Google Binary Block) header on a given firmware image.
+ * Returns a pointer to valid GBB header, or NULL on not found.
+ */
+const struct vb2_gbb_header *find_gbb(const struct firmware_image *image)
+{
+ struct firmware_section section;
+ struct vb2_gbb_header *gbb_header;
+
+ find_firmware_section(&section, image, FMAP_RO_GBB);
+ gbb_header = (struct vb2_gbb_header *)section.data;
+ /*
+ * futil_valid_gbb_header needs v1 header (GoogleBinaryBlockHeader)
+ * but that should be compatible with vb2_gbb_header
+ */
+ if (!futil_valid_gbb_header((GoogleBinaryBlockHeader *)gbb_header,
+ section.size, NULL)) {
+ ERROR("Cannot find GBB in image: %s.", image->file_name);
+ return NULL;
+ }
+ return gbb_header;
+}
+
+/*
+ * Preserve the GBB contents from image_from to image_to.
+ * HWID is always preserved, and flags are preserved only if preserve_flags set.
+ * Returns 0 if success, otherwise -1 if GBB header can't be found or if HWID is
+ * too large.
+ */
+static int preserve_gbb(const struct firmware_image *image_from,
+ struct firmware_image *image_to,
+ int preserve_flags)
+{
+ int len;
+ uint8_t *hwid_to, *hwid_from;
+ const struct vb2_gbb_header *gbb_from;
+ struct vb2_gbb_header *gbb_to;
+
+ gbb_from = find_gbb(image_from);
+ /* We do want to change GBB contents later. */
+ gbb_to = (struct vb2_gbb_header *)find_gbb(image_to);
+
+ if (!gbb_from || !gbb_to)
+ return -1;
+
+ /* Preserve flags (for non-factory mode). */
+ if (preserve_flags)
+ gbb_to->flags = gbb_from->flags;
+
+ hwid_to = (uint8_t *)gbb_to + gbb_to->hwid_offset;
+ hwid_from = (uint8_t *)gbb_from + gbb_from->hwid_offset;
+
+ /* Preserve HWID. */
+ len = strlen((const char *)hwid_from);
+ if (len >= gbb_to->hwid_size)
+ return -1;
+
+ /* Zero whole area so we won't have garbage after NUL. */
+ memset(hwid_to, 0, gbb_to->hwid_size);
+ memcpy(hwid_to, hwid_from, len);
+ return 0;
+}
+
+/*
+ * Preserves the regions locked by Intel management engine.
+ */
+static int preserve_management_engine(struct updater_config *cfg,
+ const struct firmware_image *image_from,
+ struct firmware_image *image_to)
+{
+ struct firmware_section section;
+
+ find_firmware_section(&section, image_from, FMAP_SI_ME);
+ if (!section.data) {
+ DEBUG("Skipped because no section %s.", FMAP_SI_ME);
+ return 0;
+ }
+ if (section_is_filled_with(&section, 0xFF)) {
+ DEBUG("ME is probably locked - preserving %s.", FMAP_SI_DESC);
+ return preserve_firmware_section(
+ image_from, image_to, FMAP_SI_DESC);
+ }
+
+ return try_apply_quirk(QUIRK_UNLOCK_ME_FOR_UPDATE, cfg);
+}
+
+/*
+ * Preserves the critical sections from the current (active) firmware.
+ * Currently preserved sections: GBB (HWID and flags), x86 ME, {RO,RW}_PRESERVE,
+ * {RO,RW}_VPD, RW_NVRAM.
+ * Returns 0 if success, non-zero if error.
+ */
+static int preserve_images(struct updater_config *cfg)
+{
+ int errcnt = 0, i;
+ struct firmware_image *from = &cfg->image_current, *to = &cfg->image;
+ const char * const optional_sections[] = {
+ FMAP_RO_PRESERVE,
+ FMAP_RW_PRESERVE,
+ FMAP_RW_NVRAM,
+ FMAP_RW_ELOG,
+ FMAP_RW_SMMSTORE,
+ /*
+ * TODO(hungte): b/116326638: Remove RO_FSG after the migration
+ * is finished.
+ */
+ "RO_FSG",
+ };
+
+ errcnt += preserve_gbb(from, to, !cfg->factory_update);
+ errcnt += preserve_management_engine(cfg, from, to);
+ errcnt += preserve_firmware_section(from, to, FMAP_RO_VPD);
+ errcnt += preserve_firmware_section(from, to, FMAP_RW_VPD);
+ for (i = 0; i < ARRAY_SIZE(optional_sections); i++) {
+ if (!firmware_section_exists(from, optional_sections[i]))
+ continue;
+ errcnt += preserve_firmware_section(
+ from, to, optional_sections[i]);
+ }
+ return errcnt;
+}
+
+/*
+ * Compares if two sections have same size and data.
+ * Returns 0 if given sections are the same, otherwise non-zero.
+ */
+static int compare_section(const struct firmware_section *a,
+ const struct firmware_section *b)
+{
+ if (a->size != b->size)
+ return a->size - b->size;
+ return memcmp(a->data, b->data, a->size);
+}
+
+/*
+ * Returns if the images are different (should be updated) in given section.
+ * If the section contents are the same or if the section does not exist on both
+ * images, return value is 0 (no need to update). Otherwise the return value is
+ * non-zero, indicating an update should be performed.
+ * If section_name is NULL, compare whole images.
+ */
+static int section_needs_update(const struct firmware_image *image_from,
+ const struct firmware_image *image_to,
+ const char *section_name)
+{
+ struct firmware_section from, to;
+
+ if (!section_name) {
+ if (image_from->size != image_to->size)
+ return -1;
+ return memcmp(image_from->data, image_to->data, image_to->size);
+ }
+
+ find_firmware_section(&from, image_from, section_name);
+ find_firmware_section(&to, image_to, section_name);
+
+ return compare_section(&from, &to);
+}
+
+/*
+ * Returns true if the write protection is enabled on current system.
+ */
+static int is_write_protection_enabled(struct updater_config *cfg)
+{
+ /* Default to enabled. */
+ int wp = get_system_property(SYS_PROP_WP_HW, cfg);
+ if (wp == WP_DISABLED)
+ return wp;
+ /* For error or enabled, check WP SW. */
+ wp = get_system_property(SYS_PROP_WP_SW, cfg);
+ /* Consider all errors as enabled. */
+ if (wp != WP_DISABLED)
+ return WP_ENABLED;
+ return wp;
+}
+
+/*
+ * Checks if the given firmware images are compatible with current platform.
+ * In current implementation (following Chrome OS style), we assume the platform
+ * is identical to the name before a dot (.) in firmware version.
+ * Returns 0 for success, otherwise failure.
+ */
+static int check_compatible_platform(struct updater_config *cfg)
+{
+ int len;
+ struct firmware_image *image_from = &cfg->image_current,
+ *image_to = &cfg->image;
+ const char *from_dot = strchr(image_from->ro_version, '.'),
+ *to_dot = strchr(image_to->ro_version, '.');
+
+ if (!from_dot || !to_dot) {
+ DEBUG("Missing dot (from=%p, to=%p)", from_dot, to_dot);
+ return -1;
+ }
+ len = from_dot - image_from->ro_version + 1;
+ DEBUG("Platform: %*.*s", len, len, image_from->ro_version);
+ return strncmp(image_from->ro_version, image_to->ro_version, len);
+}
+
+/*
+ * Returns a valid root key from GBB header, or NULL on failure.
+ */
+static const struct vb2_packed_key *get_rootkey(
+ const struct vb2_gbb_header *gbb)
+{
+ struct vb2_packed_key *key = NULL;
+
+ key = (struct vb2_packed_key *)((uint8_t *)gbb + gbb->rootkey_offset);
+ if (!packed_key_looks_ok(key, gbb->rootkey_size)) {
+ ERROR("Invalid root key.");
+ return NULL;
+ }
+ return key;
+}
+
+/*
+ * Returns a key block key from given image section, or NULL on failure.
+ */
+static const struct vb2_keyblock *get_keyblock(
+ const struct firmware_image *image,
+ const char *section_name)
+{
+ struct firmware_section section;
+
+ find_firmware_section(&section, image, section_name);
+ /* A keyblock must be followed by a vb2_fw_preamble. */
+ if (section.size < sizeof(struct vb2_keyblock) +
+ sizeof(struct vb2_fw_preamble)) {
+ ERROR("Invalid section: %s", section_name);
+ return NULL;
+ }
+ return (const struct vb2_keyblock *)section.data;
+}
+
+/*
+ * Duplicates a key block and returns the duplicated block.
+ * The caller must free the returned key block after being used.
+ */
+static struct vb2_keyblock *dupe_keyblock(const struct vb2_keyblock *block)
+{
+ struct vb2_keyblock *new_block;
+
+ new_block = (struct vb2_keyblock *)malloc(block->keyblock_size);
+ assert(new_block);
+ memcpy(new_block, block, block->keyblock_size);
+ return new_block;
+}
+
+/*
+ * Verifies if keyblock is signed with given key.
+ * Returns 0 on success, otherwise failure.
+ */
+static int verify_keyblock(const struct vb2_keyblock *block,
+ const struct vb2_packed_key *sign_key) {
+ int r;
+ uint8_t workbuf[VB2_WORKBUF_RECOMMENDED_SIZE];
+ struct vb2_workbuf wb;
+ struct vb2_public_key key;
+ struct vb2_keyblock *new_block;
+
+ if (block->keyblock_signature.sig_size == 0) {
+ ERROR("Keyblock is not signed.");
+ return -1;
+ }
+ vb2_workbuf_init(&wb, workbuf, sizeof(workbuf));
+ if (VB2_SUCCESS != vb2_unpack_key(&key, sign_key)) {
+ ERROR("Invalid signing key,");
+ return -1;
+ }
+
+ /*
+ * vb2_verify_keyblock will destroy the signature inside keyblock
+ * so we have to verify with a local copy.
+ */
+ new_block = dupe_keyblock(block);
+ r = vb2_verify_keyblock(new_block, new_block->keyblock_size, &key, &wb);
+ free(new_block);
+
+ if (r != VB2_SUCCESS) {
+ ERROR("Failed verifying key block.");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Gets the data key and firmware version from a section on firmware image.
+ * The section should contain a vb2_keyblock and a vb2_fw_preamble immediately
+ * after key block so we can decode and save the data key and firmware version
+ * into argument `data_key_version` and `firmware_version`.
+ * Returns 0 for success, otherwise failure.
+ */
+static int get_key_versions(const struct firmware_image *image,
+ const char *section_name,
+ unsigned int *data_key_version,
+ unsigned int *firmware_version)
+{
+ const struct vb2_keyblock *keyblock = get_keyblock(image, section_name);
+ const struct vb2_fw_preamble *pre;
+
+ if (!keyblock)
+ return -1;
+ *data_key_version = keyblock->data_key.key_version;
+ pre = (struct vb2_fw_preamble *)((uint8_t*)keyblock +
+ keyblock->keyblock_size);
+ *firmware_version = pre->firmware_version;
+ DEBUG("%s: data key version = %d, firmware version = %d",
+ image->file_name, *data_key_version, *firmware_version);
+ return 0;
+}
+
+/*
+ * Checks if the root key in ro_image can verify vblocks in rw_image.
+ * Returns 0 for success, otherwise failure.
+ */
+static int check_compatible_root_key(const struct firmware_image *ro_image,
+ const struct firmware_image *rw_image)
+{
+ const struct vb2_gbb_header *gbb = find_gbb(ro_image);
+ const struct vb2_packed_key *rootkey;
+ const struct vb2_keyblock *keyblock;
+
+ if (!gbb)
+ return -1;
+
+ rootkey = get_rootkey(gbb);
+ if (!rootkey)
+ return -1;
+
+ /* Assume VBLOCK_A and VBLOCK_B are signed in same way. */
+ keyblock = get_keyblock(rw_image, FMAP_RW_VBLOCK_A);
+ if (!keyblock)
+ return -1;
+
+ if (verify_keyblock(keyblock, rootkey) != 0) {
+ const struct vb2_gbb_header *gbb_rw = find_gbb(rw_image);
+ const struct vb2_packed_key *rootkey_rw = NULL;
+ int is_same_key = 0;
+ /*
+ * Try harder to provide more info.
+ * packed_key_sha1_string uses static buffer so don't call
+ * it twice in args list of one expression.
+ */
+ if (gbb_rw)
+ rootkey_rw = get_rootkey(gbb_rw);
+ if (rootkey_rw) {
+ if (rootkey->key_offset == rootkey_rw->key_offset &&
+ rootkey->key_size == rootkey_rw->key_size &&
+ memcmp(rootkey, rootkey_rw, rootkey->key_size +
+ rootkey->key_offset) == 0)
+ is_same_key = 1;
+ }
+ printf("Current (RO) image root key is %s, ",
+ packed_key_sha1_string(rootkey));
+ if (is_same_key)
+ printf("same with target (RW) image. "
+ "Maybe RW corrupted?\n");
+ else
+ printf("target (RW) image is signed with rootkey %s.\n",
+ rootkey_rw ? packed_key_sha1_string(rootkey_rw) :
+ "<invalid>");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Returns 1 if a given file (cbfs_entry_name) exists inside a particular CBFS
+ * section of an image file, otherwise 0.
+ */
+static int cbfs_file_exists(const char *image_file,
+ const char *section_name,
+ const char *cbfs_entry_name)
+{
+ char *cmd;
+ int r;
+
+ ASPRINTF(&cmd,
+ "cbfstool '%s' print -r %s 2>/dev/null | grep -q '^%s '",
+ image_file, section_name, cbfs_entry_name);
+ r = system(cmd);
+ free(cmd);
+ return !r;
+}
+
+/*
+ * Returns non-zero if the RW_LEGACY needs to be updated, otherwise 0.
+ */
+static int legacy_needs_update(struct updater_config *cfg)
+{
+ int has_from, has_to;
+ const char * const tag = "cros_allow_auto_update";
+ const char *section = FMAP_RW_LEGACY;
+ const char *tmp_path = updater_create_temp_file(cfg);
+
+ DEBUG("Checking %s contents...", FMAP_RW_LEGACY);
+ if (!tmp_path ||
+ vb2_write_file(tmp_path, cfg->image.data, cfg->image.size)) {
+ ERROR("Failed to create temporary file for image contents.");
+ return 0;
+ }
+
+ has_to = cbfs_file_exists(tmp_path, section, tag);
+ has_from = cbfs_file_exists(tmp_path, section, tag);
+
+ if (!has_from || !has_to) {
+ DEBUG("Current legacy firmware has%s updater tag (%s) "
+ "and target firmware has%s updater tag, won't update.",
+ has_from ? "" : " no", tag, has_to ? "" : " no");
+ return 0;
+ }
+
+ return section_needs_update(
+ &cfg->image_current, &cfg->image, FMAP_RW_LEGACY);
+}
+
+/*
+ * Checks if the given firmware image is signed with a key that won't be
+ * blocked by TPM's anti-rollback detection.
+ * Returns 0 for success, otherwise failure.
+ */
+static int do_check_compatible_tpm_keys(struct updater_config *cfg,
+ const struct firmware_image *rw_image)
+{
+ unsigned int data_key_version = 0, firmware_version = 0,
+ tpm_data_key_version = 0, tpm_firmware_version = 0;
+ int tpm_fwver = 0;
+
+ /* Fail if the given image does not look good. */
+ if (get_key_versions(rw_image, FMAP_RW_VBLOCK_A, &data_key_version,
+ &firmware_version) != 0)
+ return -1;
+
+ /* The stored tpm_fwver can be 0 (b/116298359#comment3). */
+ tpm_fwver = get_system_property(SYS_PROP_TPM_FWVER, cfg);
+ if (tpm_fwver < 0) {
+ ERROR("Invalid tpm_fwver: %d.", tpm_fwver);
+ return -1;
+ }
+
+ tpm_data_key_version = tpm_fwver >> 16;
+ tpm_firmware_version = tpm_fwver & 0xffff;
+ DEBUG("TPM: data_key_version = %d, firmware_version = %d",
+ tpm_data_key_version, tpm_firmware_version);
+
+ if (tpm_data_key_version > data_key_version) {
+ ERROR("Data key version rollback detected (%d->%d).",
+ tpm_data_key_version, data_key_version);
+ return -1;
+ }
+ if (tpm_firmware_version > firmware_version) {
+ ERROR("Firmware version rollback detected (%d->%d).",
+ tpm_firmware_version, firmware_version);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Wrapper for do_check_compatible_tpm_keys.
+ * Will return 0 if do_check_compatible_tpm_keys success or if cfg.force_update
+ * is set; otherwise non-zero.
+ */
+static int check_compatible_tpm_keys(struct updater_config *cfg,
+ const struct firmware_image *rw_image)
+{
+ int r = do_check_compatible_tpm_keys(cfg, rw_image);
+ if (!r)
+ return r;
+ if (!cfg->force_update) {
+ ERROR("Add --force if you want to waive TPM checks.");
+ return r;
+ }
+ printf("TPM KEYS CHECK IS WAIVED BY --force. YOU ARE ON YOUR OWN.\n");
+ return 0;
+}
+
+const char * const updater_error_messages[] = {
+ [UPDATE_ERR_DONE] = "Done (no error)",
+ [UPDATE_ERR_NEED_RO_UPDATE] = "RO changed and no WP. Need full update.",
+ [UPDATE_ERR_NO_IMAGE] = "No image to update; try specify with -i.",
+ [UPDATE_ERR_SYSTEM_IMAGE] = "Cannot load system active firmware.",
+ [UPDATE_ERR_INVALID_IMAGE] = "The given firmware image is not valid.",
+ [UPDATE_ERR_SET_COOKIES] = "Failed writing system flags to try update.",
+ [UPDATE_ERR_WRITE_FIRMWARE] = "Failed writing firmware.",
+ [UPDATE_ERR_PLATFORM] = "Your system platform is not compatible.",
+ [UPDATE_ERR_TARGET] = "No valid RW target to update. Abort.",
+ [UPDATE_ERR_ROOT_KEY] = "RW not signed by same RO root key",
+ [UPDATE_ERR_TPM_ROLLBACK] = "RW not usable due to TPM anti-rollback.",
+ [UPDATE_ERR_UNKNOWN] = "Unknown error.",
+};
+
+/*
+ * The main updater for "Try-RW update", to update only one RW section
+ * and try if it can boot properly on reboot.
+ * This was also known as --mode=autoupdate,--wp=1 in legacy updater.
+ * Returns UPDATE_ERR_DONE if success, otherwise error.
+ */
+static enum updater_error_codes update_try_rw_firmware(
+ struct updater_config *cfg,
+ struct firmware_image *image_from,
+ struct firmware_image *image_to,
+ int wp_enabled)
+{
+ const char *target;
+ int has_update = 1;
+ int is_vboot2 = get_system_property(SYS_PROP_FW_VBOOT2, cfg);
+
+ preserve_gbb(image_from, image_to, 1);
+ if (!wp_enabled && section_needs_update(
+ image_from, image_to, FMAP_RO_SECTION))
+ return UPDATE_ERR_NEED_RO_UPDATE;
+
+ printf("Checking compatibility...\n");
+ if (check_compatible_root_key(image_from, image_to))
+ return UPDATE_ERR_ROOT_KEY;
+ if (check_compatible_tpm_keys(cfg, image_to))
+ return UPDATE_ERR_TPM_ROLLBACK;
+
+ DEBUG("Firmware %s vboot2.", is_vboot2 ? "is" : "is NOT");
+ target = decide_rw_target(cfg, TARGET_SELF, is_vboot2);
+ if (target == NULL) {
+ ERROR("TRY-RW update needs system to boot in RW firmware.");
+ return UPDATE_ERR_TARGET;
+ }
+
+ printf("Checking %s contents...\n", target);
+ if (!firmware_section_exists(image_to, target)) {
+ ERROR("Cannot find section '%s' on firmware image: %s",
+ target, image_to->file_name);
+ return UPDATE_ERR_INVALID_IMAGE;
+ }
+ if (!cfg->force_update)
+ has_update = section_needs_update(image_from, image_to, target);
+
+ if (has_update) {
+ target = decide_rw_target(cfg, TARGET_UPDATE, is_vboot2);
+ printf(">> TRY-RW UPDATE: Updating %s to try on reboot.\n",
+ target);
+
+ if (write_firmware(cfg, image_to, target))
+ return UPDATE_ERR_WRITE_FIRMWARE;
+ if (set_try_cookies(cfg, target, is_vboot2))
+ return UPDATE_ERR_SET_COOKIES;
+ } else {
+ /* Clear trial cookies for vboot1. */
+ if (!is_vboot2 && !cfg->emulation)
+ VbSetSystemPropertyInt("fwb_tries", 0);
+ }
+
+ /* Do not fail on updating legacy. */
+ if (legacy_needs_update(cfg)) {
+ has_update = 1;
+ printf(">> LEGACY UPDATE: Updating %s.\n", FMAP_RW_LEGACY);
+ write_firmware(cfg, image_to, FMAP_RW_LEGACY);
+ }
+
+ if (!has_update)
+ printf(">> No need to update.\n");
+
+ return UPDATE_ERR_DONE;
+}
+
+/*
+ * The main updater for "RW update".
+ * This was also known as --mode=recovery, --wp=1 in legacy updater.
+ * Returns UPDATE_ERR_DONE if success, otherwise error.
+ */
+static enum updater_error_codes update_rw_firmrware(
+ struct updater_config *cfg,
+ struct firmware_image *image_from,
+ struct firmware_image *image_to)
+{
+ printf(">> RW UPDATE: Updating RW sections (%s, %s, %s, and %s).\n",
+ FMAP_RW_SECTION_A, FMAP_RW_SECTION_B, FMAP_RW_SHARED,
+ FMAP_RW_LEGACY);
+
+ printf("Checking compatibility...\n");
+ if (check_compatible_root_key(image_from, image_to))
+ return UPDATE_ERR_ROOT_KEY;
+ if (check_compatible_tpm_keys(cfg, image_to))
+ return UPDATE_ERR_TPM_ROLLBACK;
+ /*
+ * TODO(hungte) Speed up by flashing multiple sections in one
+ * command, or provide diff file.
+ */
+ if (write_firmware(cfg, image_to, FMAP_RW_SECTION_A) ||
+ write_firmware(cfg, image_to, FMAP_RW_SECTION_B) ||
+ write_firmware(cfg, image_to, FMAP_RW_SHARED) ||
+ write_optional_firmware(cfg, image_to, FMAP_RW_LEGACY))
+ return UPDATE_ERR_WRITE_FIRMWARE;
+
+ return UPDATE_ERR_DONE;
+}
+
+/*
+ * The main updater for "Legacy update".
+ * This is equivalent to --mode=legacy.
+ * Returns UPDATE_ERR_DONE if success, otherwise error.
+ */
+static enum updater_error_codes update_legacy_firmware(
+ struct updater_config *cfg,
+ struct firmware_image *image_to)
+{
+ printf(">> LEGACY UPDATE: Updating firmware %s.\n", FMAP_RW_LEGACY);
+
+ if (write_firmware(cfg, image_to, FMAP_RW_LEGACY))
+ return UPDATE_ERR_WRITE_FIRMWARE;
+
+ return UPDATE_ERR_DONE;
+}
+
+/*
+ * The main updater for "Full update".
+ * This was also known as "--mode=factory" or "--mode=recovery, --wp=0" in
+ * legacy updater.
+ * Returns UPDATE_ERR_DONE if success, otherwise error.
+ */
+static enum updater_error_codes update_whole_firmware(
+ struct updater_config *cfg,
+ struct firmware_image *image_to)
+{
+ printf(">> FULL UPDATE: Updating whole firmware image(s), RO+RW.\n");
+
+ if (preserve_images(cfg))
+ DEBUG("Failed to preserve some sections - ignore.");
+
+
+ printf("Checking compatibility...\n");
+ if (check_compatible_tpm_keys(cfg, image_to))
+ return UPDATE_ERR_TPM_ROLLBACK;
+
+ /* FMAP may be different so we should just update all. */
+ if (write_firmware(cfg, image_to, NULL) ||
+ write_optional_firmware(cfg, &cfg->ec_image, NULL) ||
+ write_optional_firmware(cfg, &cfg->pd_image, NULL))
+ return UPDATE_ERR_WRITE_FIRMWARE;
+
+ return UPDATE_ERR_DONE;
+}
+
+/*
+ * The main updater to update system firmware using the configuration parameter.
+ * Returns UPDATE_ERR_DONE if success, otherwise failure.
+ */
+enum updater_error_codes update_firmware(struct updater_config *cfg)
+{
+ int wp_enabled;
+ struct firmware_image *image_from = &cfg->image_current,
+ *image_to = &cfg->image;
+ if (!image_to->data)
+ return UPDATE_ERR_NO_IMAGE;
+
+ if (try_apply_quirk(QUIRK_DAISY_SNOW_DUAL_MODEL, cfg))
+ return UPDATE_ERR_PLATFORM;
+
+ printf(">> Target image: %s (RO:%s, RW/A:%s, RW/B:%s).\n",
+ image_to->file_name, image_to->ro_version,
+ image_to->rw_version_a, image_to->rw_version_b);
+
+ if (try_apply_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg))
+ return UPDATE_ERR_PLATFORM;
+
+ if (!image_from->data) {
+ /*
+ * TODO(hungte) Read only RO_SECTION, VBLOCK_A, VBLOCK_B,
+ * RO_VPD, RW_VPD, RW_NVRAM, RW_LEGACY.
+ */
+ printf("Loading current system firmware...\n");
+ if (load_system_firmware(cfg, image_from) != 0)
+ return UPDATE_ERR_SYSTEM_IMAGE;
+ }
+ printf(">> Current system: %s (RO:%s, RW/A:%s, RW/B:%s).\n",
+ image_from->file_name, image_from->ro_version,
+ image_from->rw_version_a, image_from->rw_version_b);
+
+ if (check_compatible_platform(cfg))
+ return UPDATE_ERR_PLATFORM;
+
+ wp_enabled = is_write_protection_enabled(cfg);
+ printf(">> Write protection: %d (%s; HW=%d, SW=%d).\n", wp_enabled,
+ wp_enabled ? "enabled" : "disabled",
+ get_system_property(SYS_PROP_WP_HW, cfg),
+ get_system_property(SYS_PROP_WP_SW, cfg));
+
+ if (try_apply_quirk(QUIRK_ENLARGE_IMAGE, cfg))
+ return UPDATE_ERR_SYSTEM_IMAGE;
+
+ if (try_apply_quirk(QUIRK_EVE_SMM_STORE, cfg))
+ return UPDATE_ERR_INVALID_IMAGE;
+
+ if (debugging_enabled)
+ print_system_properties(cfg);
+
+ if (cfg->legacy_update)
+ return update_legacy_firmware(cfg, image_to);
+
+ if (cfg->try_update) {
+ enum updater_error_codes r;
+ r = update_try_rw_firmware(cfg, image_from, image_to,
+ wp_enabled);
+ if (r != UPDATE_ERR_NEED_RO_UPDATE)
+ return r;
+ printf("Warning: %s\n", updater_error_messages[r]);
+ }
+
+ if (wp_enabled)
+ return update_rw_firmrware(cfg, image_from, image_to);
+ else
+ return update_whole_firmware(cfg, image_to);
+}
+
+/*
+ * Allocates and initializes a updater_config object with default values.
+ * Returns the newly allocated object, or NULL on error.
+ */
+struct updater_config *updater_new_config()
+{
+ struct system_property *props;
+ struct updater_config *cfg = (struct updater_config *)calloc(
+ 1, sizeof(struct updater_config));
+ if (!cfg)
+ return cfg;
+ cfg->image.programmer = PROG_HOST;
+ cfg->image_current.programmer = PROG_HOST;
+ cfg->ec_image.programmer = PROG_EC;
+ cfg->pd_image.programmer = PROG_PD;
+
+ props = cfg->system_properties;
+ props[SYS_PROP_MAINFW_ACT].getter = host_get_mainfw_act;
+ props[SYS_PROP_TPM_FWVER].getter = host_get_tpm_fwver;
+ props[SYS_PROP_FW_VBOOT2].getter = host_get_fw_vboot2;
+ props[SYS_PROP_PLATFORM_VER].getter = host_get_platform_version;
+ props[SYS_PROP_WP_HW].getter = host_get_wp_hw;
+ props[SYS_PROP_WP_SW].getter = host_get_wp_sw;
+
+ updater_register_quirks(cfg);
+ return cfg;
+}
+
+/*
+ * Saves everything from stdin to given output file.
+ * Returns 0 on success, otherwise failure.
+ */
+static int save_from_stdin(const char *output)
+{
+ FILE *in = stdin, *out = fopen(output, "wb");
+ char buffer[4096];
+ size_t sz;
+
+ assert(in);
+ if (!out)
+ return -1;
+
+ while (!feof(in)) {
+ sz = fread(buffer, 1, sizeof(buffer), in);
+ fwrite(buffer, 1, sz, out);
+ }
+ fclose(out);
+ return 0;
+}
+
+/*
+ * Loads images into updater configuration.
+ * Returns 0 on success, otherwise number of failures.
+ */
+static int updater_load_images(struct updater_config *cfg,
+ int host_only,
+ const char *image,
+ const char *ec_image,
+ const char *pd_image)
+{
+ int errorcnt = 0;
+ struct archive *ar = cfg->archive;
+
+ if (!cfg->image.data && image) {
+ if (image && strcmp(image, "-") == 0) {
+ fprintf(stderr, "Reading image from stdin...\n");
+ image = updater_create_temp_file(cfg);
+ if (image)
+ errorcnt += !!save_from_stdin(image);
+ }
+ errorcnt += !!load_firmware_image(&cfg->image, image, ar);
+ }
+ if (cfg->emulation || host_only)
+ return errorcnt;
+
+ if (!cfg->ec_image.data && ec_image)
+ errorcnt += !!load_firmware_image(&cfg->ec_image, ec_image, ar);
+ if (!cfg->pd_image.data && pd_image)
+ errorcnt += !!load_firmware_image(&cfg->pd_image, pd_image, ar);
+ return errorcnt;
+}
+
+/*
+ * Writes a firmware image to specified file.
+ * Returns 0 on success, otherwise failure.
+ */
+static int updater_output_image(const struct firmware_image *image,
+ const char *fname, const char *root)
+{
+ int r = 0;
+ char *fpath;
+
+ if (!image->data)
+ return 0;
+
+ ASPRINTF(&fpath, "%s/%s", root, fname);
+ r = vb2_write_file(fpath, image->data, image->size);
+ if (r)
+ ERROR("Failed writing firmware image to: %s", fpath);
+ else
+ printf("Firmware image saved in: %s\n", fpath);
+
+ free(fpath);
+ return !!r;
+}
+
+/*
+ * Applies white label information to an existing model config.
+ * Returns 0 on success, otherwise failure.
+ */
+static int updater_apply_white_label(struct updater_config *cfg,
+ struct model_config *model,
+ const char *signature_id)
+{
+ const char *tmp_image = NULL;
+
+ assert(model->is_white_label);
+ if (!signature_id) {
+ if (cfg->image_current.data) {
+ tmp_image = updater_create_temp_file(cfg);
+ if (!tmp_image)
+ return 1;
+ if (vb2_write_file(tmp_image, cfg->image_current.data,
+ cfg->image_current.size)) {
+ ERROR("Failed writing temporary image file.");
+ return 1;
+ }
+ } else {
+ printf("Loading system firmware for white label..\n");
+ load_system_firmware(cfg, &cfg->image_current);
+ tmp_image = cfg->image_current.file_name;
+ }
+ if (!tmp_image) {
+ ERROR("Failed to get system current firmware");
+ return 1;
+ }
+ }
+ return !!model_apply_white_label(
+ model, cfg->archive, signature_id, tmp_image);
+}
+
+/*
+ * Setup what the updater has to do against an archive.
+ * Returns number of failures, or 0 on success.
+ */
+static int updater_setup_archive(
+ struct updater_config *cfg,
+ const struct updater_config_arguments *arg,
+ struct manifest *manifest,
+ int is_factory)
+{
+ int errorcnt = 0;
+ struct archive *ar = cfg->archive;
+ const struct model_config *model;
+
+ if (arg->do_manifest) {
+ assert(!arg->image);
+ print_json_manifest(manifest);
+ /* No additional error. */
+ return errorcnt;
+ }
+
+ model = manifest_find_model(manifest, arg->model);
+ if (!model)
+ return ++errorcnt;
+
+ if (model->is_white_label) {
+ /*
+ * It is fine to fail in updater_apply_white_label for factory
+ * mode so we are not checking the return value; instead we
+ * verify if the patches do contain new root key.
+ */
+ updater_apply_white_label(cfg, (struct model_config *)model,
+ arg->signature_id);
+ if (!model->patches.rootkey) {
+ if (!is_factory) {
+ ERROR("Need VPD set for white label.");
+ return ++errorcnt;
+ }
+ fprintf(stderr, "Warning: No VPD for white label.\n");
+ }
+ }
+
+ errorcnt += updater_load_images(
+ cfg, arg->host_only, model->image, model->ec_image,
+ model->pd_image);
+ errorcnt += patch_image_by_model(&cfg->image, model, ar);
+ return errorcnt;
+}
+
+/*
+ * Helper function to setup an allocated updater_config object.
+ * Returns number of failures, or 0 on success.
+ */
+int updater_setup_config(struct updater_config *cfg,
+ const struct updater_config_arguments *arg,
+ int *do_update)
+{
+ int errorcnt = 0;
+ int check_single_image = 0, check_wp_disabled = 0;
+ int do_output = 0;
+ const char *default_quirks = NULL;
+ const char *archive_path = arg->archive;
+
+ /* Setup values that may change output or decision of other argument. */
+ cfg->verbosity = arg->verbosity;
+ cfg->factory_update = arg->is_factory;
+ if (arg->force_update)
+ cfg->force_update = 1;
+
+ /* Check incompatible options and return early. */
+ if (arg->do_manifest) {
+ if (!!arg->archive == !!arg->image) {
+ ERROR("--manifest needs either -a or -i");
+ return ++errorcnt;
+ }
+ if (arg->archive && (arg->ec_image || arg->pd_image)) {
+ ERROR("--manifest for archive (-a) does not accept "
+ "additional images (--ec_image, --pd_image).");
+ return ++errorcnt;
+ }
+ *do_update = 0;
+ }
+ if (arg->repack || arg->unpack) {
+ if (!arg->archive) {
+ ERROR("--{re,un}pack needs --archive.");
+ return ++errorcnt;
+ }
+ *do_update = 0;
+ }
+
+ /* Setup update mode. */
+ if (arg->try_update)
+ cfg->try_update = 1;
+ if (arg->mode) {
+ if (strcmp(arg->mode, "autoupdate") == 0) {
+ cfg->try_update = 1;
+ } else if (strcmp(arg->mode, "recovery") == 0) {
+ cfg->try_update = 0;
+ } else if (strcmp(arg->mode, "legacy") == 0) {
+ cfg->legacy_update = 1;
+ } else if (strcmp(arg->mode, "factory") == 0 ||
+ strcmp(arg->mode, "factory_install") == 0) {
+ cfg->factory_update = 1;
+ } else if (strcmp(arg->mode, "output") == 0) {
+ do_output = 1;
+ } else {
+ errorcnt++;
+ ERROR("Invalid mode: %s", arg->mode);
+ }
+ }
+ if (cfg->factory_update) {
+ /* factory_update must be processed after arg->mode. */
+ check_wp_disabled = 1;
+ cfg->try_update = 0;
+ }
+
+ /* Setup properties and fields that do not have external dependency. */
+ if (arg->programmer) {
+ check_single_image = 1;
+ cfg->image.programmer = arg->programmer;
+ cfg->image_current.programmer = arg->programmer;
+ DEBUG("AP (host) programmer changed to %s.", arg->programmer);
+ }
+ if (arg->sys_props)
+ override_properties_from_list(arg->sys_props, cfg);
+ if (arg->write_protection) {
+ /* arg->write_protection must be done after arg->sys_props. */
+ int r = strtol(arg->write_protection, NULL, 0);
+ override_system_property(SYS_PROP_WP_HW, cfg, r);
+ override_system_property(SYS_PROP_WP_SW, cfg, r);
+ }
+
+ /* Set up archive and load images. */
+ if (arg->emulation) {
+ /* Process emulation file first. */
+ check_single_image = 1;
+ cfg->emulation = arg->emulation;
+ DEBUG("Using file %s for emulation.", arg->emulation);
+ errorcnt += !!load_firmware_image(
+ &cfg->image_current, arg->emulation, NULL);
+ }
+
+ /* Always load images specified from command line directly. */
+ errorcnt += updater_load_images(
+ cfg, arg->host_only, arg->image, arg->ec_image,
+ arg->pd_image);
+
+ if (!archive_path)
+ archive_path = ".";
+ cfg->archive = archive_open(archive_path);
+ if (!cfg->archive) {
+ ERROR("Failed to open archive: %s", archive_path);
+ return ++errorcnt;
+ }
+
+ /* Process archives which may not have valid contents. */
+ if (arg->repack || arg->unpack) {
+ const char *work_name = arg->repack ? arg->repack : arg->unpack;
+ struct archive *from, *to, *work;
+
+ work = archive_open(work_name);
+ if (arg->repack) {
+ from = work;
+ to = cfg->archive;
+ } else {
+ to = work;
+ from = cfg->archive;
+ }
+ if (!work) {
+ ERROR("Failed to open: %s", work_name);
+ return ++errorcnt;
+ }
+ errorcnt += !!archive_copy(from, to);
+ /* TODO(hungte) Update manifest after copied. */
+ archive_close(work);
+ return errorcnt;
+ }
+
+ /* Load images from archive. */
+ if (arg->archive) {
+ struct manifest *m = new_manifest_from_archive(cfg->archive);
+ if (m) {
+ errorcnt += updater_setup_archive(
+ cfg, arg, m, cfg->factory_update);
+ delete_manifest(m);
+ } else {
+ ERROR("Failure in archive: %s", arg->archive);
+ ++errorcnt;
+ }
+ } else if (arg->do_manifest) {
+ char name[] = "default";
+ struct model_config model = {
+ .name = name,
+ .image = arg->image,
+ .ec_image = arg->ec_image,
+ .pd_image = arg->pd_image,
+ };
+ struct manifest manifest = {
+ .num = 1,
+ .models = &model,
+ };
+ assert(model.image);
+ print_json_manifest(&manifest);
+ }
+
+ /*
+ * Quirks must be loaded after images are loaded because we use image
+ * contents to decide default quirks to load. Also, we have to load
+ * default quirks first so user can override them using command line.
+ */
+ default_quirks = updater_get_default_quirks(cfg);
+ if (default_quirks)
+ errorcnt += !!setup_config_quirks(default_quirks, cfg);
+ if (arg->quirks)
+ errorcnt += !!setup_config_quirks(arg->quirks, cfg);
+
+ /* Additional checks. */
+ if (check_single_image && (cfg->ec_image.data || cfg->pd_image.data)) {
+ errorcnt++;
+ ERROR("EC/PD images are not supported in current mode.");
+ }
+ if (check_wp_disabled && is_write_protection_enabled(cfg)) {
+ errorcnt++;
+ ERROR("Factory mode needs WP disabled.");
+ }
+ if (!errorcnt && do_output) {
+ const char *r = arg->output_dir;
+ if (!r)
+ r = ".";
+ errorcnt += updater_output_image(&cfg->image, "bios.bin", r);
+ errorcnt += updater_output_image(&cfg->ec_image, "ec.bin", r);
+ errorcnt += updater_output_image(&cfg->pd_image, "pd.bin", r);
+ *do_update = 0;
+ }
+ return errorcnt;
+}
+
+/*
+ * Releases all resources in an updater configuration object.
+ */
+void updater_delete_config(struct updater_config *cfg)
+{
+ assert(cfg);
+ free_firmware_image(&cfg->image);
+ free_firmware_image(&cfg->image_current);
+ free_firmware_image(&cfg->ec_image);
+ free_firmware_image(&cfg->pd_image);
+ updater_remove_all_temp_files(cfg);
+ if (cfg->archive)
+ archive_close(cfg->archive);
+ free(cfg);
+}