summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHung-Te Lin <hungte@chromium.org>2018-09-17 17:47:25 +0800
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2018-11-05 04:20:53 +0000
commit534155d94bc79e422f644c5d828484aec507d544 (patch)
tree58a2859b527f2da1cdfabeeb6e1c52a23fdbe11f
parent2495d9902bf9107a95adea3528ad8164e53e87da (diff)
downloadvboot-534155d94bc79e422f644c5d828484aec507d544.tar.gz
futility: Add firmware updater as 'futility update'
The factory branches may need to run firmware updater extracted from newer release images which relies on 'futility update' to run. This change collects all related changes: cabd2f8c futility: update: Fix update failure in factory mode due to wrong WP logic ac8cb8dd futility: update: Add legacy option '--factory' 79bdb0cc futility: Remove FUTIL_STATIC build rules d6f7c16b futility: update: Skip TPM check if tpm_fwver is not valid. f6198074 futility: update: Preserve new sections: RO_PRESERVE and RW_PRESERVE f67b991a futility: update: Preserve RO_FSG ee96e741 futility: update: Preserve RW_ELOG b07165b5 futility: update: Allow tpm_fwver=0 and allow --force to waive TPM check 5a5be0c7 futility: update: Revise error messages ba73f35e futility: update: Refactor 'emulation' (--emulate). eb166257 futility: update: Add --programmer to override flashrom programmer for servo 5fa5e3b9 futility: update: Refactor: move updater logic to 'updater.c' 243e2115 futility: updater: Refactor: move command line processing to cmd_update.c e7bcda54 futility: updater: Refactor: localize temp files management 8810b089 futility: updater: Refactor: move quirks to 'updater_quirks.c' a1bfb650 futility: updater: Add default quirks a2b9e356 futility: updater: Add quirk 'daisy_snow_dual_model' for daisy_snow ba30c93b futility: updater: Preserve SMMSTORE and add quirk 'eve_smm_store' 38d06701 futility: updater: Support reading main image from stdin f1609b8c futility: updater: Revise verbosity and error messages fabf760f futility: updater: Add ASPRINTF macro 7aa34302 futility: updater: Refactor function names 077026fd futility: updater: Add '--archive' to read from an archive or directory f7c44a32 futility: updater: Refactor how arguments were configured 1847ba4f futility: updater: Add --manifest to scan and print archive info a71e0ccd futility: updater: Allow patching rootkey and vblock files 2ae23df7 futility: updater: Change default model name to FWID platform name 3d615c91 futility: updater: Do not preserve GBB flags in --factory mode. 10b3d4ed futility: updater: Add '--model' and select images by system model 622c35aa futility: updater: Support white label from VPD and --signature_id c6781206 futility: updater: Support --mode=output and --output_dir fb947ae0 futility: update: Add `--host_only` argument 67d66f18 futility: updater: Support --repack and --unpack 1f489393 futility: updater: Fix model detection error BUG=chromium:882445 TEST=make futil; tests/futility/run_test_scripts.sh $(pwd)/build/futility BRANCH=None Signed-off-by: Hung-Te Lin <hungte@chromium.org> Change-Id: I6c14133f59561a422a190b17fa8ff7e79c45d953 Reviewed-on: https://chromium-review.googlesource.com/c/1317057
-rw-r--r--Makefile52
-rw-r--r--futility/cmd_update.c1928
-rw-r--r--futility/updater.c1981
-rw-r--r--futility/updater.h357
-rw-r--r--futility/updater_archive.c1034
-rw-r--r--futility/updater_quirks.c357
-rw-r--r--tests/futility/link.manifest.json7
-rwxr-xr-xtests/futility/models/link/setvars.sh14
-rwxr-xr-xtests/futility/models/peppy/setvars.sh14
-rwxr-xr-xtests/futility/models/whitetip/setvars.sh21
-rwxr-xr-xtests/futility/test_update.sh166
11 files changed, 4060 insertions, 1871 deletions
diff --git a/Makefile b/Makefile
index 5e73d36d..f64a5d4f 100644
--- a/Makefile
+++ b/Makefile
@@ -233,6 +233,14 @@ LDFLAGS += -static
PKG_CONFIG += --static
endif
+# Optional Libraries
+LIBZIP_VERSION := $(shell ${PKG_CONFIG} --modversion libzip 2>/dev/null)
+HAVE_LIBZIP := $(if ${LIBZIP_VERSION},1)
+ifneq (${HAVE_LIBZIP},)
+ CFLAGS += -DHAVE_LIBZIP $(shell ${PKG_CONFIG} --cflags libzip)
+ LIBZIP_LIBS := $(shell ${PKG_CONFIG} --libs libzip)
+endif
+
# Determine QEMU architecture needed, if any
ifeq (${ARCH},${HOST_ARCH})
# Same architecture; no need for QEMU
@@ -661,44 +669,41 @@ FUTIL_SYMLINKS = \
vbutil_key \
vbutil_keyblock
-FUTIL_STATIC_SRCS = \
- futility/futility.c \
- futility/cmd_dump_fmap.c \
- futility/cmd_gbb_utility.c \
- futility/cmd_update.c \
- futility/cmd_vbutil_firmware.c \
- futility/cmd_vbutil_key.c \
- futility/misc.c \
- futility/ryu_root_header.c
-
FUTIL_SRCS = \
- ${FUTIL_STATIC_SRCS} \
+ futility/futility.c \
+ futility/bdb_helper.c \
futility/cmd_bdb.c \
futility/cmd_create.c \
+ futility/cmd_dump_fmap.c \
futility/cmd_dump_kernel_config.c \
+ futility/cmd_gbb_utility.c \
futility/cmd_load_fmap.c \
futility/cmd_pcr.c \
futility/cmd_show.c \
futility/cmd_sign.c \
+ futility/cmd_update.c \
futility/cmd_validate_rec_mrc.c \
futility/cmd_vbutil_firmware.c \
+ futility/cmd_vbutil_firmware.c \
futility/cmd_vbutil_kernel.c \
- futility/cmd_vbutil_key.c \
futility/cmd_vbutil_keyblock.c \
- futility/file_type.c \
+ futility/cmd_vbutil_key.c \
+ futility/cmd_vbutil_key.c \
futility/file_type_bios.c \
+ futility/file_type.c \
futility/file_type_rwsig.c \
futility/file_type_usbpd1.c \
+ futility/misc.c \
+ futility/ryu_root_header.c \
+ futility/updater.c \
+ futility/updater_archive.c \
+ futility/updater_quirks.c \
futility/vb1_helper.c \
- futility/vb2_helper.c \
- futility/bdb_helper.c
+ futility/vb2_helper.c
-# List of commands built in futility and futility_s.
-FUTIL_STATIC_CMD_LIST = ${BUILD}/gen/futility_static_cmds.c
+# List of commands built in futility.
FUTIL_CMD_LIST = ${BUILD}/gen/futility_cmds.c
-FUTIL_STATIC_OBJS = ${FUTIL_STATIC_SRCS:%.c=${BUILD}/%.o} \
- ${FUTIL_STATIC_CMD_LIST:%.c=%.o}
FUTIL_OBJS = ${FUTIL_SRCS:%.c=${BUILD}/%.o} ${FUTIL_CMD_LIST:%.c=%.o}
${FUTIL_OBJS}: INCLUDES += -Ihost/lib21/include -Ifirmware/lib21/include \
@@ -1124,7 +1129,10 @@ signing_install: ${SIGNING_SCRIPTS} ${SIGNING_SCRIPTS_DEV} ${SIGNING_COMMON}
.PHONY: futil
futil: ${FUTIL_BIN}
-${FUTIL_BIN}: LDLIBS += ${CRYPTO_LIBS}
+# FUTIL_LIBS is shared by FUTIL_BIN and TEST_FUTIL_BINS.
+FUTIL_LIBS = ${CRYPTO_LIBS} ${LIBZIP_LIBS}
+
+${FUTIL_BIN}: LDLIBS += ${FUTIL_LIBS}
${FUTIL_BIN}: ${FUTIL_OBJS} ${UTILLIB} ${FWLIB20} ${UTILBDB}
@${PRINTF} " LD $(subst ${BUILD}/,,$@)\n"
${Q}${LD} -o $@ ${CFLAGS} ${LDFLAGS} $^ ${LDLIBS}
@@ -1167,7 +1175,7 @@ ${TEST_BINS}: LIBS = ${TESTLIB} ${UTILLIB}
${TEST_FUTIL_BINS}: ${FUTIL_OBJS} ${UTILLIB} ${UTILBDB}
${TEST_FUTIL_BINS}: INCLUDES += -Ifutility
${TEST_FUTIL_BINS}: OBJS += ${FUTIL_OBJS} ${UTILLIB} ${UTILBDB}
-${TEST_FUTIL_BINS}: LDLIBS += ${CRYPTO_LIBS}
+${TEST_FUTIL_BINS}: LDLIBS += ${FUTIL_LIBS}
${TEST2X_BINS}: ${FWLIB2X}
${TEST2X_BINS}: LIBS += ${FWLIB2X}
@@ -1306,9 +1314,7 @@ endif
# Generates the list of commands defined in futility by running grep in the
# source files looking for the DECLARE_FUTIL_COMMAND() macro usage.
-${FUTIL_STATIC_CMD_LIST}: ${FUTIL_STATIC_SRCS}
${FUTIL_CMD_LIST}: ${FUTIL_SRCS}
-${FUTIL_CMD_LIST} ${FUTIL_STATIC_CMD_LIST}:
@${PRINTF} " GEN $(subst ${BUILD}/,,$@)\n"
${Q}rm -f $@ $@_t $@_commands
${Q}mkdir -p ${BUILD}/gen
diff --git a/futility/cmd_update.c b/futility/cmd_update.c
index 7e57b97d..f3bb29b9 100644
--- a/futility/cmd_update.c
+++ b/futility/cmd_update.c
@@ -3,1777 +3,17 @@
* 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.
+ * The command line tool to invoke firmware updater.
*/
#include <assert.h>
-#include <ctype.h>
-#include <getopt.h>
#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
+#include <getopt.h>
-#include "2rsa.h"
-#include "crossystem.h"
-#include "fmap.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 "
-#define DEBUG(format, ...) Debug("%s: " format "\n", __FUNCTION__,##__VA_ARGS__)
-#define ERROR(format, ...) Error("%s: " format "\n", __FUNCTION__,##__VA_ARGS__)
-
-/* FMAP section names. */
-static const char * const FMAP_RO_FRID = "RO_FRID",
- * const FMAP_RO_SECTION = "RO_SECTION",
- * const FMAP_RO_GBB = "GBB",
- * const FMAP_RO_VPD = "RO_VPD",
- * const FMAP_RW_VPD = "RW_VPD",
- * const FMAP_RW_VBLOCK_A = "VBLOCK_A",
- * const FMAP_RW_SECTION_A = "RW_SECTION_A",
- * const FMAP_RW_SECTION_B = "RW_SECTION_B",
- * const FMAP_RW_FWID = "RW_FWID",
- * const FMAP_RW_FWID_A = "RW_FWID_A",
- * const FMAP_RW_FWID_B = "RW_FWID_B",
- * const FMAP_RW_SHARED = "RW_SHARED",
- * const FMAP_RW_NVRAM = "RW_NVRAM",
- * const FMAP_RW_LEGACY = "RW_LEGACY",
- * const FMAP_SI_DESC = "SI_DESC",
- * const FMAP_SI_ME = "SI_ME";
-
-/* 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_EMULATE = "dummy:emulate",
- * 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,
-};
-
-struct firmware_image {
- const char *programmer;
- char *emulation;
- uint32_t size;
- uint8_t *data;
- char *file_name;
- char *ro_version, *rw_version_a, *rw_version_b;
- FmapHeader *fmap_header;
-};
-
-struct firmware_section {
- uint8_t *data;
- size_t size;
-};
-
-struct system_property {
- int (*getter)();
- int value;
- int initialized;
-};
-
-enum system_property_type {
- SYS_PROP_MAINFW_ACT,
- SYS_PROP_TPM_FWVER,
- SYS_PROP_FW_VBOOT2,
- SYS_PROP_PLATFORM_VER,
- SYS_PROP_WP_HW,
- SYS_PROP_WP_SW,
- SYS_PROP_MAX
-};
-
-struct updater_config;
-struct quirk_entry {
- const char *name;
- const char *help;
- int (*apply)(struct updater_config *cfg);
- int value;
-};
-
-enum quirk_types {
- QUIRK_ENLARGE_IMAGE,
- QUIRK_UNLOCK_ME_FOR_UPDATE,
- QUIRK_MIN_PLATFORM_VERSION,
- QUIRK_MAX,
-};
-
-struct updater_config {
- struct firmware_image image, image_current;
- struct firmware_image ec_image, pd_image;
- struct system_property system_properties[SYS_PROP_MAX];
- struct quirk_entry quirks[QUIRK_MAX];
- int try_update;
- int force_update;
- int legacy_update;
- int emulate;
-};
-
-struct tempfile {
- char *filepath;
- struct tempfile *next;
-};
-
-static struct tempfile *tempfiles;
-
-/*
- * Helper function to create a new temporary file.
- * All files created will be removed by function remove_all_temp_files().
- * Returns the path of new file, or NULL on failure.
- */
-static const char *create_temp_file()
-{
- 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 = tempfiles;
- 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 remove_all_temp_files()
-{
- 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);
- }
-}
-
-/*
- * 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.
- */
-static 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;
-
- if (debugging_enabled)
- verbose = 1;
-
- if (!verbose)
- postfix = " >/dev/null 2>&1";
-
- if (!section_name || !*section_name) {
- dash_i = "";
- section_name = "";
- }
-
- if (strncmp(programmer, PROG_EMULATE, strlen(PROG_EMULATE)) == 0) {
- ignore_lock = "--ignore-lock";
- }
-
- 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. */
- r = asprintf(&command, "flashrom %s %s -p %s %s %s %s %s", op_cmd,
- image_path, programmer, dash_i, section_name, ignore_lock,
- postfix);
-
- if (r == -1) {
- /* `command` will be not available. */
- ERROR("Cannot allocate memory for command to execute.");
- return -1;
- }
-
- 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.
- */
-static 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))
- 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 = %d", 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. */
-static 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. */
-static void 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.
- */
-static 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.
- * Returns 0 on success, otherwise failure.
- */
-static int load_image(const char *file_name, struct firmware_image *image)
-{
- DEBUG("Load image file from %s...", file_name);
-
- if (vb2_read_file(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 and emulates system firmware by an image file.
- * This will set a emulation programmer in image->emulation so flashrom
- * can access the file as system firmware storage.
- * Returns 0 if success, non-zero if error.
- */
-static int emulate_system_image(const char *file_name,
- struct firmware_image *image)
-{
- if (load_image(file_name, image))
- return -1;
-
- if (asprintf(&image->emulation,
- "%s=VARIABLE_SIZE,image=%s,size=%u",
- PROG_EMULATE, file_name, image->size) < 0) {
- ERROR("Failed to allocate programmer buffer: %s.", file_name);
- return -1;
- }
- return 0;
-}
-
-/*
- * Loads the active system firmware image (usually from SPI flash chip).
- * Returns 0 if success, non-zero if error.
- */
-static int load_system_image(struct updater_config *cfg,
- struct firmware_image *image)
-{
- const char *tmp_file = create_temp_file();
-
- if (!tmp_file)
- return -1;
- RETURN_ON_FAILURE(host_flashrom(
- FLASHROM_READ, tmp_file, image->programmer, 0, NULL));
- return load_image(tmp_file, image);
-}
-
-/*
- * Frees the allocated resource from a firmware image object.
- */
-static void free_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);
- free(image->emulation);
- memset(image, 0, sizeof(*image));
-}
-/*
- * Reloads a firmware image from file.
- * Keeps special configuration like emulation.
- * Returns 0 on success, otherwise failure.
- */
-static int reload_image(const char *file_name, struct firmware_image *image)
-{
- char *emulation = image->emulation;
- int r;
-
- /*
- * All values except emulation and programmer will be re-constructed
- * in load_image. `programmer` is not touched in free_image so we only
- * need to keep `emulation`.
- */
- image->emulation = NULL;
- free_image(image);
- r = load_image(file_name, image);
- if (r == 0)
- image->emulation = emulation;
- return r;
-}
-
-/*
- * 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->emulate) {
- 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_image(filename, &to_image)) {
- 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 %d 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_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 = create_temp_file();
- const char *programmer = cfg->emulate ? image->emulation :
- image->programmer;
-
- if (!tmp_file)
- return -1;
-
- if (cfg->emulate) {
- printf("%s: (emulation) %s %s from %s to %s.\n",
- __FUNCTION__,
- image->emulation ? "Writing" : "Skipped writing",
- section_name ? section_name : "whole image",
- image->file_name, programmer);
-
- if (!image->emulation)
- return 0;
-
- /*
- * TODO(hungte): Extract the real target from image->emulation,
- * and allow to emulate writing with flashrom.
- */
- return emulate_write_firmware(
- cfg->image_current.file_name, 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, 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 all 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.
- */
-static 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)
- 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.
- */
-static 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.
- * Currently only GBB flags and HWID are preserved.
- * 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 len;
- uint8_t *hwid_to, *hwid_from;
- struct vb2_gbb_header *gbb_from, *gbb_to;
-
- gbb_from = find_gbb(image_from);
- gbb_to = find_gbb(image_to);
-
- if (!gbb_from || !gbb_to)
- return -1;
-
- /* 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 only GBB, VPD (RO+RW) and NVRAM sections are preserved.
- * Returns 0 if success, non-zero if error.
- */
-static int preserve_images(struct updater_config *cfg)
-{
- int errcnt = 0;
- struct firmware_image *from = &cfg->image_current, *to = &cfg->image;
- errcnt += preserve_gbb(from, to);
- 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);
- errcnt += preserve_firmware_section(from, to, FMAP_RW_NVRAM);
- 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("Error 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;
-
- if (asprintf(&cmd,
- "cbfstool '%s' print -r %s 2>/dev/null | grep -q '^%s '",
- image_file, section_name, cbfs_entry_name) < 0) {
- ERROR("Failed to allocate buffer.");
- return 0;
- }
- 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;
-
- DEBUG("Checking %s contents...", FMAP_RW_LEGACY);
-
- /* TODO(hungte): Save image_current as temp file and use it. */
- has_to = cbfs_file_exists(cfg->image.file_name, section, tag);
- has_from = cbfs_file_exists(cfg->image_current.file_name, 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 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,
- tpm_fwver = 0;
-
- 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 (get_key_versions(rw_image, FMAP_RW_VBLOCK_A, &data_key_version,
- &firmware_version) != 0)
- return -1;
-
- 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;
-}
-
-/*
- * Quirk to enlarge a firmware image to match flash size. This is needed by
- * devices using multiple SPI flash with different sizes, for example 8M and
- * 16M. The image_to will be padded with 0xFF using the size of image_from.
- * Returns 0 on success, otherwise failure.
- */
-static int quirk_enlarge_image(struct updater_config *cfg)
-{
- struct firmware_image *image_from = &cfg->image_current,
- *image_to = &cfg->image;
- const char *tmp_path;
- size_t to_write;
- FILE *fp;
-
- if (image_from->size <= image_to->size)
- return 0;
-
- tmp_path = create_temp_file();
- if (!tmp_path)
- return -1;
-
- DEBUG("Resize image from %u to %u.", image_to->size, image_from->size);
- to_write = image_from->size - image_to->size;
- vb2_write_file(tmp_path, image_to->data, image_to->size);
- fp = fopen(tmp_path, "ab");
- if (!fp) {
- ERROR("Cannot open temporary file %s.", tmp_path);
- return -1;
- }
- while (to_write-- > 0)
- fputc('\xff', fp);
- fclose(fp);
- return reload_image(tmp_path, image_to);
-}
-
-/*
- * Quirk to unlock a firmware image with SI_ME (management engine) when updating
- * so the system has a chance to make sure SI_ME won't be corrupted on next boot
- * before locking the Flash Master values in SI_DESC.
- * Returns 0 on success, otherwise failure.
- */
-static int quirk_unlock_me_for_update(struct updater_config *cfg)
-{
- struct firmware_section section;
- struct firmware_image *image_to = &cfg->image;
- const int flash_master_offset = 128;
- const uint8_t flash_master[] = {
- 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
- 0xff, 0xff
- };
-
- find_firmware_section(&section, image_to, FMAP_SI_DESC);
- if (section.size < flash_master_offset + ARRAY_SIZE(flash_master))
- return 0;
- if (memcmp(section.data + flash_master_offset, flash_master,
- ARRAY_SIZE(flash_master)) == 0) {
- DEBUG("Target ME not locked.");
- return 0;
- }
- /*
- * b/35568719: We should only update with unlocked ME and let
- * board-postinst lock it.
- */
- printf("%s: Changed Flash Master Values to unlocked.\n", __FUNCTION__);
- memcpy(section.data + flash_master_offset, flash_master,
- ARRAY_SIZE(flash_master));
- return 0;
-}
-
-/*
- * Checks and returns 0 if the platform version of current system is larger
- * or equal to given number, otherwise non-zero.
- */
-static int quirk_min_platform_version(struct updater_config *cfg)
-{
- int min_version = get_config_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg);
- int platform_version = get_system_property(SYS_PROP_PLATFORM_VER, cfg);
-
- DEBUG("Minimum required version=%d, current platform version=%d",
- min_version, platform_version);
-
- if (platform_version >= min_version)
- return 0;
- ERROR("Need platform version >= %d (current is %d). "
- "This firmware will only run on newer systems.",
- min_version, platform_version);
- return -1;
-}
-
-enum updater_error_codes {
- UPDATE_ERR_DONE,
- UPDATE_ERR_NEED_RO_UPDATE,
- UPDATE_ERR_NO_IMAGE,
- UPDATE_ERR_SYSTEM_IMAGE,
- UPDATE_ERR_INVALID_IMAGE,
- UPDATE_ERR_SET_COOKIES,
- UPDATE_ERR_WRITE_FIRMWARE,
- UPDATE_ERR_PLATFORM,
- UPDATE_ERR_TARGET,
- UPDATE_ERR_ROOT_KEY,
- UPDATE_ERR_TPM_ROLLBACK,
- UPDATE_ERR_UNKNOWN,
-};
-
-static 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);
- 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\n",
- 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->emulate)
- 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");
- preserve_images(cfg);
-
- 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.
- */
-static 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;
-
- 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_image(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 (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);
-}
-
-/*
- * Releases all loaded images in an updater configuration object.
- */
-static void unload_updater_config(struct updater_config *cfg)
-{
- int i;
- for (i = 0; i < SYS_PROP_MAX; i++) {
- cfg->system_properties[i].initialized = 0;
- cfg->system_properties[i].value = 0;
- }
- free_image(&cfg->image);
- free_image(&cfg->image_current);
- free_image(&cfg->ec_image);
- free_image(&cfg->pd_image);
- cfg->emulate = 0;
-}
/* Command line options */
static struct option const long_opts[] = {
@@ -1782,12 +22,22 @@ static struct option const long_opts[] = {
{"ec_image", 1, NULL, 'e'},
{"pd_image", 1, NULL, 'P'},
{"try", 0, NULL, 't'},
+ {"archive", 1, NULL, 'a'},
{"quirks", 1, NULL, 'f'},
{"list-quirks", 0, NULL, 'L'},
{"mode", 1, NULL, 'm'},
+ {"model", 1, NULL, 'M'},
+ {"signature_id", 1, NULL, 'G'},
+ {"manifest", 0, NULL, 'A'},
+ {"repack", 1, NULL, 'k'},
+ {"unpack", 1, NULL, 'u'},
+ {"factory", 0, NULL, 'Y'},
{"force", 0, NULL, 'F'},
+ {"programmer", 1, NULL, 'p'},
{"wp", 1, NULL, 'W'},
+ {"host_only", 0, NULL, 'H'},
{"emulate", 1, NULL, 'E'},
+ {"output_dir", 1, NULL, 'U'},
{"sys_props", 1, NULL, 'S'},
{"debug", 0, NULL, 'd'},
{"verbose", 0, NULL, 'v'},
@@ -1795,7 +45,7 @@ static struct option const long_opts[] = {
{NULL, 0, NULL, 0},
};
-static const char * const short_opts = "hi:e:tm:dv";
+static const char * const short_opts = "hi:e:ta:m:p:dv";
static void print_help(int argc, char *argv[])
{
@@ -1806,16 +56,26 @@ static void print_help(int argc, char *argv[])
"-e, --ec_image=FILE \tEC firmware image (i.e, ec.bin)\n"
" --pd_image=FILE \tPD firmware image (i.e, pd.bin)\n"
"-t, --try \tTry A/B update on reboot if possible\n"
+ "-a, --archive=PATH \tRead resources from archive\n"
+ " --manifest \tPrint out a JSON manifest and exit\n"
+ " --repack=DIR \tUpdates archive from DIR\n"
+ " --unpack=DIR \tExtracts archive to DIR\n"
+ "-p, --programmer=PRG\tChange AP (host) flashrom programmer\n"
" --quirks=LIST \tSpecify the quirks to apply\n"
" --list-quirks \tPrint all available quirks\n"
"\n"
"Legacy and compatibility options:\n"
"-m, --mode=MODE \tRun updater in given mode\n"
+ " --factory \tAlias for --mode=factory\n"
" --force \tForce update (skip checking contents)\n"
+ " --output_dir=DIR\tSpecify the target for --mode=output\n"
"\n"
"Debugging and testing options:\n"
" --wp=1|0 \tSpecify write protection status\n"
+ " --host_only \tUpdate only AP (host) firmware\n"
" --emulate=FILE \tEmulate system firmware using file\n"
+ " --model=MODEL \tOverride system model for images\n"
+ " --signature_id=S\tOverride signature ID for key files\n"
" --sys_props=LIST\tList of system properties to override\n"
"-d, --debug \tPrint debugging messages\n"
"-v, --verbose \tPrint verbose messages\n"
@@ -1825,112 +85,86 @@ static void print_help(int argc, char *argv[])
static int do_update(int argc, char *argv[])
{
- int i, r, errorcnt = 0;
- struct updater_config cfg = {
- .image = { .programmer = PROG_HOST, },
- .image_current = { .programmer = PROG_HOST, },
- .ec_image = { .programmer = PROG_EC, },
- .pd_image = { .programmer = PROG_PD, },
- .system_properties = {
- [SYS_PROP_MAINFW_ACT] = {.getter = host_get_mainfw_act},
- [SYS_PROP_TPM_FWVER] = {.getter = host_get_tpm_fwver},
- [SYS_PROP_FW_VBOOT2] = {.getter = host_get_fw_vboot2},
- [SYS_PROP_PLATFORM_VER] = {
- .getter = host_get_platform_version},
- [SYS_PROP_WP_HW] = {.getter = host_get_wp_hw},
- [SYS_PROP_WP_SW] = {.getter = host_get_wp_sw},
- },
- .quirks = {
- [QUIRK_ENLARGE_IMAGE] = {
- .name="enlarge_image",
- .help="Enlarge firmware image by flash size.",
- .apply=quirk_enlarge_image,
- },
- [QUIRK_UNLOCK_ME_FOR_UPDATE] = {
- .name="unlock_me_for_update",
- .help="b/35568719: Only lock management engine "
- "by board-postinst.",
- .apply=quirk_unlock_me_for_update,
- },
- [QUIRK_MIN_PLATFORM_VERSION] = {
- .name="min_platform_version",
- .help="Minimum compatible platform version "
- "(also known as Board ID version).",
- .apply=quirk_min_platform_version,
- },
- },
- };
+ struct updater_config *cfg;
+ struct updater_config_arguments args = {0};
+ int i, errorcnt = 0, do_update = 1;
- printf(">> Firmware updater started.\n");
+ fprintf(stderr, ">> Firmware updater started.\n");
+ cfg = updater_new_config();
+ assert(cfg);
opterr = 0;
while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
switch (i) {
case 'i':
- errorcnt += !!load_image(optarg, &cfg.image);
+ args.image = optarg;
break;
case 'e':
- errorcnt += !!load_image(optarg, &cfg.ec_image);
+ args.ec_image = optarg;
break;
case 'P':
- errorcnt += !!load_image(optarg, &cfg.pd_image);
+ args.pd_image = optarg;
break;
case 't':
- cfg.try_update = 1;
+ args.try_update = 1;
+ break;
+ case 'a':
+ args.archive = optarg;
+ break;
+ case 'k':
+ args.repack = optarg;
+ break;
+ case 'u':
+ args.unpack = optarg;
break;
case 'f':
- errorcnt += !!setup_config_quirks(optarg, &cfg);
+ args.quirks = optarg;
break;
case 'L':
- list_config_quirks(&cfg);
+ updater_list_config_quirks(cfg);
return 0;
case 'm':
- if (strcmp(optarg, "autoupdate") == 0) {
- cfg.try_update = 1;
- } else if (strcmp(optarg, "recovery") == 0) {
- cfg.try_update = 0;
- } else if (strcmp(optarg, "legacy") == 0) {
- cfg.legacy_update = 1;
- } else if (strcmp(optarg, "factory") == 0 ||
- strcmp(optarg, "factory_install") == 0) {
- cfg.try_update = 0;
- if (!is_write_protection_enabled(&cfg)) {
- errorcnt++;
- Error("Mode %s needs WP disabled.\n",
- optarg);
- }
- } else {
- errorcnt++;
- Error("Invalid mode: %s\n", optarg);
- }
+ args.mode = optarg;
+ break;
+ case 'U':
+ args.output_dir = optarg;
+ break;
+ case 'M':
+ args.model = optarg;
+ break;
+ case 'G':
+ args.signature_id = optarg;
+ break;
+ case 'A':
+ args.do_manifest = 1;
+ break;
+ case 'Y':
+ args.is_factory = 1;
break;
case 'W':
- r = strtol(optarg, NULL, 0);
- override_system_property(SYS_PROP_WP_HW, &cfg, r);
- override_system_property(SYS_PROP_WP_SW, &cfg, r);
+ args.write_protection = optarg;
+ break;
+ case 'H':
+ args.host_only = 1;
break;
case 'E':
- cfg.emulate = 1;
- errorcnt += !!emulate_system_image(
- optarg, &cfg.image_current);
- /* Both image and image_current need emulation. */
- if (!errorcnt) {
- cfg.image.emulation = strdup(
- cfg.image_current.emulation);
- }
+ args.emulation = optarg;
+ break;
+ case 'p':
+ args.programmer = optarg;
break;
case 'F':
- cfg.force_update = 1;
+ args.force_update = 1;
break;
case 'S':
- override_properties_from_list(optarg, &cfg);
+ args.sys_props = optarg;
break;
case 'v':
- /* TODO(hungte) Change to better verbosity control. */
- debugging_enabled = 1;
+ args.verbosity++;
break;
case 'd':
debugging_enabled = 1;
+ args.verbosity++;
break;
case 'h':
@@ -1955,19 +189,21 @@ static int do_update(int argc, char *argv[])
errorcnt++;
Error("Unexpected arguments.\n");
}
- if (!errorcnt) {
- int r = update_firmware(&cfg);
+ if (!errorcnt)
+ errorcnt += updater_setup_config(cfg, &args, &do_update);
+ if (!errorcnt && do_update) {
+ int r = update_firmware(cfg);
if (r != UPDATE_ERR_DONE) {
r = Min(r, UPDATE_ERR_UNKNOWN);
Error("%s\n", updater_error_messages[r]);
errorcnt++;
}
}
- printf(">> %s: Firmware updater %s.\n",
- errorcnt ? "FAILED": "DONE",
- errorcnt ? "stopped due to error" : "exited successfully");
- unload_updater_config(&cfg);
- remove_all_temp_files();
+ fprintf(stderr, ">> %s: Firmware updater %s.\n",
+ errorcnt ? "FAILED": "DONE",
+ errorcnt ? "stopped due to error" : "exited successfully");
+
+ updater_delete_config(cfg);
return !!errorcnt;
}
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);
+}
diff --git a/futility/updater.h b/futility/updater.h
new file mode 100644
index 00000000..5698f9ed
--- /dev/null
+++ b/futility/updater.h
@@ -0,0 +1,357 @@
+/*
+ * 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.
+ */
+#ifndef VBOOT_REFERENCE_FUTILITY_UPDATER_H_
+#define VBOOT_REFERENCE_FUTILITY_UPDATER_H_
+
+#include <stdio.h>
+
+#include "fmap.h"
+
+extern int debugging_enabled;
+#define DEBUG(format, ...) do { if (debugging_enabled) fprintf(stderr, \
+ "DEBUG: %s: " format "\n", __FUNCTION__, ##__VA_ARGS__); } while (0)
+#define ERROR(format, ...) fprintf(stderr, \
+ "ERROR: %s: " format "\n", __FUNCTION__, ##__VA_ARGS__)
+#define ASPRINTF(strp, ...) do { if (asprintf(strp, __VA_ARGS__) >= 0) break; \
+ ERROR("Failed to allocate memory, abort."); exit(1); } while (0)
+
+/* FMAP section names. */
+static const char * const FMAP_RO_FRID = "RO_FRID",
+ * const FMAP_RO_SECTION = "RO_SECTION",
+ * const FMAP_RO_GBB = "GBB",
+ * const FMAP_RO_PRESERVE = "RO_PRESERVE",
+ * const FMAP_RO_VPD = "RO_VPD",
+ * const FMAP_RW_VPD = "RW_VPD",
+ * const FMAP_RW_VBLOCK_A = "VBLOCK_A",
+ * const FMAP_RW_VBLOCK_B = "VBLOCK_B",
+ * const FMAP_RW_SECTION_A = "RW_SECTION_A",
+ * const FMAP_RW_SECTION_B = "RW_SECTION_B",
+ * const FMAP_RW_FWID = "RW_FWID",
+ * const FMAP_RW_FWID_A = "RW_FWID_A",
+ * const FMAP_RW_FWID_B = "RW_FWID_B",
+ * const FMAP_RW_SHARED = "RW_SHARED",
+ * const FMAP_RW_NVRAM = "RW_NVRAM",
+ * const FMAP_RW_ELOG = "RW_ELOG",
+ * const FMAP_RW_PRESERVE = "RW_PRESERVE",
+ * const FMAP_RW_LEGACY = "RW_LEGACY",
+ * const FMAP_RW_SMMSTORE = "SMMSTORE",
+ * const FMAP_SI_DESC = "SI_DESC",
+ * const FMAP_SI_ME = "SI_ME";
+
+struct firmware_image {
+ const char *programmer;
+ uint32_t size;
+ uint8_t *data;
+ char *file_name;
+ char *ro_version, *rw_version_a, *rw_version_b;
+ FmapHeader *fmap_header;
+};
+
+struct firmware_section {
+ uint8_t *data;
+ size_t size;
+};
+
+struct system_property {
+ int (*getter)();
+ int value;
+ int initialized;
+};
+
+enum system_property_type {
+ SYS_PROP_MAINFW_ACT,
+ SYS_PROP_TPM_FWVER,
+ SYS_PROP_FW_VBOOT2,
+ SYS_PROP_PLATFORM_VER,
+ SYS_PROP_WP_HW,
+ SYS_PROP_WP_SW,
+ SYS_PROP_MAX
+};
+
+struct updater_config;
+struct quirk_entry {
+ const char *name;
+ const char *help;
+ int (*apply)(struct updater_config *cfg);
+ int value;
+};
+
+enum quirk_types {
+ QUIRK_ENLARGE_IMAGE,
+ QUIRK_MIN_PLATFORM_VERSION,
+ QUIRK_UNLOCK_ME_FOR_UPDATE,
+ QUIRK_DAISY_SNOW_DUAL_MODEL,
+ QUIRK_EVE_SMM_STORE,
+ QUIRK_MAX,
+};
+
+struct tempfile {
+ char *filepath;
+ struct tempfile *next;
+};
+
+struct archive;
+struct updater_config {
+ struct firmware_image image, image_current;
+ struct firmware_image ec_image, pd_image;
+ struct system_property system_properties[SYS_PROP_MAX];
+ struct quirk_entry quirks[QUIRK_MAX];
+ struct archive *archive;
+ struct tempfile *tempfiles;
+ int try_update;
+ int force_update;
+ int legacy_update;
+ int factory_update;
+ int verbosity;
+ const char *emulation;
+};
+
+struct updater_config_arguments {
+ char *image, *ec_image, *pd_image;
+ char *archive, *quirks, *mode;
+ char *programmer, *model, *signature_id;
+ char *emulation, *sys_props, *write_protection;
+ char *output_dir;
+ char *repack, *unpack;
+ int is_factory, try_update, force_update, do_manifest, host_only;
+ int verbosity;
+};
+
+struct patch_config {
+ char *rootkey;
+ char *vblock_a;
+ char *vblock_b;
+};
+
+struct model_config {
+ char *name;
+ char *image, *ec_image, *pd_image;
+ struct patch_config patches;
+ char *signature_id;
+ int is_white_label;
+};
+
+struct manifest {
+ int num;
+ struct model_config *models;
+ struct archive *archive;
+ int default_model;
+ int has_keyset;
+};
+
+enum updater_error_codes {
+ UPDATE_ERR_DONE,
+ UPDATE_ERR_NEED_RO_UPDATE,
+ UPDATE_ERR_NO_IMAGE,
+ UPDATE_ERR_SYSTEM_IMAGE,
+ UPDATE_ERR_INVALID_IMAGE,
+ UPDATE_ERR_SET_COOKIES,
+ UPDATE_ERR_WRITE_FIRMWARE,
+ UPDATE_ERR_PLATFORM,
+ UPDATE_ERR_TARGET,
+ UPDATE_ERR_ROOT_KEY,
+ UPDATE_ERR_TPM_ROLLBACK,
+ UPDATE_ERR_UNKNOWN,
+};
+
+/* Messages explaining enum updater_error_codes. */
+extern const char * const updater_error_messages[];
+
+struct updater_config;
+
+/*
+ * 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);
+
+/*
+ * 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();
+
+/*
+ * Releases all resources in an updater configuration object.
+ */
+void updater_delete_config(struct updater_config *cfg);
+
+/*
+ * 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);
+
+/* Prints the name and description from all supported quirks. */
+void updater_list_config_quirks(const struct updater_config *cfg);
+
+/*
+ * Registers known quirks to a updater_config object.
+ */
+void updater_register_quirks(struct updater_config *cfg);
+
+/*
+ * Helper function to create a new temporary file within updater's life cycle.
+ * Returns the path of new file, or NULL on failure.
+ */
+const char *updater_create_temp_file(struct updater_config *cfg);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/*
+ * 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);
+
+/* Frees the allocated resource from a firmware image object. */
+void free_firmware_image(struct firmware_image *image);
+
+/* Gets the value (setting) of specified quirks from updater configuration. */
+int get_config_quirk(enum quirk_types quirk, const struct updater_config *cfg);
+
+/* Gets the system property by given type. Returns the property value. */
+int get_system_property(enum system_property_type property_type,
+ struct updater_config *cfg);
+/*
+ * Gets the default quirk config string for target image.
+ * Returns a string (in same format as --quirks) to load or NULL if no quirks.
+ */
+const char * const updater_get_default_quirks(struct updater_config *cfg);
+
+/*
+ * Finds the GBB (Google Binary Block) header on a given firmware image.
+ * Returns a pointer to valid GBB header, or NULL on not found.
+ */
+struct vb2_gbb_header;
+const struct vb2_gbb_header *find_gbb(const struct firmware_image *image);
+
+/*
+ * 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);
+
+/* Functions from updater_archive.c */
+
+/*
+ * Opens an archive from given path.
+ * The type of archive will be determined automatically.
+ * Returns a pointer to reference to archive (must be released by archive_close
+ * when not used), otherwise NULL on error.
+ */
+struct archive *archive_open(const char *path);
+
+/*
+ * Closes an archive reference.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_close(struct archive *ar);
+
+/*
+ * Checks if an entry (either file or directory) exists in archive.
+ * Returns 1 if exists, otherwise 0
+ */
+int archive_has_entry(struct archive *ar, const char *name);
+
+/*
+ * Reads a file from archive.
+ * Returns 0 on success (data and size reflects the file content),
+ * otherwise non-zero as failure.
+ */
+int archive_read_file(struct archive *ar, const char *fname,
+ uint8_t **data, uint32_t *size);
+
+/*
+ * Writes a file into archive.
+ * If entry name (fname) is an absolute path (/file), always write into real
+ * file system.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_write_file(struct archive *ar, const char *fname,
+ uint8_t *data, uint32_t size);
+
+/*
+ * Copies all entries from one archive to another.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_copy(struct archive *from, struct archive *to);
+
+/*
+ * Creates a new manifest object by scanning files in archive.
+ * Returns the manifest on success, otherwise NULL for failure.
+ */
+struct manifest *new_manifest_from_archive(struct archive *archive);
+
+/* Releases all resources allocated by given manifest object. */
+void delete_manifest(struct manifest *manifest);
+
+/* Prints the information of objects in manifest (models and images) in JSON. */
+void print_json_manifest(const struct manifest *manifest);
+
+/*
+ * Modifies a firmware image from patch information specified in model config.
+ * Returns 0 on success, otherwise number of failures.
+ */
+int patch_image_by_model(
+ struct firmware_image *image, const struct model_config *model,
+ struct archive *archive);
+
+/*
+ * Finds the existing model_config from manifest that best matches current
+ * system (as defined by model_name).
+ * Returns a model_config from manifest, or NULL if not found.
+ */
+const struct model_config *manifest_find_model(const struct manifest *manifest,
+ const char *model_name);
+
+/*
+ * Applies white label information to an existing model configuration.
+ * Collects signature ID information from either parameter signature_id or
+ * image file (via VPD) and updates model.patches for key files.
+ * Returns 0 on success, otherwise failure.
+ */
+int model_apply_white_label(
+ struct model_config *model,
+ struct archive *archive,
+ const char *signature_id,
+ const char *image);
+
+#endif /* VBOOT_REFERENCE_FUTILITY_UPDATER_H_ */
diff --git a/futility/updater_archive.c b/futility/updater_archive.c
new file mode 100644
index 00000000..1c8030b7
--- /dev/null
+++ b/futility/updater_archive.c
@@ -0,0 +1,1034 @@
+/*
+ * 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.
+ *
+ * Accessing updater resources from an archive.
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <fts.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBZIP
+#include <zip.h>
+#endif
+
+#include "host_misc.h"
+#include "updater.h"
+#include "util_misc.h"
+#include "vb2_common.h"
+
+/*
+ * A firmware update package (archive) is a file packed by either shar(1) or
+ * zip(1). See https://chromium.googlesource.com/chromiumos/platform/firmware/
+ * for more information.
+ *
+ * A package for single board (i.e., not Unified Build) will have all the image
+ * files in top folder:
+ * - host: 'bios.bin'
+ * - ec: 'ec.bin'
+ * - pd: 'pd.bin'
+ * If white label is supported, a 'keyset/' folder will be available, with key
+ * files in it:
+ * - rootkey.$WLTAG
+ * - vblock_A.$WLTAG
+ * - vblock_B.$WLTAG
+ * The $WLTAG should come from VPD value 'whitelabel_tag', or the
+ * 'customization_id'. Note 'customization_id' is in format LOEM[-VARIANT] and
+ * we can only take LOEM as $WLTAG, for example A-B => $WLTAG=A.
+ *
+ * A package for Unified Build is more complicated. There will be a models/
+ * folder, and each model (by $(mosys platform model) ) should appear as a sub
+ * folder, with a 'setvars.sh' file inside. The 'setvars.sh' is a shell script
+ * describing what files should be used and the signature ID ($SIGID) to use.
+ *
+ * Similar to write label in non-Unified-Build, the keys and vblock files will
+ * be in 'keyset/' folder:
+ * - rootkey.$SIGID
+ * - vblock_A.$SIGID
+ * - vblock_B.$SIGID
+ * If $SIGID starts with 'sig-id-in-*' then we have to replace it by VPD value
+ * 'whitelabel_tag' as '$MODEL-$WLTAG'.
+ */
+
+static const char * const SETVARS_IMAGE_MAIN = "IMAGE_MAIN",
+ * const SETVARS_IMAGE_EC = "IMAGE_EC",
+ * const SETVARS_IMAGE_PD = "IMAGE_PD",
+ * const SETVARS_SIGNATURE_ID = "SIGNATURE_ID",
+ * const SIG_ID_IN_VPD_PREFIX = "sig-id-in",
+ * const DIR_KEYSET = "keyset",
+ * const DIR_MODELS = "models",
+ * const DEFAULT_MODEL_NAME = "default",
+ * const VPD_WHITELABEL_TAG = "whitelabel_tag",
+ * const VPD_CUSTOMIZATION_ID = "customization_id",
+ * const ENV_VAR_MODEL_DIR = "${MODEL_DIR}",
+ * const PATH_STARTSWITH_KEYSET = "keyset/",
+ * const PATH_ENDSWITH_SERVARS = "/setvars.sh";
+
+struct archive {
+ void *handle;
+
+ void * (*open)(const char *name);
+ int (*close)(void *handle);
+
+ int (*walk)(void *handle, void *arg,
+ int (*callback)(const char *path, void *arg));
+ int (*has_entry)(void *handle, const char *name);
+ int (*read_file)(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size);
+ int (*write_file)(void *handle, const char *fname,
+ uint8_t *data, uint32_t size);
+};
+
+/*
+ * -- Begin of archive implementations --
+ */
+
+/* Callback for archive_open on a general file system. */
+static void *archive_fallback_open(const char *name)
+{
+ assert(name && *name);
+ return strdup(name);
+}
+
+/* Callback for archive_close on a general file system. */
+static int archive_fallback_close(void *handle)
+{
+ free(handle);
+ return 0;
+}
+
+/* Callback for archive_walk on a general file system. */
+static int archive_fallback_walk(
+ void *handle, void *arg,
+ int (*callback)(const char *path, void *arg))
+{
+ FTS *fts_handle;
+ FTSENT *ent;
+ char *fts_argv[2] = {};
+ char default_path[] = ".";
+ char *root = default_path;
+ size_t root_len;
+
+ if (handle)
+ root = (char *)handle;
+ root_len = strlen(root);
+ fts_argv[0] = root;
+
+ fts_handle = fts_open(fts_argv, FTS_NOCHDIR, NULL);
+ if (!fts_handle)
+ return -1;
+
+ while ((ent = fts_read(fts_handle)) != NULL) {
+ char *path = ent->fts_path + root_len;
+ if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
+ continue;
+ while (*path == '/')
+ path++;
+ if (!*path)
+ continue;
+ if (callback(path, arg))
+ break;
+ }
+ return 0;
+}
+
+/* Callback for fallback drivers to get full path easily. */
+static const char *archive_fallback_get_path(void *handle, const char *fname,
+ char **temp_path)
+{
+ if (handle && *fname != '/') {
+ ASPRINTF(temp_path, "%s/%s", (char *)handle, fname);
+ return *temp_path;
+ }
+ return fname;
+}
+
+/* Callback for archive_has_entry on a general file system. */
+static int archive_fallback_has_entry(void *handle, const char *fname)
+{
+ int r;
+ char *temp_path = NULL;
+ const char *path = archive_fallback_get_path(handle, fname, &temp_path);
+
+ DEBUG("Checking %s", path);
+ r = access(path, R_OK);
+ free(temp_path);
+ return r == 0;
+}
+
+/* Callback for archive_read_file on a general file system. */
+static int archive_fallback_read_file(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ int r;
+ char *temp_path = NULL;
+ const char *path = archive_fallback_get_path(handle, fname, &temp_path);
+
+ DEBUG("Reading %s", path);
+ *data = NULL;
+ *size = 0;
+ r = vb2_read_file(path, data, size) != VB2_SUCCESS;
+ free(temp_path);
+ return r;
+}
+
+/* Callback for archive_write_file on a general file system. */
+static int archive_fallback_write_file(void *handle, const char *fname,
+ uint8_t *data, uint32_t size)
+{
+ int r;
+ char *temp_path = NULL;
+ const char *path = archive_fallback_get_path(handle, fname, &temp_path);
+
+ DEBUG("Writing %s", path);
+ if (strchr(path, '/')) {
+ char *dirname = strdup(path);
+ *strrchr(dirname, '/') = '\0';
+ /* TODO(hungte): call mkdir(2) instead of shell invocation. */
+ if (access(dirname, W_OK) != 0) {
+ char *command;
+ ASPRINTF(&command, "mkdir -p %s", dirname);
+ free(host_shell(command));
+ free(command);
+ }
+ free(dirname);
+ }
+ r = vb2_write_file(path, data, size) != VB2_SUCCESS;
+ free(temp_path);
+ return r;
+}
+
+#ifdef HAVE_LIBZIP
+
+/* Callback for archive_open on a ZIP file. */
+static void *archive_zip_open(const char *name)
+{
+ return zip_open(name, 0, NULL);
+}
+
+/* Callback for archive_close on a ZIP file. */
+static int archive_zip_close(void *handle)
+{
+ struct zip *zip = (struct zip *)handle;
+
+ if (zip)
+ return zip_close(zip);
+ return 0;
+}
+
+/* Callback for archive_has_entry on a ZIP file. */
+static int archive_zip_has_entry(void *handle, const char *fname)
+{
+ struct zip *zip = (struct zip *)handle;
+ assert(zip);
+ return zip_name_locate(zip, fname, 0) != -1;
+}
+
+/* Callback for archive_walk on a ZIP file. */
+static int archive_zip_walk(
+ void *handle, void *arg,
+ int (*callback)(const char *name, void *arg))
+{
+ zip_int64_t num, i;
+ struct zip *zip = (struct zip *)handle;
+ assert(zip);
+
+ num = zip_get_num_entries(zip, 0);
+ if (num < 0)
+ return 1;
+ for (i = 0; i < num; i++) {
+ const char *name = zip_get_name(zip, i, 0);
+ if (*name && name[strlen(name) - 1] == '/')
+ continue;
+ if (callback(name, arg))
+ break;
+ }
+ return 0;
+}
+
+/* Callback for archive_zip_read_file on a ZIP file. */
+static int archive_zip_read_file(void *handle, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ struct zip *zip = (struct zip *)handle;
+ struct zip_file *fp;
+ struct zip_stat stat;
+
+ assert(zip);
+ *data = NULL;
+ *size = 0;
+ zip_stat_init(&stat);
+ if (zip_stat(zip, fname, 0, &stat)) {
+ ERROR("Fail to stat entry in ZIP: %s", fname);
+ return 1;
+ }
+ fp = zip_fopen(zip, fname, 0);
+ if (!fp) {
+ ERROR("Failed to open entry in ZIP: %s", fname);
+ return 1;
+ }
+ *data = (uint8_t *)malloc(stat.size);
+ if (*data) {
+ if (zip_fread(fp, *data, stat.size) == stat.size) {
+ *size = stat.size;
+ } else {
+ ERROR("Failed to read entry in zip: %s", fname);
+ free(*data);
+ *data = NULL;
+ }
+ }
+ zip_fclose(fp);
+ return *data == NULL;
+}
+
+/* Callback for archive_zip_write_file on a ZIP file. */
+static int archive_zip_write_file(void *handle, const char *fname,
+ uint8_t *data, uint32_t size)
+{
+ struct zip *zip = (struct zip *)handle;
+ struct zip_source *src;
+
+ DEBUG("Writing %s", fname);
+ assert(zip);
+ src = zip_source_buffer(zip, data, size, 0);
+ if (!src) {
+ ERROR("Internal error: cannot allocate buffer: %s", fname);
+ return 1;
+ }
+
+ if (zip_file_add(zip, fname, src, ZIP_FL_OVERWRITE) < 0) {
+ zip_source_free(src);
+ ERROR("Internal error: failed to add: %s", fname);
+ return 1;
+ }
+ /* zip_source_free is not needed if zip_file_add success. */
+#if LIBZIP_VERSION_MAJOR >= 1
+ zip_file_set_mtime(zip, zip_name_locate(zip, fname, 0), 0, 0);
+#endif
+ return 0;
+}
+#endif
+
+/*
+ * Opens an archive from given path.
+ * The type of archive will be determined automatically.
+ * Returns a pointer to reference to archive (must be released by archive_close
+ * when not used), otherwise NULL on error.
+ */
+struct archive *archive_open(const char *path)
+{
+ struct stat path_stat;
+ struct archive *ar;
+
+ if (stat(path, &path_stat) != 0) {
+ ERROR("Cannot identify type of path: %s", path);
+ return NULL;
+ }
+
+ ar = (struct archive *)malloc(sizeof(*ar));
+ if (!ar) {
+ ERROR("Internal error: allocation failure.");
+ return NULL;
+ }
+
+ if (S_ISDIR(path_stat.st_mode)) {
+ DEBUG("Found directory, use fallback (fs) driver: %s", path);
+ /* Regular file system. */
+ ar->open = archive_fallback_open;
+ ar->close = archive_fallback_close;
+ ar->walk = archive_fallback_walk;
+ ar->has_entry = archive_fallback_has_entry;
+ ar->read_file = archive_fallback_read_file;
+ ar->write_file = archive_fallback_write_file;
+ } else {
+#ifdef HAVE_LIBZIP
+ DEBUG("Found file, use ZIP driver: %s", path);
+ ar->open = archive_zip_open;
+ ar->close = archive_zip_close;
+ ar->walk = archive_zip_walk;
+ ar->has_entry = archive_zip_has_entry;
+ ar->read_file = archive_zip_read_file;
+ ar->write_file = archive_zip_write_file;
+#else
+ ERROR("Found file, but no drivers were enabled: %s", path);
+ free(ar);
+ return NULL;
+#endif
+ }
+ ar->handle = ar->open(path);
+ if (!ar->handle) {
+ ERROR("Failed to open archive: %s", path);
+ free(ar);
+ return NULL;
+ }
+ return ar;
+}
+
+/*
+ * Closes an archive reference.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_close(struct archive *ar)
+{
+ int r = ar->close(ar->handle);
+ free(ar);
+ return r;
+}
+
+/*
+ * Checks if an entry (either file or directory) exists in archive.
+ * If entry name (fname) is an absolute path (/file), always check
+ * with real file system.
+ * Returns 1 if exists, otherwise 0
+ */
+int archive_has_entry(struct archive *ar, const char *name)
+{
+ if (!ar || *name == '/')
+ return archive_fallback_has_entry(NULL, name);
+ return ar->has_entry(ar->handle, name);
+}
+
+/*
+ * Traverses all files within archive (directories are ignored).
+ * For every entry, the path (relative the archive root) will be passed to
+ * callback function, until the callback returns non-zero.
+ * The arg argument will also be passed to callback.
+ * Returns 0 on success otherwise non-zero as failure.
+ */
+int archive_walk(
+ struct archive *ar, void *arg,
+ int (*callback)(const char *path, void *arg))
+{
+ if (!ar)
+ return archive_fallback_walk(NULL, arg, callback);
+ return ar->walk(ar->handle, arg, callback);
+}
+
+/*
+ * Reads a file from archive.
+ * If entry name (fname) is an absolute path (/file), always read
+ * from real file system.
+ * Returns 0 on success (data and size reflects the file content),
+ * otherwise non-zero as failure.
+ */
+int archive_read_file(struct archive *ar, const char *fname,
+ uint8_t **data, uint32_t *size)
+{
+ if (!ar || *fname == '/')
+ return archive_fallback_read_file(NULL, fname, data, size);
+ return ar->read_file(ar->handle, fname, data, size);
+}
+
+/*
+ * Writes a file into archive.
+ * If entry name (fname) is an absolute path (/file), always write into real
+ * file system.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_write_file(struct archive *ar, const char *fname,
+ uint8_t *data, uint32_t size)
+{
+ if (!ar || *fname == '/')
+ return archive_fallback_write_file(NULL, fname, data, size);
+ return ar->write_file(ar->handle, fname, data, size);
+}
+
+struct _copy_arg {
+ struct archive *from, *to;
+};
+
+/* Callback for archive_copy. */
+static int archive_copy_callback(const char *path, void *_arg)
+{
+ const struct _copy_arg *arg = (const struct _copy_arg*)_arg;
+ uint32_t size;
+ uint8_t *data;
+ int r;
+
+ printf("Copying: %s\n", path);
+ if (archive_read_file(arg->from, path, &data, &size)) {
+ ERROR("Failed reading: %s", path);
+ return 1;
+ }
+ r = archive_write_file(arg->to, path, data, size);
+ DEBUG("result=%d", r);
+ free(data);
+ return r;
+}
+
+/*
+ * Copies all entries from one archive to another.
+ * Returns 0 on success, otherwise non-zero as failure.
+ */
+int archive_copy(struct archive *from, struct archive *to)
+{
+ struct _copy_arg arg = { .from = from, .to = to };
+ return archive_walk(from, &arg, archive_copy_callback);
+}
+
+/*
+ * -- End of archive implementations --
+ */
+
+/* Utility function to convert a string. */
+static void str_convert(char *s, int (*convert)(int c))
+{
+ int c;
+
+ for (; *s; s++) {
+ c = *s;
+ if (!isascii(c))
+ continue;
+ *s = convert(c);
+ }
+}
+
+/* Returns 1 if name ends by given pattern, otherwise 0. */
+static int str_endswith(const char *name, const char *pattern)
+{
+ size_t name_len = strlen(name), pattern_len = strlen(pattern);
+ if (name_len < pattern_len)
+ return 0;
+ return strcmp(name + name_len - pattern_len, pattern) == 0;
+}
+
+/* Returns 1 if name starts by given pattern, otherwise 0. */
+static int str_startswith(const char *name, const char *pattern)
+{
+ return strncmp(name, pattern, strlen(pattern)) == 0;
+}
+
+/* Returns the VPD value by given key name, or NULL on error (or no value). */
+static char *vpd_get_value(const char *fpath, const char *key)
+{
+ char *command, *result;
+
+ assert(fpath);
+ ASPRINTF(&command, "vpd -g %s -f %s 2>/dev/null", key, fpath);
+ result = host_shell(command);
+ free(command);
+
+ if (result && !*result) {
+ free(result);
+ result = NULL;
+ }
+ return result;
+}
+
+/*
+ * Reads and parses a setvars type file from archive, then stores into config.
+ * Returns 0 on success (at least one entry found), otherwise failure.
+ */
+static int model_config_parse_setvars_file(
+ struct model_config *cfg, struct archive *archive,
+ const char *fpath)
+{
+ uint8_t *data;
+ uint32_t len;
+
+ char *ptr_line, *ptr_token;
+ char *line, *k, *v;
+ int valid = 0;
+
+ if (archive_read_file(archive, fpath, &data, &len) != 0) {
+ ERROR("Failed reading: %s", fpath);
+ return -1;
+ }
+
+ /* Valid content should end with \n, or \"; ensure ASCIIZ for parsing */
+ if (len)
+ data[len - 1] = '\0';
+
+ for (line = strtok_r((char *)data, "\n\r", &ptr_line); line;
+ line = strtok_r(NULL, "\n\r", &ptr_line)) {
+ char *expand_path = NULL;
+ int found_valid = 1;
+
+ /* Format: KEY="value" */
+ k = strtok_r(line, "=", &ptr_token);
+ if (!k)
+ continue;
+ v = strtok_r(NULL, "\"", &ptr_token);
+ if (!v)
+ continue;
+
+ /* Some legacy updaters may be still using ${MODEL_DIR}. */
+ if (str_startswith(v, ENV_VAR_MODEL_DIR)) {
+ ASPRINTF(&expand_path, "%s/%s%s", DIR_MODELS, cfg->name,
+ v + strlen(ENV_VAR_MODEL_DIR));
+ }
+
+ if (strcmp(k, SETVARS_IMAGE_MAIN) == 0)
+ cfg->image = strdup(v);
+ else if (strcmp(k, SETVARS_IMAGE_EC) == 0)
+ cfg->ec_image = strdup(v);
+ else if (strcmp(k, SETVARS_IMAGE_PD) == 0)
+ cfg->pd_image = strdup(v);
+ else if (strcmp(k, SETVARS_SIGNATURE_ID) == 0) {
+ cfg->signature_id = strdup(v);
+ if (str_startswith(v, SIG_ID_IN_VPD_PREFIX))
+ cfg->is_white_label = 1;
+ } else
+ found_valid = 0;
+ free(expand_path);
+ valid += found_valid;
+ }
+ free(data);
+ return valid == 0;
+}
+
+/*
+ * Changes the rootkey in firmware GBB to given new key.
+ * Returns 0 on success, otherwise failure.
+ */
+static int change_gbb_rootkey(struct firmware_image *image,
+ const char *section_name,
+ const uint8_t *rootkey, uint32_t rootkey_len)
+{
+ const struct vb2_gbb_header *gbb = find_gbb(image);
+ uint8_t *gbb_rootkey;
+ if (!gbb) {
+ ERROR("Cannot find GBB in image %s.", image->file_name);
+ return -1;
+ }
+ if (gbb->rootkey_size < rootkey_len) {
+ ERROR("New root key (%u bytes) larger than GBB (%u bytes).",
+ rootkey_len, gbb->rootkey_size);
+ return -1;
+ }
+
+ gbb_rootkey = (uint8_t *)gbb + gbb->rootkey_offset;
+ /* See cmd_gbb_utility: root key must be first cleared with zero. */
+ memset(gbb_rootkey, 0, gbb->rootkey_size);
+ memcpy(gbb_rootkey, rootkey, rootkey_len);
+ return 0;
+}
+
+/*
+ * Changes the VBlock in firmware section to new data.
+ * Returns 0 on success, otherwise failure.
+ */
+static int change_vblock(struct firmware_image *image, const char *section_name,
+ const uint8_t *vblock, uint32_t vblock_len)
+{
+ struct firmware_section section;
+
+ find_firmware_section(&section, image, section_name);
+ if (!section.data) {
+ ERROR("Need section %s in image %s.", section_name,
+ image->file_name);
+ return -1;
+ }
+ if (section.size < vblock_len) {
+ ERROR("Section %s too small (%zu bytes) for vblock (%u bytes).",
+ section_name, section.size, vblock_len);
+ return -1;
+ }
+ memcpy(section.data, vblock, vblock_len);
+ return 0;
+}
+
+/*
+ * Applies a key file to firmware image.
+ * Returns 0 on success, otherwise failure.
+ */
+static int apply_key_file(
+ struct firmware_image *image, const char *path,
+ struct archive *archive, const char *section_name,
+ int (*apply)(struct firmware_image *image, const char *section,
+ const uint8_t *data, uint32_t len))
+{
+ int r = 0;
+ uint8_t *data = NULL;
+ uint32_t len;
+
+ r = archive_read_file(archive, path, &data, &len);
+ if (r == 0) {
+ DEBUG("Loaded file: %s", path);
+ r = apply(image, section_name, data, len);
+ if (r)
+ ERROR("Failed applying %s to %s", path, section_name);
+ } else {
+ ERROR("Failed reading: %s", path);
+ }
+ free(data);
+ return r;
+}
+
+/*
+ * Modifies a firmware image from patch information specified in model config.
+ * Returns 0 on success, otherwise number of failures.
+ */
+int patch_image_by_model(
+ struct firmware_image *image, const struct model_config *model,
+ struct archive *archive)
+{
+ int err = 0;
+ if (model->patches.rootkey)
+ err += !!apply_key_file(
+ image, model->patches.rootkey, archive,
+ FMAP_RO_GBB, change_gbb_rootkey);
+ if (model->patches.vblock_a)
+ err += !!apply_key_file(
+ image, model->patches.vblock_a, archive,
+ FMAP_RW_VBLOCK_A, change_vblock);
+ if (model->patches.vblock_b)
+ err += !!apply_key_file(
+ image, model->patches.vblock_b, archive,
+ FMAP_RW_VBLOCK_B, change_vblock);
+ return err;
+}
+
+/*
+ * Finds available patch files by given model.
+ * Updates `model` argument with path of patch files.
+ */
+static void find_patches_for_model(struct model_config *model,
+ struct archive *archive,
+ const char *signature_id)
+{
+ char *path;
+ int i;
+
+ const char *names[] = {
+ "rootkey",
+ "vblock_A",
+ "vblock_B",
+ };
+
+ char **targets[] = {
+ &model->patches.rootkey,
+ &model->patches.vblock_a,
+ &model->patches.vblock_b,
+ };
+
+ assert(ARRAY_SIZE(names) == ARRAY_SIZE(targets));
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ ASPRINTF(&path, "%s/%s.%s", DIR_KEYSET, names[i], signature_id);
+ if (archive_has_entry(archive, path))
+ *targets[i] = path;
+ else
+ free(path);
+ }
+}
+
+/*
+ * Adds and copies one new model config to the existing list of given manifest.
+ * Returns a pointer to the newly allocated config, or NULL on failure.
+ */
+static struct model_config *manifest_add_model(
+ struct manifest *manifest,
+ const struct model_config *cfg)
+{
+ struct model_config *model;
+ manifest->num++;
+ manifest->models = (struct model_config *)realloc(
+ manifest->models, manifest->num * sizeof(*model));
+ if (!manifest->models) {
+ ERROR("Internal error: failed to allocate buffer.");
+ return NULL;
+ }
+ model = &manifest->models[manifest->num - 1];
+ memcpy(model, cfg, sizeof(*model));
+ return model;
+}
+
+/*
+ * A callback function for manifest to scan files in archive.
+ * Returns 0 to keep scanning, or non-zero to stop.
+ */
+static int manifest_scan_entries(const char *name, void *arg)
+{
+ struct manifest *manifest = (struct manifest *)arg;
+ struct archive *archive = manifest->archive;
+ struct model_config model = {0};
+ char *slash;
+
+ if (str_startswith(name, PATH_STARTSWITH_KEYSET))
+ manifest->has_keyset = 1;
+ if (!str_endswith(name, PATH_ENDSWITH_SERVARS))
+ return 0;
+
+ /* name: models/$MODEL/setvars.sh */
+ model.name = strdup(strchr(name, '/') + 1);
+ slash = strchr(model.name, '/');
+ if (slash)
+ *slash = '\0';
+
+ DEBUG("Found model <%s> setvars: %s", model.name, name);
+ if (model_config_parse_setvars_file(&model, archive, name)) {
+ ERROR("Invalid setvars file: %s", name);
+ return 0;
+ }
+
+ /* In legacy setvars.sh, the ec_image and pd_image may not exist. */
+ if (model.ec_image && !archive_has_entry(archive, model.ec_image)) {
+ DEBUG("Ignore non-exist EC image: %s", model.ec_image);
+ free(model.ec_image);
+ model.ec_image = NULL;
+ }
+ if (model.pd_image && !archive_has_entry(archive, model.pd_image)) {
+ DEBUG("Ignore non-exist PD image: %s", model.pd_image);
+ free(model.pd_image);
+ model.pd_image = NULL;
+ }
+
+ /* Find patch files. */
+ if (model.signature_id)
+ find_patches_for_model(&model, archive, model.signature_id);
+
+ return !manifest_add_model(manifest, &model);
+}
+
+/*
+ * Finds the existing model_config from manifest that best matches current
+ * system (as defined by model_name).
+ * Returns a model_config from manifest, or NULL if not found.
+ */
+const struct model_config *manifest_find_model(const struct manifest *manifest,
+ const char *model_name)
+{
+ char *sys_model_name = NULL;
+ const struct model_config *model = NULL;
+ int i;
+
+ /* Match if the manifest has only one package without signature. */
+ if (manifest->num == 1 && !manifest->models[0].signature_id)
+ model = &manifest->models[0];
+
+ if (!model && !model_name) {
+ sys_model_name = host_shell("mosys platform model");
+ DEBUG("System model name: '%s'", sys_model_name);
+ model_name = sys_model_name;
+ }
+
+ for (i = 0; !model && i < manifest->num; i++) {
+ if (strcmp(model_name, manifest->models[i].name) == 0)
+ model = &manifest->models[i];
+ }
+ if (!model)
+ ERROR("Model '%s' is not defined in manifest.", model_name);
+
+ free(sys_model_name);
+ return model;
+}
+
+/*
+ * Applies white label information to an existing model configuration.
+ * Collects signature ID information from either parameter signature_id or
+ * image file (via VPD) and updates model.patches for key files.
+ * Returns 0 on success, otherwise failure.
+ */
+int model_apply_white_label(
+ struct model_config *model,
+ struct archive *archive,
+ const char *signature_id,
+ const char *image)
+{
+ char *sig_id = NULL;
+ int r = 0;
+
+ if (!signature_id) {
+ int remove_dash = 0, prefix_model = model->signature_id ? 1 : 0;
+ char *wl_tag = vpd_get_value(image, VPD_WHITELABEL_TAG);
+
+ if (!wl_tag) {
+ if (model->signature_id)
+ return -1;
+ wl_tag = vpd_get_value(image, VPD_CUSTOMIZATION_ID);
+ /* customization_id in format LOEM[-VARIANT]. */
+ remove_dash = 1;
+
+ }
+ if (!wl_tag)
+ return 1;
+
+ if (remove_dash) {
+ char *dash = strchr(wl_tag, '-');
+ if (dash)
+ *dash = '\0';
+ }
+ if (!prefix_model)
+ str_convert(wl_tag, toupper);
+
+ sig_id = wl_tag;
+ if (prefix_model)
+ ASPRINTF(&sig_id, "%s-%s", model->name, wl_tag);
+ else
+ wl_tag = NULL;
+ free(wl_tag);
+ signature_id = sig_id;
+ }
+
+ DEBUG("Find white label patches by signature ID: '%s'.", signature_id);
+ find_patches_for_model(model, archive, signature_id);
+ if (!model->patches.rootkey) {
+ ERROR("No keys found for signature_id: '%s'", signature_id);
+ r = 1;
+ } else {
+ printf("Applied for white label: %s\n", signature_id);
+ }
+ free(sig_id);
+ return r;
+}
+
+/*
+ * Creates a new manifest object by scanning files in archive.
+ * Returns the manifest on success, otherwise NULL for failure.
+ */
+struct manifest *new_manifest_from_archive(struct archive *archive)
+{
+ struct manifest manifest = {0}, *new_manifest;
+ struct model_config model = {0};
+ const char * const image_name = "bios.bin",
+ * const ec_name = "ec.bin",
+ * const pd_name = "pd.bin";
+
+ manifest.archive = archive;
+ manifest.default_model = -1;
+ archive_walk(archive, &manifest, manifest_scan_entries);
+ if (manifest.num == 0) {
+ struct firmware_image image = {0};
+ /* Try to load from current folder. */
+ if (!archive_has_entry(archive, image_name))
+ return 0;
+ model.image = strdup(image_name);
+ if (archive_has_entry(archive, ec_name))
+ model.ec_image = strdup(ec_name);
+ if (archive_has_entry(archive, pd_name))
+ model.pd_image = strdup(pd_name);
+ /* Extract model name from FWID: $Vendor_$Platform.$Version */
+ if (!load_firmware_image(&image, image_name, archive)) {
+ char *token = NULL;
+ if (strtok(image.ro_version, "_"))
+ token = strtok(NULL, ".");
+ if (token && *token) {
+ str_convert(token, tolower);
+ model.name = strdup(token);
+ }
+ free_firmware_image(&image);
+ }
+ if (!model.name)
+ model.name = strdup(DEFAULT_MODEL_NAME);
+ if (manifest.has_keyset)
+ model.is_white_label = 1;
+ manifest_add_model(&manifest, &model);
+ manifest.default_model = manifest.num - 1;
+ }
+ DEBUG("%d model(s) loaded.", manifest.num);
+ if (!manifest.num) {
+ ERROR("No valid configurations found from archive.");
+ return NULL;
+ }
+
+ new_manifest = (struct manifest *)malloc(sizeof(manifest));
+ if (!new_manifest) {
+ ERROR("Internal error: memory allocation error.");
+ return NULL;
+ }
+ memcpy(new_manifest, &manifest, sizeof(manifest));
+ return new_manifest;
+}
+
+/* Releases all resources allocated by given manifest object. */
+void delete_manifest(struct manifest *manifest)
+{
+ int i;
+ assert(manifest);
+ for (i = 0; i < manifest->num; i++) {
+ struct model_config *model = &manifest->models[i];
+ free(model->name);
+ free(model->signature_id);
+ free(model->image);
+ free(model->ec_image);
+ free(model->pd_image);
+ free(model->patches.rootkey);
+ free(model->patches.vblock_a);
+ free(model->patches.vblock_b);
+ }
+ free(manifest->models);
+ free(manifest);
+}
+
+static const char *get_gbb_key_hash(const struct vb2_gbb_header *gbb,
+ int32_t offset, int32_t size)
+{
+ struct vb2_packed_key *key;
+
+ if (!gbb)
+ return "<No GBB>";
+ key = (struct vb2_packed_key *)((uint8_t *)gbb + offset);
+ if (!packed_key_looks_ok(key, size))
+ return "<Invalid key>";
+ return packed_key_sha1_string(key);
+}
+
+/* Prints the information of given image file in JSON format. */
+static void print_json_image(
+ const char *name, const char *fpath, struct model_config *m,
+ struct archive *archive, int indent, int is_host)
+{
+ struct firmware_image image = {0};
+ const struct vb2_gbb_header *gbb = NULL;
+ if (!fpath)
+ return;
+ load_firmware_image(&image, fpath, archive);
+ if (is_host)
+ gbb = find_gbb(&image);
+ else
+ printf(",\n");
+ printf("%*s\"%s\": { \"versions\": { \"ro\": \"%s\", \"rw\": \"%s\" },",
+ indent, "", name, image.ro_version, image.rw_version_a);
+ indent += 2;
+ if (is_host && patch_image_by_model(&image, m, archive) != 0) {
+ ERROR("Failed to patch images by model: %s", m->name);
+ } else if (gbb) {
+ printf("\n%*s\"keys\": { \"root\": \"%s\", ",
+ indent, "",
+ get_gbb_key_hash(gbb, gbb->rootkey_offset,
+ gbb->rootkey_size));
+ printf("\"recovery\": \"%s\" },",
+ get_gbb_key_hash(gbb, gbb->recovery_key_offset,
+ gbb->recovery_key_size));
+ }
+ printf("\n%*s\"image\": \"%s\" }", indent, "", fpath);
+ free_firmware_image(&image);
+}
+
+/* Prints the information of objects in manifest (models and images) in JSON. */
+void print_json_manifest(const struct manifest *manifest)
+{
+ int i, indent;
+ struct archive *ar = manifest->archive;
+
+ printf("{\n");
+ for (i = 0, indent = 2; i < manifest->num; i++) {
+ struct model_config *m = &manifest->models[i];
+ printf("%s%*s\"%s\": {\n", i ? ",\n" : "", indent, "", m->name);
+ indent += 2;
+ print_json_image("host", m->image, m, ar, indent, 1);
+ print_json_image("ec", m->ec_image, m, ar, indent, 0);
+ print_json_image("pd", m->pd_image, m, ar, indent, 0);
+ if (m->patches.rootkey) {
+ struct patch_config *p = &m->patches;
+ printf(",\n%*s\"patches\": { \"rootkey\": \"%s\", "
+ "\"vblock_a\": \"%s\", \"vblock_b\": \"%s\" }",
+ indent, "", p->rootkey, p->vblock_a,
+ p->vblock_b);
+ }
+ if (m->signature_id)
+ printf(",\n%*s\"signature_id\": \"%s\"", indent, "",
+ m->signature_id);
+ printf("\n }");
+ indent -= 2;
+ assert(indent == 2);
+ }
+ printf("\n}\n");
+}
diff --git a/futility/updater_quirks.c b/futility/updater_quirks.c
new file mode 100644
index 00000000..8c924a37
--- /dev/null
+++ b/futility/updater_quirks.c
@@ -0,0 +1,357 @@
+/*
+ * 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.
+ *
+ * The board-specific quirks needed by firmware updater.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "futility.h"
+#include "host_misc.h"
+#include "updater.h"
+
+struct quirks_record {
+ const char * const match;
+ const char * const quirks;
+};
+
+static const struct quirks_record quirks_records[] = {
+ { .match = "Google_Whirlwind.", .quirks = "enlarge_image" },
+ { .match = "Google_Arkham.", .quirks = "enlarge_image" },
+ { .match = "Google_Storm.", .quirks = "enlarge_image" },
+ { .match = "Google_Gale.", .quirks = "enlarge_image" },
+
+ { .match = "Google_Chell.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Lars.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Sentry.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Asuka.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Caroline.", .quirks = "unlock_me_for_update" },
+ { .match = "Google_Cave.", .quirks = "unlock_me_for_update" },
+
+ { .match = "Google_Eve.",
+ .quirks = "unlock_me_for_update,eve_smm_store" },
+
+ { .match = "Google_Poppy.", .quirks = "min_platform_version=6" },
+ { .match = "Google_Scarlet.", .quirks = "min_platform_version=1" },
+
+ { .match = "Google_Snow.", .quirks = "daisy_snow_dual_model" },
+};
+
+/*
+ * Helper function to write a firmware image into file on disk.
+ * Returns the result from vb2_write_file.
+ */
+static int write_image(const char *file_path, struct firmware_image *image)
+{
+ return vb2_write_file(file_path, image->data, image->size);
+}
+
+/* Preserves meta data and reload image contents from given file path. */
+static int reload_firmware_image(const char *file_path, struct firmware_image *image)
+{
+ const char *programmer = image->programmer;
+ free_firmware_image(image);
+ image->programmer = programmer;
+ return load_firmware_image(image, file_path, NULL);
+}
+
+/*
+ * Quirk to enlarge a firmware image to match flash size. This is needed by
+ * devices using multiple SPI flash with different sizes, for example 8M and
+ * 16M. The image_to will be padded with 0xFF using the size of image_from.
+ * Returns 0 on success, otherwise failure.
+ */
+static int quirk_enlarge_image(struct updater_config *cfg)
+{
+ struct firmware_image *image_from = &cfg->image_current,
+ *image_to = &cfg->image;
+ const char *tmp_path;
+ size_t to_write;
+ FILE *fp;
+
+ if (image_from->size <= image_to->size)
+ return 0;
+
+ tmp_path = updater_create_temp_file(cfg);
+ if (!tmp_path)
+ return -1;
+
+ DEBUG("Resize image from %u to %u.", image_to->size, image_from->size);
+ to_write = image_from->size - image_to->size;
+ write_image(tmp_path, image_to);
+ fp = fopen(tmp_path, "ab");
+ if (!fp) {
+ ERROR("Cannot open temporary file %s.", tmp_path);
+ return -1;
+ }
+ while (to_write-- > 0)
+ fputc('\xff', fp);
+ fclose(fp);
+ return reload_firmware_image(tmp_path, image_to);
+}
+
+/*
+ * Quirk to unlock a firmware image with SI_ME (management engine) when updating
+ * so the system has a chance to make sure SI_ME won't be corrupted on next boot
+ * before locking the Flash Master values in SI_DESC.
+ * Returns 0 on success, otherwise failure.
+ */
+static int quirk_unlock_me_for_update(struct updater_config *cfg)
+{
+ struct firmware_section section;
+ struct firmware_image *image_to = &cfg->image;
+ const int flash_master_offset = 128;
+ const uint8_t flash_master[] = {
+ 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
+ 0xff, 0xff
+ };
+
+ find_firmware_section(&section, image_to, FMAP_SI_DESC);
+ if (section.size < flash_master_offset + ARRAY_SIZE(flash_master))
+ return 0;
+ if (memcmp(section.data + flash_master_offset, flash_master,
+ ARRAY_SIZE(flash_master)) == 0) {
+ DEBUG("Target ME not locked.");
+ return 0;
+ }
+ /*
+ * b/35568719: We should only update with unlocked ME and let
+ * board-postinst lock it.
+ */
+ printf("%s: Changed Flash Master Values to unlocked.\n", __FUNCTION__);
+ memcpy(section.data + flash_master_offset, flash_master,
+ ARRAY_SIZE(flash_master));
+ return 0;
+}
+
+/*
+ * Checks and returns 0 if the platform version of current system is larger
+ * or equal to given number, otherwise non-zero.
+ */
+static int quirk_min_platform_version(struct updater_config *cfg)
+{
+ int min_version = get_config_quirk(QUIRK_MIN_PLATFORM_VERSION, cfg);
+ int platform_version = get_system_property(SYS_PROP_PLATFORM_VER, cfg);
+
+ DEBUG("Minimum required version=%d, current platform version=%d",
+ min_version, platform_version);
+
+ if (platform_version >= min_version)
+ return 0;
+ ERROR("Need platform version >= %d (current is %d). "
+ "This firmware will only run on newer systems.",
+ min_version, platform_version);
+ return -1;
+}
+
+/*
+ * Adjust firmware image according to running platform version.
+ * Returns 0 if success, non-zero if error.
+ */
+static int quirk_daisy_snow_dual_model(struct updater_config *cfg)
+{
+ /*
+ * The daisy-snow firmware should be packed as RO, RW_A=x16, RW_B=x8.
+ * RO update for x8 and RO EC update for all are no longer supported.
+ */
+ struct firmware_section a, b;
+ int i, is_x8 = 0, is_x16 = 0;
+ const char * const x8_versions[] = {
+ "DVT",
+ "PVT",
+ "PVT2",
+ "MP",
+ };
+ const char * const x16_versions[] = {
+ "MPx16", /* Rev 4 */
+ "MP2", /* Rev 5 */
+ };
+ char *platform_version = host_shell("mosys platform version");
+
+ for (i = 0; i < ARRAY_SIZE(x8_versions) && !is_x8; i++) {
+ if (strcmp(x8_versions[i], platform_version) == 0)
+ is_x8 = 1;
+ }
+ for (i = 0; i < ARRAY_SIZE(x16_versions) && !is_x8 && !is_x16; i++) {
+ if (strcmp(x16_versions[i], platform_version) == 0)
+ is_x16 = 1;
+ }
+ printf("%s: Platform version: %s (original value: %s)\n", __FUNCTION__,
+ is_x8 ? "x8" : is_x16 ? "x16": "unknown", platform_version);
+ free(platform_version);
+
+ find_firmware_section(&a, &cfg->image, FMAP_RW_SECTION_A);
+ find_firmware_section(&b, &cfg->image, FMAP_RW_SECTION_B);
+
+ if (cfg->ec_image.data) {
+ ERROR("EC RO update is not supported with this quirk.");
+ return -1;
+ }
+ if (!a.data || !b.data || a.size != b.size) {
+ ERROR("Invalid firmware image: %s", cfg->image.file_name);
+ return -1;
+ }
+ if (memcmp(a.data, b.data, a.size) == 0) {
+ ERROR("Input image must have both x8 and x16 firmware.");
+ return -1;
+ }
+
+ if (is_x16) {
+ memmove(b.data, a.data, a.size);
+ free(cfg->image.rw_version_b);
+ cfg->image.rw_version_b = strdup(cfg->image.rw_version_a);
+ } else if (is_x8) {
+ memmove(a.data, b.data, b.size);
+ free(cfg->image.rw_version_a);
+ cfg->image.rw_version_a = strdup(cfg->image.rw_version_b);
+ /* Need to use RO from current system. */
+ if (!cfg->image_current.data &&
+ load_system_firmware(cfg, &cfg->image_current) != 0) {
+ ERROR("Cannot get system RO contents");
+ return -1;
+ }
+ preserve_firmware_section(&cfg->image_current, &cfg->image,
+ FMAP_RO_SECTION);
+ } else {
+ ERROR("Unknown platform, cannot update.");
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Extracts files from a CBFS on given region (section) of image_file.
+ * Returns the path to a temporary file on success, otherwise NULL.
+ */
+static const char *extract_cbfs_file(struct updater_config *cfg,
+ const char *image_file,
+ const char *cbfs_region,
+ const char *cbfs_name)
+{
+ const char *output = updater_create_temp_file(cfg);
+ char *command, *result;
+
+ ASPRINTF(&command, "cbfstool \"%s\" extract -r %s -n \"%s\" "
+ "-f \"%s\" 2>&1", image_file, cbfs_region,
+ cbfs_name, output);
+
+ result = host_shell(command);
+ free(command);
+
+ if (!*result)
+ output = NULL;
+
+ free(result);
+ return output;
+}
+
+/*
+ * Quirk to help preserving SMM store on devices without a dedicated "SMMSTORE"
+ * FMAP section. These devices will store "smm store" file in same CBFS where
+ * the legacy boot loader lives (i.e, FMAP RW_LEGACY).
+ * Note this currently has dependency on external program "cbstool".
+ * Returns 0 if the SMM store is properly preserved, or if the system is not
+ * available to do that (problem in cbfstool, or no "smm store" in current
+ * system firmware). Otherwise non-zero as failure.
+ */
+static int quirk_eve_smm_store(struct updater_config *cfg)
+{
+ const char *smm_store_name = "smm store";
+ const char *temp_image = updater_create_temp_file(cfg);
+ const char *old_store;
+ char *command;
+
+ if (write_image(temp_image, &cfg->image_current) != VBERROR_SUCCESS)
+ return -1;
+
+ old_store = extract_cbfs_file(cfg, temp_image, FMAP_RW_LEGACY,
+ smm_store_name);
+ if (!old_store) {
+ DEBUG("cbfstool failure or SMM store not available. "
+ "Don't preserve.");
+ return 0;
+ }
+
+ /* Reuse temp_image. */
+ if (write_image(temp_image, &cfg->image) != VBERROR_SUCCESS)
+ return -1;
+
+ /* crosreview.com/1165109: The offset is fixed at 0x1bf000. */
+ ASPRINTF(&command,
+ "cbfstool \"%s\" remove -r %s -n \"%s\" 2>/dev/null; "
+ "cbfstool \"%s\" add -r %s -n \"%s\" -f \"%s\" "
+ " -t raw -b 0x1bf000", temp_image, FMAP_RW_LEGACY,
+ smm_store_name, temp_image, FMAP_RW_LEGACY,
+ smm_store_name, old_store);
+ host_shell(command);
+ free(command);
+
+ return reload_firmware_image(temp_image, &cfg->image);
+}
+
+/*
+ * Registers known quirks to a updater_config object.
+ */
+void updater_register_quirks(struct updater_config *cfg)
+{
+ struct quirk_entry *quirks;
+
+ assert(ARRAY_SIZE(cfg->quirks) == QUIRK_MAX);
+ quirks = &cfg->quirks[QUIRK_ENLARGE_IMAGE];
+ quirks->name = "enlarge_image";
+ quirks->help = "Enlarge firmware image by flash size.";
+ quirks->apply = quirk_enlarge_image;
+
+ quirks = &cfg->quirks[QUIRK_MIN_PLATFORM_VERSION];
+ quirks->name = "min_platform_version";
+ quirks->help = "Minimum compatible platform version "
+ "(also known as Board ID version).";
+ quirks->apply = quirk_min_platform_version;
+
+ quirks = &cfg->quirks[QUIRK_UNLOCK_ME_FOR_UPDATE];
+ quirks->name = "unlock_me_for_update";
+ quirks->help = "b/35568719; only lock management engine in "
+ "board-postinst.";
+ quirks->apply = quirk_unlock_me_for_update;
+
+ quirks = &cfg->quirks[QUIRK_DAISY_SNOW_DUAL_MODEL];
+ quirks->name = "daisy_snow_dual_model";
+ quirks->help = "b/35525858; needs an image RW A=[model x16], B=x8.";
+ quirks->apply = quirk_daisy_snow_dual_model;
+
+ quirks = &cfg->quirks[QUIRK_EVE_SMM_STORE];
+ quirks->name = "eve_smm_store";
+ quirks->help = "b/70682365; preserve UEFI SMM store without "
+ "dedicated FMAP section.";
+ quirks->apply = quirk_eve_smm_store;
+}
+
+/*
+ * Gets the default quirk config string for target image.
+ * Returns a string (in same format as --quirks) to load or NULL if no quirks.
+ */
+const char * const updater_get_default_quirks(struct updater_config *cfg)
+{
+ const char *pattern = cfg->image.ro_version;
+ int i;
+
+ if (!pattern) {
+ DEBUG("Cannot identify system for default quirks.");
+ return NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(quirks_records); i++) {
+ const struct quirks_record *r = &quirks_records[i];
+ if (strncmp(r->match, pattern, strlen(r->match)) != 0)
+ continue;
+ DEBUG("Found system default quirks: %s", r->quirks);
+ return r->quirks;
+ }
+ return NULL;
+}
diff --git a/tests/futility/link.manifest.json b/tests/futility/link.manifest.json
new file mode 100644
index 00000000..9bd33f52
--- /dev/null
+++ b/tests/futility/link.manifest.json
@@ -0,0 +1,7 @@
+{
+ "link": {
+ "host": { "versions": { "ro": "Google_Link.2695.1.133", "rw": "Google_Link.2695.1.133" },
+ "keys": { "root": "7b5c520ceabce86f13e02b7ca363cfb509fc5b98", "recovery": "7e74cd6d66f361da068c0419d2e0946b4d091e1c" },
+ "image": "bios.bin" }
+ }
+}
diff --git a/tests/futility/models/link/setvars.sh b/tests/futility/models/link/setvars.sh
new file mode 100755
index 00000000..19180998
--- /dev/null
+++ b/tests/futility/models/link/setvars.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+# Copyright 2017 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.
+
+# This is a template file which provides settings for firmware update of a
+# particular model. The pack_firmware.py script uses this to create a working
+# setvars-model.sh script.
+
+# Image and key files for model link
+IMAGE_MAIN="images/bios_link.bin"
+IMAGE_EC="images/ec_link.bin"
+IMAGE_PD=""
+SIGNATURE_ID="link"
diff --git a/tests/futility/models/peppy/setvars.sh b/tests/futility/models/peppy/setvars.sh
new file mode 100755
index 00000000..855e815a
--- /dev/null
+++ b/tests/futility/models/peppy/setvars.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+# Copyright 2017 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.
+
+# This is a template file which provides settings for firmware update of a
+# particular model. The pack_firmware.py script uses this to create a working
+# setvars-model.sh script.
+
+# Image and key files for model peppy
+IMAGE_MAIN="images/bios_peppy.bin"
+IMAGE_EC="images/ec_peppy.bin"
+IMAGE_PD=""
+SIGNATURE_ID="peppy"
diff --git a/tests/futility/models/whitetip/setvars.sh b/tests/futility/models/whitetip/setvars.sh
new file mode 100755
index 00000000..4b9cb406
--- /dev/null
+++ b/tests/futility/models/whitetip/setvars.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright 2017 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.
+
+# This is a template file which provides settings for firmware update of a
+# particular model. The pack_firmware.py script uses this to create a working
+# setvars-model.sh script.
+
+# Version information for model whitetip
+TARGET_RO_FWID="Google_Coral.10068.45.0"
+TARGET_FWID="Google_Coral.10068.45.0"
+TARGET_ECID="coral_v1.1.7272-0b44fba22"
+TARGET_PDID=""
+TARGET_PLATFORM="Google_Coral"
+
+# Image and key files for model whitetip
+IMAGE_MAIN="images/bios_coral.bin"
+IMAGE_EC=""
+IMAGE_PD=""
+SIGNATURE_ID="sig-id-in-customization-id"
diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh
index 44530abb..740e53cf 100755
--- a/tests/futility/test_update.sh
+++ b/tests/futility/test_update.sh
@@ -8,7 +8,7 @@ TMP="$me.tmp"
# Test --sys_props (primitive test needed for future updating tests).
test_sys_props() {
- ! "${FUTILITY}" --debug update --sys_props "$*" |
+ ! "${FUTILITY}" --debug update --sys_props "$*" 2>&1 |
sed -n 's/.*property\[\(.*\)].value = \(.*\)/\1,\2,/p' |
tr '\n' ' '
}
@@ -20,7 +20,7 @@ test "$(test_sys_props " 1,, 2")" = "0,1, 2,2, "
test "$(test_sys_props " , 4,")" = "1,4, "
test_quirks() {
- ! "${FUTILITY}" --debug update --quirks "$*" |
+ ! "${FUTILITY}" --debug update --quirks "$*" 2>&1 |
sed -n 's/.*Set quirk \(.*\) to \(.*\)./\1,\2/p' |
tr '\n' ' '
}
@@ -112,6 +112,10 @@ cp -f "${FROM_IMAGE}" "${TMP}.expected.legacy"
RW_SECTION_B:${TMP}.to/RW_SECTION_B
"${FUTILITY}" load_fmap "${TMP}.expected.legacy" \
RW_LEGACY:${TMP}.to/RW_LEGACY
+cp -f "${TMP}.expected.full" "${TMP}.expected.full.gbb0"
+"${FUTILITY}" gbb -s --flags=0 "${TMP}.expected.full.gbb0"
+cp -f "${FROM_IMAGE}" "${FROM_IMAGE}.gbb0"
+"${FUTILITY}" gbb -s --flags=0 "${FROM_IMAGE}.gbb0"
cp -f "${TMP}.expected.full" "${TMP}.expected.large"
dd if=/dev/zero bs=8388608 count=1 | tr '\000' '\377' >>"${TMP}.expected.large"
cp -f "${TMP}.expected.full" "${TMP}.expected.me_unlocked"
@@ -158,6 +162,31 @@ test_update "Full update (TPM Anti-rollback: kernel key)" \
"${FROM_IMAGE}" "!Firmware version rollback detected (5->4)" \
-i "${TO_IMAGE}" --wp=0 --sys_props 1,0x10005,1
+test_update "Full update (TPM Anti-rollback: 0 as tpm_fwver)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x0,1
+
+test_update "Full update (TPM check failure due to invalid tpm_fwver)" \
+ "${FROM_IMAGE}" "!Invalid tpm_fwver: -1" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,-1,1
+
+test_update "Full update (Skip TPM check with --force)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,-1,1 --force
+
+test_update "Full update (from stdin)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i - --wp=0 --sys_props 0,-1,1 --force <"${TO_IMAGE}"
+
+test_update "Full update (GBB=0 -> 0)" \
+ "${FROM_IMAGE}.gbb0" "${TMP}.expected.full.gbb0" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (--host_only)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1 \
+ --host_only --ec_image non-exist.bin --pd_image non_exist.bin
+
# Test RW-only update.
test_update "RW update" \
"${FROM_IMAGE}" "${TMP}.expected.rw" \
@@ -220,6 +249,27 @@ test_update "RW update (vboot1, B->B)" \
"${FROM_IMAGE}" "${TMP}.expected.b" \
-i "${TO_IMAGE}" -t --wp=1 --sys_props 1,0 --sys_props 0,0x10001,0
+# Test 'factory mode'
+test_update "Factory mode update (WP=0)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1 --mode=factory
+
+test_update "Factory mode update (WP=0)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ --factory -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Factory mode update (WP=1)" \
+ "${FROM_IMAGE}" "!needs WP disabled" \
+ -i "${TO_IMAGE}" --wp=1 --sys_props 0,0x10001,1 --mode=factory
+
+test_update "Factory mode update (WP=1)" \
+ "${FROM_IMAGE}" "!needs WP disabled" \
+ --factory -i "${TO_IMAGE}" --wp=1 --sys_props 0,0x10001,1
+
+test_update "Factory mode update (GBB=0 -> 39)" \
+ "${FROM_IMAGE}.gbb0" "${TMP}.expected.full" \
+ --factory -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
# Test legacy update
test_update "Legacy update" \
"${FROM_IMAGE}" "${TMP}.expected.legacy" \
@@ -248,3 +298,115 @@ test_update "Full update (--quirks min_platform_version)" \
"${FROM_IMAGE}" "${TMP}.expected.full" \
--quirks min_platform_version=3 \
-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1,3
+
+# Test archive and manifest.
+A="${TMP}.archive"
+mkdir -p "${A}/bin"
+echo 'echo "${WL_TAG}"' >"${A}/bin/vpd"
+chmod +x "${A}/bin/vpd"
+
+cp -f "${LINK_BIOS}" "${A}/bios.bin"
+echo "TEST: Manifest (--manifest)"
+${FUTILITY} update -a "${A}" --manifest >"${TMP}.json.out"
+cmp "${TMP}.json.out" "${SCRIPTDIR}/link.manifest.json"
+
+cp -f "${TO_IMAGE}" "${A}/bios.bin"
+test_update "Full update (--archive, single package)" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3
+
+echo "TEST: Output (--mode=output)"
+mkdir -p "${TMP}.output"
+${FUTILITY} update -i "${LINK_BIOS}" --mode=output --output_dir="${TMP}.output"
+cmp "${LINK_BIOS}" "${TMP}.output/bios.bin"
+
+mkdir -p "${A}/keyset"
+cp -f "${LINK_BIOS}" "${A}/bios.bin"
+cp -f "${TMP}.to/rootkey" "${A}/keyset/rootkey.WL"
+cp -f "${TMP}.to/VBLOCK_A" "${A}/keyset/vblock_A.WL"
+cp -f "${TMP}.to/VBLOCK_B" "${A}/keyset/vblock_B.WL"
+${FUTILITY} gbb -s --rootkey="${TMP}.from/rootkey" "${A}/bios.bin"
+${FUTILITY} load_fmap "${A}/bios.bin" VBLOCK_A:"${TMP}.from/VBLOCK_A"
+${FUTILITY} load_fmap "${A}/bios.bin" VBLOCK_B:"${TMP}.from/VBLOCK_B"
+
+test_update "Full update (--archive, whitelabel, no VPD)" \
+ "${A}/bios.bin" "!Need VPD set for white" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3
+
+test_update "Full update (--archive, whitelabel, no VPD - factory mode)" \
+ "${LINK_BIOS}" "${A}/bios.bin" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --mode=factory
+
+test_update "Full update (--archive, WL, single package)" \
+ "${A}/bios.bin" "${LINK_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --signature_id=WL
+
+WL_TAG="WL" PATH="${A}/bin:${PATH}" \
+ test_update "Full update (--archive, WL, fake vpd)" \
+ "${A}/bios.bin" "${LINK_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3
+
+echo "TEST: Output (-a, --mode=output)"
+mkdir -p "${TMP}.outa"
+cp -f "${A}/bios.bin" "${TMP}.emu"
+WL_TAG="WL" PATH="${A}/bin:${PATH}" \
+ ${FUTILITY} update -a "${A}" --mode=output --emu="${TMP}.emu" \
+ --output_dir="${TMP}.outa"
+cmp "${LINK_BIOS}" "${TMP}.outa/bios.bin"
+
+# Test archive with Unified Build contents.
+cp -r "${SCRIPTDIR}/models" "${A}/"
+mkdir -p "${A}/images"
+mv "${A}/bios.bin" "${A}/images/bios_coral.bin"
+cp -f "${PEPPY_BIOS}" "${A}/images/bios_peppy.bin"
+cp -f "${LINK_BIOS}" "${A}/images/bios_link.bin"
+cp -f "${TMP}.to/rootkey" "${A}/keyset/rootkey.whitetip-wl"
+cp -f "${TMP}.to/VBLOCK_A" "${A}/keyset/vblock_A.whitetip-wl"
+cp -f "${TMP}.to/VBLOCK_B" "${A}/keyset/vblock_B.whitetip-wl"
+cp -f "${PEPPY_BIOS}" "${FROM_IMAGE}.ap"
+cp -f "${LINK_BIOS}" "${FROM_IMAGE}.al"
+patch_file ${FROM_IMAGE}.ap FW_MAIN_A 0 "corrupted"
+patch_file ${FROM_IMAGE}.al FW_MAIN_A 0 "corrupted"
+test_update "Full update (--archive, model=link)" \
+ "${FROM_IMAGE}.al" "${LINK_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --model=link
+test_update "Full update (--archive, model=peppy)" \
+ "${FROM_IMAGE}.ap" "${PEPPY_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --model=peppy
+test_update "Full update (--archive, model=unknown)" \
+ "${FROM_IMAGE}.ap" "!Model 'unknown' is not defined" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --model=unknown
+test_update "Full update (--archive, model=whitetip, signature_id=WL)" \
+ "${FROM_IMAGE}.al" "${LINK_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --model=whitetip \
+ --signature_id=whitetip-wl
+
+WL_TAG="wl" PATH="${A}/bin:${PATH}" \
+ test_update "Full update (-a, model=WL, fake VPD)" \
+ "${FROM_IMAGE}.al" "${LINK_BIOS}" \
+ -a "${A}" --wp=0 --sys_props 0,0x10001,1,3 --model=whitetip
+
+# Test special programmer
+if type flashrom >/dev/null 2>&1; then
+ echo "TEST: Full update (dummy programmer)"
+ cp -f "${FROM_IMAGE}" "${TMP}.emu"
+ sudo "${FUTILITY}" update --programmer \
+ dummy:emulate=VARIABLE_SIZE,image=${TMP}.emu,size=8388608 \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1,3 >&2
+ cmp "${TMP}.emu" "${TMP}.expected.full"
+fi
+
+if type cbfstool >/dev/null 2>&1; then
+ echo "SMM STORE" >"${TMP}.smm"
+ truncate -s 262144 "${TMP}.smm"
+ cp -f "${FROM_IMAGE}" "${TMP}.from.smm"
+ cp -f "${TMP}.expected.full" "${TMP}.expected.full_smm"
+ cbfstool "${TMP}.from.smm" add -r RW_LEGACY -n "smm store" \
+ -f "${TMP}.smm" -t raw
+ cbfstool "${TMP}.expected.full_smm" add -r RW_LEGACY -n "smm store" \
+ -f "${TMP}.smm" -t raw -b 0x1bf000
+ test_update "Legacy update (--quirks eve_smm_store)" \
+ "${TMP}.from.smm" "${TMP}.expected.full_smm" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1 \
+ --quirks eve_smm_store
+fi