/* Copyright 2019 The ChromiumOS Authors * 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 #include #include #include #include #include #if defined (__FreeBSD__) || defined(__OpenBSD__) #include #endif #include "2common.h" #include "host_misc.h" #include "util_misc.h" #include "updater.h" #define COMMAND_BUFFER_SIZE 256 /* * 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; } /* * Fills in the other fields of image using image->data. * Returns IMAGE_LOAD_SUCCESS or IMAGE_PARSE_FAILURE. */ static int parse_firmware_image(struct firmware_image *image) { int ret = IMAGE_LOAD_SUCCESS; const char *section_a = NULL, *section_b = NULL; VB2_DEBUG("Image size: %d\n", image->size); assert(image->data); image->fmap_header = fmap_find(image->data, image->size); if (!image->fmap_header) { ERROR("Invalid image file (missing FMAP): %s\n", image->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", image->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; } /* * 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 u_archive *archive) { 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; } image->file_name = strdup(file_name); return parse_firmware_image(image); } /* * 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; } /* * Returns true if the write protection is enabled on current system. */ int is_write_protection_enabled(struct updater_config *cfg) { /* Assume HW/SW WP are enabled if -1 error code is returned */ return dut_get_property(DUT_PROP_WP_HW, cfg) && dut_get_property(DUT_PROP_WP_SW, cfg); } /* * 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); } void prepare_servo_control(const char *control_name, int on) { char *cmd; if (!control_name) return; ASPRINTF(&cmd, "dut-control %s:%s", control_name, on ? "on" : "off"); free(host_shell(cmd)); free(cmd); } /* * 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(const char **prepare_ctrl_name) { const char *servo_port = getenv(ENV_SERVOD_PORT); const char *servo_name = getenv(ENV_SERVOD_NAME); char *servo_type = host_shell("dut-control -o servo_type 2>/dev/null"); const char *programmer = NULL; char *ret = NULL; char *servo_serial = NULL; static const char * const raiden_debug_spi = "raiden_debug_spi"; static const char * const cpu_fw_spi = "cpu_fw_spi"; static const char * const ccd_cpu_fw_spi = "ccd_cpu_fw_spi"; /* By default, no control is needed. */ *prepare_ctrl_name = NULL; VB2_DEBUG("servo_type: %s\n", servo_type); /* Get serial name if servo port is provided. */ if ((servo_port && *servo_port) || (servo_name && *servo_name)) { const char *cmd = "dut-control -o serialname 2>/dev/null"; VB2_DEBUG("Select servod using port: %s or name: %s\n", servo_port, servo_name); if (strstr(servo_type, "with_servo_micro")) cmd = ("dut-control -o servo_micro_serialname" " 2>/dev/null"); else if (strstr(servo_type, "with_c2d2")) cmd = ("dut-control -o c2d2_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); } /* servo_type names: chromite/lib/firmware/servo_lib.py */ 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 (strcmp(servo_type, "servo_v2") == 0) { VB2_DEBUG("Selected Servo V2.\n"); programmer = "ft2232_spi:type=google-servo-v2"; *prepare_ctrl_name = cpu_fw_spi; } else if (strstr(servo_type, "servo_micro")) { VB2_DEBUG("Selected Servo Micro.\n"); programmer = raiden_debug_spi; *prepare_ctrl_name = cpu_fw_spi; } else if (strstr(servo_type, "c2d2")) { VB2_DEBUG("Selected C2D2.\n"); programmer = raiden_debug_spi; *prepare_ctrl_name = cpu_fw_spi; } else if (strstr(servo_type, "ccd_cr50") || strstr(servo_type, "ccd_gsc") || strstr(servo_type, "ccd_ti50")) { VB2_DEBUG("Selected CCD.\n"); programmer = "raiden_debug_spi:target=AP,custom_rst=true"; *prepare_ctrl_name = ccd_cpu_fw_spi; } else { WARN("Unknown servo: %s\nAssuming debug header.\n", servo_type); programmer = raiden_debug_spi; *prepare_ctrl_name = cpu_fw_spi; } 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); return ret; } /* * Returns 1 if the programmers in image1 and image2 are the same. */ static int is_the_same_programmer(const struct firmware_image *image1, const struct firmware_image *image2) { assert(image1 && image2); /* Including if both are NULL. */ if (image1->programmer == image2->programmer) return 1; /* Not the same if either one is NULL. */ if (!image1->programmer || !image2->programmer) return 0; return strcmp(image1->programmer, image2->programmer) == 0; } enum flash_command { FLASH_READ = 0, FLASH_WRITE, }; /* Converts the flashrom_params to an equivalent flashrom command. */ static char *get_flashrom_command(enum flash_command flash_cmd, struct flashrom_params *params, const char *image_name, const char *contents_name) { int i, len = 0; char *partial = NULL; char *cmd = NULL; if (!image_name) image_name = ""; if (!contents_name) contents_name = ""; for (i = 0; params->regions && params->regions[i]; i++) len += strlen(params->regions[i]) + strlen(" -i "); if (len) { partial = (char *)malloc(len + 1); if (!partial) { ERROR("Failed to allocate a string buffer.\n"); return NULL; } partial[0] = '\0'; for (i = 0; params->regions[i]; i++) { strcat(partial, " -i "); strcat(partial, params->regions[i]); } assert(strlen(partial) == len); } switch (flash_cmd) { case FLASH_READ: ASPRINTF(&cmd, "flashrom -r %s -p %s%s%s", image_name, params->image->programmer, params->verbose > 1 ? " -V" : "", partial ? partial : ""); break; case FLASH_WRITE: ASPRINTF(&cmd, "flashrom -w %s -p %s%s%s%s%s%s", image_name, params->image->programmer, params->flash_contents ? " --flash-contents " : "", params->flash_contents ? contents_name : "", params->noverify ? " --noverify" : "", params->verbose > 1 ? " -V" : "", partial ? partial : ""); break; default: ERROR("Unknown command: %d.\n", flash_cmd); break; } free(partial); return cmd; } int load_system_firmware(struct updater_config *cfg, struct firmware_image *image) { int r, i; char *cmd; const int tries = 1 + get_config_quirk(QUIRK_EXTRA_RETRIES, cfg); struct flashrom_params params = {0}; params.image = image; params.verbose = cfg->verbosity + 1; /* libflashrom verbose 1 = WARN. */ cmd = get_flashrom_command(FLASH_READ, ¶ms, NULL, NULL); INFO("%s\n", cmd); free(cmd); for (i = 1, r = -1; i <= tries && r != 0; i++, params.verbose++) { if (i > 1) WARN("Retry reading firmware (%d/%d)...\n", i, tries); r = flashrom_read_image(image, NULL, params.verbose); } if (!r) r = parse_firmware_image(image); return r; } /* * Writes sections from a given firmware image to the system firmware. * Regions should be NULL for writing the whole image, or a list of * FMAP section names (and ended with a NULL). * Returns 0 if success, non-zero if error. */ int write_system_firmware(struct updater_config *cfg, const struct firmware_image *image, const char * const sections[]) { int r = 0, i; char *cmd; const int tries = 1 + get_config_quirk(QUIRK_EXTRA_RETRIES, cfg); struct flashrom_params params = {0}; struct firmware_image *flash_contents = NULL; if (cfg->use_diff_image && cfg->image_current.data && is_the_same_programmer(&cfg->image_current, image)) flash_contents = &cfg->image_current; params.image = (struct firmware_image *)image; params.flash_contents = flash_contents; params.regions = sections; params.noverify = !cfg->do_verify; params.noverify_all = true; params.verbose = cfg->verbosity + 1; /* libflashrom verbose 1 = WARN. */ cmd = get_flashrom_command(FLASH_WRITE, ¶ms, NULL, NULL); INFO("%s\n", cmd); free(cmd); for (i = 1, r = -1; i <= tries && r != 0; i++, params.verbose++) { if (i > 1) WARN("Retry writing firmware (%d/%d)...\n", i, tries); r = flashrom_write_image(image, sections, flash_contents, !params.noverify, params.verbose); /* * Force a newline to flush stdout in case if * flashrom_write_image left some messages in the buffer. */ fprintf(stdout, "\n"); } return r; } /* * 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); } /* * Overwrite the given offset of a section in the firmware image with the * given values. * Returns 0 on success, otherwise failure. */ static int overwrite_section(struct firmware_image *image, const char *fmap_section, size_t offset, size_t size, const uint8_t *new_values) { struct firmware_section section; find_firmware_section(§ion, image, fmap_section); if (section.size < offset + size) { ERROR("Section smaller than given offset + size\n"); return -1; } if (memcmp(section.data + offset, new_values, size) == 0) { VB2_DEBUG("Section already contains given values.\n"); return 0; } memcpy(section.data + offset, new_values, size); return 0; } /* * Unlock the Flash Master values in SI_DESC. * Returns 0 on success, otherwise failure. * * TODO(b/270275115): Replace with a call to ifdtool. */ int unlock_flash_master(struct firmware_image *image) { const int flash_master_offset = 0x80; const uint8_t flash_master[] = { 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff }; if (overwrite_section(image, FMAP_SI_DESC, flash_master_offset, ARRAY_SIZE(flash_master), flash_master)) { ERROR("Failed unlocking Flash Master values\n"); return -1; } INFO("Changed Flash Master Values to unlocked.\n"); return 0; } /* * Disable GPR0 (Global Protected Range). When enabled, it provides * write-protection to part of the SI_ME region, specifically CSE_RO and * part of CSE_DATA, so it must be disabled to allow updating SI_ME. * Returns 0 on success, otherwise failure. * * TODO(b/270275115): Replace with a call to ifdtool. */ static int disable_gpr0(struct firmware_image *image) { const int gpr0_offset = 0x154; const uint8_t gpr0_value_disabled[] = { 0x00, 0x00, 0x00, 0x00 }; if (overwrite_section(image, FMAP_SI_DESC, gpr0_offset, ARRAY_SIZE(gpr0_value_disabled), gpr0_value_disabled)) { ERROR("Failed disabling GPR0.\n"); return -1; } INFO("Disabled GPR0.\n"); return 0; } /* * Unlock the Intel ME by: * - Unlocking the FLMSTR values in the descriptor. * - Disabling GPR0 in the descriptor. * This allows the SI_DESC and SI_ME regions to be updated. * Returns 0 on success, otherwise failure. */ int unlock_me(struct firmware_image *image) { if (unlock_flash_master(image)) return -1; if (disable_gpr0(image)) return -1; INFO("Unlocked Intel ME.\n"); return 0; }