| /* Copyright 2019 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 utility functions for firmware updater. |
| */ |
| |
| #include <assert.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "2common.h" |
| #include "crossystem.h" |
| #include "host_misc.h" |
| #include "util_misc.h" |
| #include "updater.h" |
| |
| #define COMMAND_BUFFER_SIZE 256 |
| #define FLASHROM_OUTPUT_WP_PATTERN "write protect is " |
| |
| enum flashrom_ops { |
| FLASHROM_READ, |
| FLASHROM_WRITE, |
| FLASHROM_WP_STATUS, |
| }; |
| |
| /* System environment values. */ |
| static const char * const STR_REV = "rev", |
| * const FLASHROM_OUTPUT_WP_ENABLED = |
| FLASHROM_OUTPUT_WP_PATTERN "enabled", |
| * const FLASHROM_OUTPUT_WP_DISABLED = |
| FLASHROM_OUTPUT_WP_PATTERN "disabled"; |
| |
| /* |
| * Strips a string (usually from shell execution output) by removing all the |
| * trailing characters in pattern. If pattern is NULL, match by space type |
| * characters (space, new line, tab, ... etc). |
| */ |
| void strip_string(char *s, const char *pattern) |
| { |
| int len; |
| assert(s); |
| |
| len = strlen(s); |
| while (len-- > 0) { |
| if (pattern) { |
| if (!strchr(pattern, s[len])) |
| break; |
| } else { |
| if (!isascii(s[len]) || !isspace(s[len])) |
| break; |
| } |
| s[len] = '\0'; |
| } |
| } |
| |
| /* |
| * Saves everything from stdin to given output file. |
| * Returns 0 on success, otherwise failure. |
| */ |
| int save_file_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); |
| if (fwrite(buffer, 1, sz, out) != sz) { |
| fclose(out); |
| return -1; |
| } |
| } |
| fclose(out); |
| return 0; |
| } |
| |
| /* |
| * Returns 1 if a given file (cbfs_entry_name) exists inside a particular CBFS |
| * section of an image file, otherwise 0. |
| */ |
| 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; |
| } |
| |
| /* |
| * Extracts files from a CBFS on given region (section) of image_file. |
| * Returns the path to a temporary file on success, otherwise NULL. |
| */ |
| const char *cbfs_extract_file(const char *image_file, |
| const char *cbfs_region, |
| const char *cbfs_name, |
| struct tempfile *tempfiles) |
| { |
| const char *output = create_temp_file(tempfiles); |
| char *command, *result; |
| |
| if (!output) |
| return NULL; |
| |
| 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; |
| } |
| |
| /* |
| * Loads the firmware information from an FMAP section in loaded firmware image. |
| * The section should only contain ASCIIZ string as firmware version. |
| * Returns 0 if a non-empty version string is stored in *version, otherwise -1. |
| */ |
| static int load_firmware_version(struct firmware_image *image, |
| const char *section_name, |
| char **version) |
| { |
| struct firmware_section fwid; |
| int len = 0; |
| |
| /* |
| * section_name is NULL when parsing the RW versions on a non-vboot |
| * image (and already warned in load_firmware_image). We still need to |
| * initialize *version with empty string. |
| */ |
| if (section_name) { |
| find_firmware_section(&fwid, image, section_name); |
| if (fwid.size) |
| len = fwid.size; |
| else |
| WARN("No valid section '%s', missing version info.\n", |
| section_name); |
| } |
| |
| if (!len) { |
| *version = strdup(""); |
| return -1; |
| } |
| |
| /* |
| * For 'system current' images, the version string may contain |
| * invalid characters that we do want to strip. |
| */ |
| *version = strndup((const char *)fwid.data, len); |
| strip_string(*version, "\xff"); |
| return 0; |
| } |
| |
| /* |
| * Loads a firmware image from file. |
| * If archive is provided and file_name is a relative path, read the file from |
| * archive. |
| * Returns IMAGE_LOAD_SUCCESS on success, IMAGE_READ_FAILURE on file I/O |
| * failure, or IMAGE_PARSE_FAILURE for non-vboot images. |
| */ |
| int load_firmware_image(struct firmware_image *image, const char *file_name, |
| struct archive *archive) |
| { |
| int ret = IMAGE_LOAD_SUCCESS; |
| const char *section_a = NULL, *section_b = NULL; |
| |
| if (!file_name) { |
| ERROR("No file name given\n"); |
| return IMAGE_READ_FAILURE; |
| } |
| |
| VB2_DEBUG("Load image file from %s...\n", file_name); |
| |
| if (!archive_has_entry(archive, file_name)) { |
| ERROR("Does not exist: %s\n", file_name); |
| return IMAGE_READ_FAILURE; |
| } |
| if (archive_read_file(archive, file_name, &image->data, &image->size, |
| NULL) != VB2_SUCCESS) { |
| ERROR("Failed to load %s\n", file_name); |
| return IMAGE_READ_FAILURE; |
| } |
| |
| VB2_DEBUG("Image size: %d\n", 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\n", file_name); |
| ret = IMAGE_PARSE_FAILURE; |
| } |
| |
| if (load_firmware_version(image, FMAP_RO_FRID, &image->ro_version)) |
| ret = IMAGE_PARSE_FAILURE; |
| |
| if (firmware_section_exists(image, FMAP_RW_FWID_A)) { |
| section_a = FMAP_RW_FWID_A; |
| section_b = FMAP_RW_FWID_B; |
| } else if (firmware_section_exists(image, FMAP_RW_FWID)) { |
| section_a = FMAP_RW_FWID; |
| section_b = FMAP_RW_FWID; |
| } else if (!ret) { |
| ERROR("Unsupported VBoot firmware (no RW ID): %s\n", file_name); |
| ret = IMAGE_PARSE_FAILURE; |
| } |
| |
| /* |
| * Load and initialize both RW A and B sections. |
| * Note some unit tests will create only RW A. |
| */ |
| load_firmware_version(image, section_a, &image->rw_version_a); |
| load_firmware_version(image, section_b, &image->rw_version_b); |
| |
| return ret; |
| } |
| |
| /* |
| * Generates a temporary file for snapshot of firmware image contents. |
| * |
| * Returns a file path if success, otherwise NULL. |
| */ |
| const char *get_firmware_image_temp_file(const struct firmware_image *image, |
| struct tempfile *tempfiles) |
| { |
| const char *tmp_path = create_temp_file(tempfiles); |
| if (!tmp_path) |
| return NULL; |
| |
| if (vb2_write_file(tmp_path, image->data, image->size) != VB2_SUCCESS) { |
| ERROR("Failed writing %s firmware image (%u bytes) to %s.\n", |
| image->programmer ? image->programmer : "temp", |
| image->size, tmp_path); |
| return NULL; |
| } |
| return tmp_path; |
| } |
| |
| /* |
| * Frees the allocated resource from a firmware image object. |
| */ |
| void free_firmware_image(struct firmware_image *image) |
| { |
| /* |
| * The programmer is not allocated by load_firmware_image and must be |
| * preserved explicitly. |
| */ |
| const char *programmer = image->programmer; |
| |
| 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)); |
| image->programmer = programmer; |
| } |
| |
| /* |
| * 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. |
| */ |
| int firmware_section_exists(const struct firmware_image *image, |
| const char *section_name) |
| { |
| struct firmware_section section; |
| find_firmware_section(§ion, image, section_name); |
| return section.data != NULL; |
| } |
| |
| /* |
| * 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) { |
| VB2_DEBUG("Cannot find section %.*s: from=%p, to=%p\n", |
| FMAP_NAMELEN, section_name, from.data, to.data); |
| return -1; |
| } |
| if (from.size > to.size) { |
| WARN("Section %.*s is truncated after updated.\n", |
| FMAP_NAMELEN, section_name); |
| } |
| /* Use memmove in case if we need to deal with sections that overlap. */ |
| memmove(to.data, from.data, VB2_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(§ion, image, FMAP_RO_GBB); |
| gbb_header = (struct vb2_gbb_header *)section.data; |
| if (!futil_valid_gbb_header(gbb_header, section.size, NULL)) { |
| ERROR("Cannot find GBB in image: %s.\n", image->file_name); |
| return NULL; |
| } |
| return gbb_header; |
| } |
| |
| /* |
| * 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"); |
| |
| VB2_DEBUG("%s\n", command); |
| buf[0] = '\0'; |
| if (!fp) { |
| VB2_DEBUG("Execution error for %s.\n", command); |
| return strdup(buf); |
| } |
| |
| if (fgets(buf, sizeof(buf), fp)) |
| strip_string(buf, NULL); |
| result = pclose(fp); |
| if (!WIFEXITED(result) || WEXITSTATUS(result) != 0) { |
| VB2_DEBUG("Execution failure with exit code %d: %s\n", |
| 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(void) |
| { |
| 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(void) |
| { |
| return VbGetSystemPropertyInt("tpm_fwver"); |
| } |
| |
| /* A helper function to return the "hardware write protection" status. */ |
| static int host_get_wp_hw(void) |
| { |
| /* wpsw refers to write protection 'switch', not 'software'. */ |
| return VbGetSystemPropertyInt("wpsw_cur"); |
| } |
| |
| /* A helper function to return "fw_vboot2" system property. */ |
| static int host_get_fw_vboot2(void) |
| { |
| return VbGetSystemPropertyInt("fw_vboot2"); |
| } |
| |
| /* A help function to get $(mosys platform version). */ |
| static int host_get_platform_version(void) |
| { |
| char *result = host_shell("mosys platform version"); |
| long rev = -1; |
| |
| /* Result should be 'revN' */ |
| if (strncmp(result, STR_REV, strlen(STR_REV)) == 0) |
| rev = strtol(result + strlen(STR_REV), NULL, 0); |
| |
| /* we should never have negative or extremely large versions, |
| * but clamp just to be sure |
| */ |
| if (rev < 0) |
| rev = 0; |
| if (rev > INT_MAX) |
| rev = INT_MAX; |
| |
| VB2_DEBUG("Raw data = [%s], parsed version is %ld\n", result, rev); |
| |
| free(result); |
| return rev; |
| } |
| |
| /* |
| * Helper function to detect type of Servo board attached to host. |
| * Returns a string as programmer parameter on success, otherwise NULL. |
| */ |
| char *host_detect_servo(int *need_prepare_ptr) |
| { |
| const char *servo_port = getenv(ENV_SERVOD_PORT); |
| char *servo_type = host_shell("dut-control -o servo_type 2>/dev/null"); |
| const char *programmer = NULL; |
| char *ret = NULL; |
| int need_prepare = 0; /* To prepare by dut-control cpu_fw_spi:on */ |
| char *servo_serial = NULL; |
| |
| /* Get serial name if servo port is provided. */ |
| if (servo_port && *servo_port) { |
| const char *cmd = "dut-control -o serialname 2>/dev/null"; |
| |
| VB2_DEBUG("Select servod using port: %s\n", servo_port); |
| if (strstr(servo_type, "with_servo_micro")) |
| cmd = ("dut-control -o servo_micro_serialname" |
| " 2>/dev/null"); |
| else if (strstr(servo_type, "with_ccd")) |
| cmd = "dut-control -o ccd_serialname 2>/dev/null"; |
| |
| servo_serial = host_shell(cmd); |
| VB2_DEBUG("Servo SN=%s (serial cmd: %s)\n", servo_serial, cmd); |
| } |
| |
| if (!*servo_type) { |
| ERROR("Failed to get servo type. Check servod.\n"); |
| } else if (servo_serial && !*servo_serial) { |
| ERROR("Failed to get serial at servo port %s.\n", servo_port); |
| } else if (strstr(servo_type, "servo_micro")) { |
| VB2_DEBUG("Selected Servo Micro.\n"); |
| programmer = "raiden_debug_spi"; |
| need_prepare = 1; |
| } else if (strstr(servo_type, "ccd_cr50")) { |
| VB2_DEBUG("Selected CCD CR50.\n"); |
| programmer = "raiden_debug_spi:target=AP"; |
| } else { |
| VB2_DEBUG("Selected Servo V2.\n"); |
| programmer = "ft2232_spi:type=google-servo-v2"; |
| need_prepare = 1; |
| } |
| |
| if (programmer) { |
| if (!servo_serial) { |
| ret = strdup(programmer); |
| } else { |
| const char prefix = strchr(programmer, ':') ? ',' : ':'; |
| ASPRINTF(&ret, "%s%cserial=%s", programmer, prefix, |
| servo_serial); |
| } |
| VB2_DEBUG("Servo programmer: %s\n", ret); |
| } |
| |
| free(servo_type); |
| free(servo_serial); |
| *need_prepare_ptr = need_prepare; |
| |
| return ret; |
| } |
| |
| /* |
| * 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, const char *extra) |
| { |
| char *command, *result; |
| const char *op_cmd, *dash_i = "-i", *postfix = ""; |
| 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; |
| } |
| |
| if (!extra) |
| extra = ""; |
| |
| /* 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, extra, |
| postfix); |
| |
| if (verbose) |
| INFO("Executing: %s\n", command); |
| |
| if (op != FLASHROM_WP_STATUS) { |
| r = system(command); |
| free(command); |
| if (r) |
| ERROR("Error code: %d\n", r); |
| return r; |
| } |
| |
| result = host_shell(command); |
| strip_string(result, NULL); |
| free(command); |
| VB2_DEBUG("wp-status: %s\n", result); |
| |
| if (strstr(result, FLASHROM_OUTPUT_WP_ENABLED)) |
| r = WP_ENABLED; |
| else if (strstr(result, FLASHROM_OUTPUT_WP_DISABLED)) |
| r = WP_DISABLED; |
| else |
| r = WP_ERROR; |
| free(result); |
| return r; |
| } |
| |
| /* Helper function to return write protection status via given programmer. */ |
| enum wp_state host_get_wp(const char *programmer) |
| { |
| return host_flashrom(FLASHROM_WP_STATUS, NULL, programmer, 0, NULL, |
| NULL); |
| } |
| |
| /* Helper function to return host software write protection status. */ |
| static int host_get_wp_sw(void) |
| { |
| return host_get_wp(PROG_HOST); |
| } |
| |
| /* |
| * Loads the active system firmware image (usually from SPI flash chip). |
| * Returns 0 if success, non-zero if error. |
| */ |
| int load_system_firmware(struct firmware_image *image, |
| struct tempfile *tempfiles, int verbosity) |
| { |
| int r; |
| const char *tmp_path = create_temp_file(tempfiles); |
| |
| if (!tmp_path) |
| return -1; |
| |
| r = host_flashrom(FLASHROM_READ, tmp_path, image->programmer, |
| verbosity, NULL, NULL); |
| if (!r) |
| r = load_firmware_image(image, tmp_path, NULL); |
| return r; |
| } |
| |
| /* |
| * 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. |
| */ |
| int write_system_firmware(const struct firmware_image *image, |
| const struct firmware_image *diff_image, |
| const char *section_name, |
| struct tempfile *tempfiles, |
| int verbosity) |
| { |
| const char *tmp_path = get_firmware_image_temp_file(image, tempfiles); |
| const char *tmp_diff = NULL; |
| |
| const char *programmer = image->programmer; |
| char *extra = NULL; |
| int r; |
| |
| if (!tmp_path) |
| return -1; |
| |
| if (diff_image) { |
| tmp_diff = get_firmware_image_temp_file( |
| diff_image, tempfiles); |
| if (!tmp_diff) |
| return -1; |
| ASPRINTF(&extra, "--noverify --diff=%s", tmp_diff); |
| } |
| |
| r = host_flashrom(FLASHROM_WRITE, tmp_path, programmer, verbosity, |
| section_name, extra); |
| free(extra); |
| return r; |
| } |
| |
| /* Helper function to configure all properties. */ |
| void init_system_properties(struct system_property *props, int num) |
| { |
| memset(props, 0, num * sizeof(*props)); |
| assert(num >= SYS_PROP_MAX); |
| 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; |
| } |
| |
| /* |
| * Helper function to create a new temporary file. |
| * All files created will be removed remove_all_temp_files(). |
| * Returns the path of new file, or NULL on failure. |
| */ |
| const char *create_temp_file(struct tempfile *head) |
| { |
| struct tempfile *new_temp; |
| char new_path[] = P_tmpdir "/fwupdater.XXXXXX"; |
| int fd; |
| mode_t umask_save; |
| |
| /* Set the umask before mkstemp for security considerations. */ |
| umask_save = umask(077); |
| fd = mkstemp(new_path); |
| umask(umask_save); |
| if (fd < 0) { |
| ERROR("Failed to create new temp file in %s\n", 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.\n"); |
| return NULL; |
| } |
| VB2_DEBUG("Created new temporary file: %s.\n", new_path); |
| new_temp->next = NULL; |
| while (head->next) |
| head = head->next; |
| head->next = 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. |
| */ |
| void remove_all_temp_files(struct tempfile *head) |
| { |
| /* head itself is dummy and should not be removed. */ |
| assert(!head->filepath); |
| struct tempfile *next = head->next; |
| head->next = NULL; |
| while (next) { |
| head = next; |
| next = head->next; |
| assert(head->filepath); |
| VB2_DEBUG("Remove temporary file: %s.\n", head->filepath); |
| remove(head->filepath); |
| free(head->filepath); |
| free(head); |
| } |
| } |
| |
| /* |
| * Returns rootkey hash of firmware image, or NULL on failure. |
| */ |
| const char *get_firmware_rootkey_hash(const struct firmware_image *image) |
| { |
| const struct vb2_gbb_header *gbb = NULL; |
| const struct vb2_packed_key *rootkey = NULL; |
| |
| assert(image->data); |
| |
| gbb = find_gbb(image); |
| if (!gbb) { |
| WARN("No GBB found in image.\n"); |
| return NULL; |
| } |
| |
| rootkey = get_rootkey(gbb); |
| if (!rootkey) { |
| WARN("No rootkey found in image.\n"); |
| return NULL; |
| } |
| |
| return packed_key_sha1_string(rootkey); |
| } |