summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile62
-rw-r--r--futility/cmd_update.c211
-rw-r--r--futility/futility.h3
-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/run_test_scripts.sh1
-rwxr-xr-xtests/futility/test_update.sh412
13 files changed, 4443 insertions, 31 deletions
diff --git a/Makefile b/Makefile
index d61844b4..2896a637 100644
--- a/Makefile
+++ b/Makefile
@@ -235,6 +235,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
@@ -646,8 +654,6 @@ SIGNING_COMMON = scripts/image_signing/common_minimal.sh
# The unified firmware utility will eventually replace all the others
FUTIL_BIN = ${BUILD}/futility/futility
-# But we still need both static (tiny) and dynamic (with openssl) versions.
-FUTIL_STATIC_BIN = ${FUTIL_BIN}_s
# These are the executables that are now built in to futility. We'll create
# symlinks for these so the old names will still work.
@@ -660,43 +666,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_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 \
@@ -1123,23 +1127,21 @@ signing_install: ${SIGNING_SCRIPTS} ${SIGNING_SCRIPTS_DEV} ${SIGNING_COMMON}
# new Firmware Utility
.PHONY: futil
-futil: ${FUTIL_STATIC_BIN} ${FUTIL_BIN}
+futil: ${FUTIL_BIN}
-${FUTIL_STATIC_BIN}: LDLIBS += ${CRYPTO_STATIC_LIBS}
-${FUTIL_STATIC_BIN}: ${FUTIL_STATIC_OBJS} ${UTILLIB}
- @${PRINTF} " LD $(subst ${BUILD}/,,$@)\n"
- ${Q}${LD} -o $@ ${CFLAGS} ${LDFLAGS} -static $^ ${LDLIBS}
+# FUTIL_LIBS is shared by FUTIL_BIN and TEST_FUTIL_BINS.
+FUTIL_LIBS = ${CRYPTO_LIBS} ${LIBZIP_LIBS}
-${FUTIL_BIN}: LDLIBS += ${CRYPTO_LIBS} ${FWLIB20}
+${FUTIL_BIN}: LDLIBS += ${FUTIL_LIBS}
${FUTIL_BIN}: ${FUTIL_OBJS} ${UTILLIB} ${FWLIB20} ${UTILBDB}
@${PRINTF} " LD $(subst ${BUILD}/,,$@)\n"
${Q}${LD} -o $@ ${CFLAGS} ${LDFLAGS} $^ ${LDLIBS}
.PHONY: futil_install
-futil_install: ${FUTIL_BIN} ${FUTIL_STATIC_BIN}
+futil_install: ${FUTIL_BIN}
@${PRINTF} " INSTALL futility\n"
${Q}mkdir -p ${UB_DIR}
- ${Q}${INSTALL} -t ${UB_DIR} ${FUTIL_BIN} ${FUTIL_STATIC_BIN}
+ ${Q}${INSTALL} -t ${UB_DIR} ${FUTIL_BIN}
${Q}for prog in ${FUTIL_SYMLINKS}; do \
ln -sf futility "${UB_DIR}/$$prog"; done
@@ -1173,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}
@@ -1324,9 +1326,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
new file mode 100644
index 00000000..f3bb29b9
--- /dev/null
+++ b/futility/cmd_update.c
@@ -0,0 +1,211 @@
+/*
+ * 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 command line tool to invoke firmware updater.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "futility.h"
+#include "updater.h"
+#include "utility.h"
+
+
+/* Command line options */
+static struct option const long_opts[] = {
+ /* name has_arg *flag val */
+ {"image", 1, NULL, 'i'},
+ {"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'},
+ {"help", 0, NULL, 'h'},
+ {NULL, 0, NULL, 0},
+};
+
+static const char * const short_opts = "hi:e:ta:m:p:dv";
+
+static void print_help(int argc, char *argv[])
+{
+ printf("\n"
+ "Usage: " MYNAME " %s [OPTIONS]\n"
+ "\n"
+ "-i, --image=FILE \tAP (host) firmware image (image.bin)\n"
+ "-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"
+ "",
+ argv[0]);
+}
+
+static int do_update(int argc, char *argv[])
+{
+ struct updater_config *cfg;
+ struct updater_config_arguments args = {0};
+ int i, errorcnt = 0, do_update = 1;
+
+ 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':
+ args.image = optarg;
+ break;
+ case 'e':
+ args.ec_image = optarg;
+ break;
+ case 'P':
+ args.pd_image = optarg;
+ break;
+ case 't':
+ 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':
+ args.quirks = optarg;
+ break;
+ case 'L':
+ updater_list_config_quirks(cfg);
+ return 0;
+ case 'm':
+ 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':
+ args.write_protection = optarg;
+ break;
+ case 'H':
+ args.host_only = 1;
+ break;
+ case 'E':
+ args.emulation = optarg;
+ break;
+ case 'p':
+ args.programmer = optarg;
+ break;
+ case 'F':
+ args.force_update = 1;
+ break;
+ case 'S':
+ args.sys_props = optarg;
+ break;
+ case 'v':
+ args.verbosity++;
+ break;
+ case 'd':
+ debugging_enabled = 1;
+ args.verbosity++;
+ break;
+
+ case 'h':
+ print_help(argc, argv);
+ return !!errorcnt;
+ case '?':
+ errorcnt++;
+ if (optopt)
+ Error("Unrecognized option: -%c\n", optopt);
+ else if (argv[optind - 1])
+ Error("Unrecognized option (possibly '%s')\n",
+ argv[optind - 1]);
+ else
+ Error("Unrecognized option.\n");
+ break;
+ default:
+ errorcnt++;
+ Error("Failed parsing options.\n");
+ }
+ }
+ if (optind < argc) {
+ errorcnt++;
+ Error("Unexpected arguments.\n");
+ }
+ 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++;
+ }
+ }
+ fprintf(stderr, ">> %s: Firmware updater %s.\n",
+ errorcnt ? "FAILED": "DONE",
+ errorcnt ? "stopped due to error" : "exited successfully");
+
+ updater_delete_config(cfg);
+ return !!errorcnt;
+}
+
+DECLARE_FUTIL_COMMAND(update, do_update, VBOOT_VERSION_ALL,
+ "Update system firmware");
diff --git a/futility/futility.h b/futility/futility.h
index c8150035..f0635750 100644
--- a/futility/futility.h
+++ b/futility/futility.h
@@ -91,6 +91,9 @@ extern const struct futil_cmd_t *const futil_cmds[];
} while (0)
#endif
+/* Print error messages (similar to VbExError but won't exit). */
+#define Error(format, ...) fprintf(stderr, "ERROR: " format, ##__VA_ARGS__ )
+
/* Debug output (off by default) */
extern int debugging_enabled;
void Debug(const char *format, ...);
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/run_test_scripts.sh b/tests/futility/run_test_scripts.sh
index 15a2580c..7171b483 100755
--- a/tests/futility/run_test_scripts.sh
+++ b/tests/futility/run_test_scripts.sh
@@ -56,6 +56,7 @@ ${SCRIPTDIR}/test_sign_fw_main.sh
${SCRIPTDIR}/test_sign_kernel.sh
${SCRIPTDIR}/test_sign_keyblocks.sh
${SCRIPTDIR}/test_sign_usbpd1.sh
+${SCRIPTDIR}/test_update.sh
${SCRIPTDIR}/test_file_types.sh
"
diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh
new file mode 100755
index 00000000..740e53cf
--- /dev/null
+++ b/tests/futility/test_update.sh
@@ -0,0 +1,412 @@
+#!/bin/bash -eux
+# 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.
+
+me=${0##*/}
+TMP="$me.tmp"
+
+# Test --sys_props (primitive test needed for future updating tests).
+test_sys_props() {
+ ! "${FUTILITY}" --debug update --sys_props "$*" 2>&1 |
+ sed -n 's/.*property\[\(.*\)].value = \(.*\)/\1,\2,/p' |
+ tr '\n' ' '
+}
+
+test "$(test_sys_props "1,2,3")" = "0,1, 1,2, 2,3, "
+test "$(test_sys_props "1 2 3")" = "0,1, 1,2, 2,3, "
+test "$(test_sys_props "1, 2,3 ")" = "0,1, 1,2, 2,3, "
+test "$(test_sys_props " 1,, 2")" = "0,1, 2,2, "
+test "$(test_sys_props " , 4,")" = "1,4, "
+
+test_quirks() {
+ ! "${FUTILITY}" --debug update --quirks "$*" 2>&1 |
+ sed -n 's/.*Set quirk \(.*\) to \(.*\)./\1,\2/p' |
+ tr '\n' ' '
+}
+
+test "$(test_quirks "enlarge_image")" = "enlarge_image,1 "
+test "$(test_quirks "enlarge_image=2")" = "enlarge_image,2 "
+test "$(test_quirks " enlarge_image, enlarge_image=2")" = \
+ "enlarge_image,1 enlarge_image,2 "
+
+# Test data files
+LINK_BIOS="${SCRIPTDIR}/data/bios_link_mp.bin"
+PEPPY_BIOS="${SCRIPTDIR}/data/bios_peppy_mp.bin"
+
+# Work in scratch directory
+cd "$OUTDIR"
+set -o pipefail
+
+# In all the test scenario, we want to test "updating from PEPPY to LINK".
+TO_IMAGE=${TMP}.src.link
+FROM_IMAGE=${TMP}.src.peppy
+TO_HWID="X86 LINK TEST 6638"
+FROM_HWID="X86 PEPPY TEST 4211"
+cp -f ${LINK_BIOS} ${TO_IMAGE}
+cp -f ${PEPPY_BIOS} ${FROM_IMAGE}
+
+patch_file() {
+ local file="$1"
+ local section="$2"
+ local section_offset="$3"
+ local data="$4"
+
+ # NAME OFFSET SIZE
+ local fmap_info="$(${FUTILITY} dump_fmap -p ${file} ${section})"
+ local base="$(echo "${fmap_info}" | sed 's/^[^ ]* //; s/ [^ ]*$//')"
+ local offset=$((base + section_offset))
+ echo "offset: ${offset}"
+ printf "${data}" | dd of="${file}" bs=1 seek="${offset}" conv=notrunc
+}
+
+# PEPPY and LINK have different platform element ("Google_Link" and
+# "Google_Peppy") in firmware ID so we want to hack them by changing
+# "Google_" to "Google.".
+patch_file ${TO_IMAGE} RW_FWID_A 0 Google.
+patch_file ${TO_IMAGE} RW_FWID_B 0 Google.
+patch_file ${TO_IMAGE} RO_FRID 0 Google.
+patch_file ${FROM_IMAGE} RW_FWID_A 0 Google.
+patch_file ${FROM_IMAGE} RW_FWID_B 0 Google.
+patch_file ${FROM_IMAGE} RO_FRID 0 Google.
+
+unpack_image() {
+ local folder="${TMP}.$1"
+ local image="$2"
+ mkdir -p "${folder}"
+ (cd "${folder}" && ${FUTILITY} dump_fmap -x "../${image}")
+ ${FUTILITY} gbb -g --rootkey="${folder}/rootkey" "${image}"
+}
+
+# Unpack images so we can prepare expected results by individual sections.
+unpack_image "to" "${TO_IMAGE}"
+unpack_image "from" "${FROM_IMAGE}"
+
+# Hack FROM_IMAGE so it has same root key as TO_IMAGE (for RW update).
+FROM_DIFFERENT_ROOTKEY_IMAGE="${FROM_IMAGE}2"
+cp -f "${FROM_IMAGE}" "${FROM_DIFFERENT_ROOTKEY_IMAGE}"
+"${FUTILITY}" gbb -s --rootkey="${TMP}.to/rootkey" "${FROM_IMAGE}"
+
+# Hack for quirks
+cp -f "${FROM_IMAGE}" "${FROM_IMAGE}.large"
+truncate -s $((8388608 * 2)) "${FROM_IMAGE}.large"
+
+# Generate expected results.
+cp -f "${TO_IMAGE}" "${TMP}.expected.full"
+cp -f "${FROM_IMAGE}" "${TMP}.expected.rw"
+cp -f "${FROM_IMAGE}" "${TMP}.expected.a"
+cp -f "${FROM_IMAGE}" "${TMP}.expected.b"
+cp -f "${FROM_IMAGE}" "${TMP}.expected.legacy"
+"${FUTILITY}" gbb -s --hwid="${FROM_HWID}" "${TMP}.expected.full"
+"${FUTILITY}" load_fmap "${TMP}.expected.full" \
+ RW_VPD:${TMP}.from/RW_VPD \
+ RO_VPD:${TMP}.from/RO_VPD
+"${FUTILITY}" load_fmap "${TMP}.expected.rw" \
+ RW_SECTION_A:${TMP}.to/RW_SECTION_A \
+ RW_SECTION_B:${TMP}.to/RW_SECTION_B \
+ RW_SHARED:${TMP}.to/RW_SHARED \
+ RW_LEGACY:${TMP}.to/RW_LEGACY
+"${FUTILITY}" load_fmap "${TMP}.expected.a" \
+ RW_SECTION_A:${TMP}.to/RW_SECTION_A
+"${FUTILITY}" load_fmap "${TMP}.expected.b" \
+ 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"
+patch_file "${TMP}.expected.me_unlocked" SI_DESC 128 \
+ "\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff"
+
+test_update() {
+ local test_name="$1"
+ local emu_src="$2"
+ local expected="$3"
+ local error_msg="${expected#!}"
+ local msg
+
+ shift 3
+ cp -f "${emu_src}" "${TMP}.emu"
+ echo "*** Test Item: ${test_name}"
+ if [ "${error_msg}" != "${expected}" ] && [ -n "${error_msg}" ]; then
+ msg="$(! "${FUTILITY}" update --emulate "${TMP}.emu" "$@" 2>&1)"
+ echo "${msg}" | grep -qF -- "${error_msg}"
+ else
+ "${FUTILITY}" update --emulate "${TMP}.emu" "$@"
+ cmp "${TMP}.emu" "${expected}"
+ fi
+}
+
+# --sys_props: mainfw_act, tpm_fwver, is_vboot2, platform_ver, [wp_hw, wp_sw]
+# tpm_fwver = <data key version:16><firmware version:16>.
+# TO_IMAGE is signed with data key version = 1, firmware version = 4 => 0x10004.
+
+# Test Full update.
+test_update "Full update" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (incompatible platform)" \
+ "${FROM_IMAGE}" "!platform is not compatible" \
+ -i "${LINK_BIOS}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (TPM Anti-rollback: data key)" \
+ "${FROM_IMAGE}" "!Data key version rollback detected (2->1)" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 1,0x20001,1
+
+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" \
+ -i "${TO_IMAGE}" --wp=1 --sys_props 0,0x10001,1
+
+test_update "RW update (incompatible platform)" \
+ "${FROM_IMAGE}" "!platform is not compatible" \
+ -i "${LINK_BIOS}" --wp=1 --sys_props 0,0x10001,1
+
+test_update "RW update (incompatible rootkey)" \
+ "${FROM_DIFFERENT_ROOTKEY_IMAGE}" "!RW not signed by same RO root key" \
+ -i "${TO_IMAGE}" --wp=1 --sys_props 0,0x10001,1
+
+test_update "RW update (TPM Anti-rollback: data key)" \
+ "${FROM_IMAGE}" "!Data key version rollback detected (2->1)" \
+ -i "${TO_IMAGE}" --wp=1 --sys_props 1,0x20001,1
+
+test_update "RW update (TPM Anti-rollback: kernel key)" \
+ "${FROM_IMAGE}" "!Firmware version rollback detected (5->4)" \
+ -i "${TO_IMAGE}" --wp=1 --sys_props 1,0x10005,1
+
+# Test Try-RW update (vboot2).
+test_update "RW update (A->B)" \
+ "${FROM_IMAGE}" "${TMP}.expected.b" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 0,0x10001,1
+
+test_update "RW update (B->A)" \
+ "${FROM_IMAGE}" "${TMP}.expected.a" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 1,0x10001,1
+
+test_update "RW update -> fallback to RO+RW Full update" \
+ "${FROM_IMAGE}" "${TMP}.expected.full" \
+ -i "${TO_IMAGE}" -t --wp=0 --sys_props 1,0x10002,1
+test_update "RW update (incompatible platform)" \
+ "${FROM_IMAGE}" "!platform is not compatible" \
+ -i "${LINK_BIOS}" -t --wp=1 --sys_props 0x10001,1
+
+test_update "RW update (incompatible rootkey)" \
+ "${FROM_DIFFERENT_ROOTKEY_IMAGE}" "!RW not signed by same RO root key" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 0,0x10001,1
+
+test_update "RW update (TPM Anti-rollback: data key)" \
+ "${FROM_IMAGE}" "!Data key version rollback detected (2->1)" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 1,0x20001,1
+
+test_update "RW update (TPM Anti-rollback: kernel key)" \
+ "${FROM_IMAGE}" "!Firmware version rollback detected (5->4)" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 1,0x10005,1
+
+test_update "RW update -> fallback to RO+RW Full update (TPM Anti-rollback)" \
+ "${TO_IMAGE}" "!Firmware version rollback detected (4->2)" \
+ -i "${FROM_IMAGE}" -t --wp=0 --sys_props 1,0x10004,1
+
+# Test Try-RW update (vboot1).
+test_update "RW update (vboot1, A->B)" \
+ "${FROM_IMAGE}" "${TMP}.expected.b" \
+ -i "${TO_IMAGE}" -t --wp=1 --sys_props 0,0 --sys_props 0,0x10001,0
+
+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" \
+ -i "${TO_IMAGE}" --mode=legacy
+
+# Test quirks
+test_update "Full update (wrong size)" \
+ "${FROM_IMAGE}.large" "!Image size is different" \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (--quirks enlarge_image)" \
+ "${FROM_IMAGE}.large" "${TMP}.expected.large" --quirks enlarge_image \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (--quirks unlock_me_for_update)" \
+ "${FROM_IMAGE}" "${TMP}.expected.me_unlocked" \
+ --quirks unlock_me_for_update \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1
+
+test_update "Full update (failure by --quirks min_platform_version)" \
+ "${FROM_IMAGE}" "!Need platform version >= 3 (current is 2)" \
+ --quirks min_platform_version=3 \
+ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1,2
+
+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