/* * 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 #include #include #include #include #include #include #include #ifdef HAVE_LIBZIP #include #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: 'image.bin' (or 'bios.bin' as legacy name before CL:1318712) * - 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); VB2_DEBUG("Checking %s\n", 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); VB2_DEBUG("Reading %s\n", 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); VB2_DEBUG("Writing %s\n", 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\n", fname); return 1; } fp = zip_fopen(zip, fname, 0); if (!fp) { ERROR("Failed to open entry in ZIP: %s\n", 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\n", 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; VB2_DEBUG("Writing %s\n", fname); assert(zip); src = zip_source_buffer(zip, data, size, 0); if (!src) { ERROR("Internal error: cannot allocate buffer: %s\n", 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\n", 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\n", path); return NULL; } ar = (struct archive *)malloc(sizeof(*ar)); if (!ar) { ERROR("Internal error: allocation failure.\n"); return NULL; } if (S_ISDIR(path_stat.st_mode)) { VB2_DEBUG("Found directory, use fallback (fs) driver: %s\n", 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 VB2_DEBUG("Found file, use ZIP driver: %s\n", 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\n", path); free(ar); return NULL; #endif } ar->handle = ar->open(path); if (!ar->handle) { ERROR("Failed to open archive: %s\n", 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. */ static 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; INFO("Copying: %s\n", path); if (archive_read_file(arg->from, path, &data, &size)) { ERROR("Failed reading: %s\n", path); return 1; } r = archive_write_file(arg->to, path, data, size); VB2_DEBUG("result=%d\n", 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\n", 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.\n", image->file_name); return -1; } if (gbb->rootkey_size < rootkey_len) { ERROR("New root key (%u bytes) larger than GBB (%u bytes).\n", 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(§ion, image, section_name); if (!section.data) { ERROR("Need section %s in image %s.\n", section_name, image->file_name); return -1; } if (section.size < vblock_len) { ERROR("Section %s too small (%zu bytes) for vblock (%u bytes).\n", 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) { VB2_DEBUG("Loaded file: %s\n", path); r = apply(image, section_name, data, len); if (r) ERROR("Failed applying %s to %s\n", path, section_name); } else { ERROR("Failed reading: %s\n", 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.\n"); 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'; VB2_DEBUG("Found model <%s> setvars: %s\n", model.name, name); if (model_config_parse_setvars_file(&model, archive, name)) { ERROR("Invalid setvars file: %s\n", 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)) { VB2_DEBUG("Ignore non-exist EC image: %s\n", model.ec_image); free(model.ec_image); model.ec_image = NULL; } if (model.pd_image && !archive_has_entry(archive, model.pd_image)) { VB2_DEBUG("Ignore non-exist PD image: %s\n", 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; /* * For manifest with single model defined, we should just return because * there are other mechanisms like platform name check to double confirm * if the firmware is valid. */ if (manifest->num == 1) return &manifest->models[0]; if (!model_name) { sys_model_name = host_shell("mosys platform model"); VB2_DEBUG("System model name: '%s'\n", 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) { if (!*model_name) ERROR("Cannot get model name.\n"); else ERROR("Unsupported model: '%s'.\n", model_name); fprintf(stderr, "You are probably running an image for wrong board, or " "a device in early stage that 'mosys' command is not " "ready, or image from old (or factory) branches that " "Unified Build config is not updated yet for 'mosys'.\n" "Please check command 'mosys platform model', " "which should output one of the supported models below:" "\n"); for (i = 0; i < manifest->num; i++) fprintf(stderr, " %s", manifest->models[i].name); fprintf(stderr, "\n"); } free(sys_model_name); return model; } /* * Determines the signature ID to use for white label. * Returns the signature ID for looking up rootkey and vblock files. * Caller must free the returned string. */ static char *resolve_signature_id(struct model_config *model, const char *image) { int is_unibuild = model->signature_id ? 1 : 0; char *wl_tag = vpd_get_value(image, VPD_WHITELABEL_TAG); char *sig_id = NULL; /* Unified build: $model.$wl_tag, or $model (b/126800200). */ if (is_unibuild) { if (!wl_tag) { WARN("No VPD '%s' set for white label - use model name " "'%s' as default.\n", VPD_WHITELABEL_TAG, model->name); return strdup(model->name); } ASPRINTF(&sig_id, "%s-%s", model->name, wl_tag); free(wl_tag); return sig_id; } /* Non-Unibuild: Upper($wl_tag), or Upper(${cid%%-*}). */ if (!wl_tag) { char *cid = vpd_get_value(image, VPD_CUSTOMIZATION_ID); if (cid) { /* customization_id in format LOEM[-VARIANT]. */ char *dash = strchr(cid, '-'); if (dash) *dash = '\0'; wl_tag = cid; } } if (wl_tag) str_convert(wl_tag, toupper); return wl_tag; } /* * 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) { sig_id = resolve_signature_id(model, image); signature_id = sig_id; } if (signature_id) { VB2_DEBUG("Find white label patches by signature ID: '%s'.\n", signature_id); find_patches_for_model(model, archive, signature_id); } else { signature_id = ""; WARN("No VPD '%s' set for white label - use default keys.\n", VPD_WHITELABEL_TAG); } if (!model->patches.rootkey) { ERROR("No keys found for signature_id: '%s'\n", signature_id); r = 1; } else { INFO("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 host_image_name = "image.bin", * const old_host_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) { const char *image_name = NULL; struct firmware_image image = {0}; /* Try to load from current folder. */ if (archive_has_entry(archive, old_host_image_name)) image_name = old_host_image_name; else if (archive_has_entry(archive, host_image_name)) image_name = host_image_name; else 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; } VB2_DEBUG("%d model(s) loaded.\n", manifest.num); if (!manifest.num) { ERROR("No valid configurations found from archive.\n"); return NULL; } new_manifest = (struct manifest *)malloc(sizeof(manifest)); if (!new_manifest) { ERROR("Internal error: memory allocation error.\n"); 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 ""; key = (struct vb2_packed_key *)((uint8_t *)gbb + offset); if (!packed_key_looks_ok(key, size)) return ""; 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; if (load_firmware_image(&image, fpath, archive)) return; 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\n", 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"); }