1#!/usr/bin/python3 2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""A module to support automated testing of ChromeOS firmware. 6 7Utilizes services provided by saft_flashrom_util.py read/write the 8flashrom chip and to parse the flash rom image. 9 10See docstring for FlashromHandler class below. 11""" 12 13import hashlib 14import logging 15import os 16import struct 17import tempfile 18 19import six 20 21import common 22 23from autotest_lib.client.common_lib import error 24from autotest_lib.client.common_lib.cros import chip_utils 25from autotest_lib.client.cros.faft.utils import saft_flashrom_util 26 27 28class FvSection(object): 29 """An object to hold information about a firmware section. 30 31 This includes file names for the signature header and the body, and the 32 version number. 33 """ 34 35 def __init__(self, sig_name, body_name, fwid_name=None): 36 """ 37 @param sig_name: name of signature section in fmap 38 @param body_name: name of body section in fmap 39 @param fwid_name: name of fwid section in fmap 40 @type sig_name: str | None 41 @type body_name: str | None 42 @type fwid_name: str | None 43 """ 44 self._sig_name = sig_name 45 self._body_name = body_name 46 self._fwid_name = fwid_name 47 self._version = -1 # Is not set on construction. 48 self._flags = 0 # Is not set on construction. 49 self._sha = None # Is not set on construction. 50 self._sig_sha = None # Is not set on construction. 51 self._datakey_version = -1 # Is not set on construction. 52 self._kernel_subkey_version = -1 # Is not set on construction. 53 54 def names(self): 55 """Return the desired file names for the signature, body, and fwid.""" 56 return (self._sig_name, self._body_name, self._fwid_name) 57 58 def get_sig_name(self): 59 return self._sig_name 60 61 def get_body_name(self): 62 return self._body_name 63 64 def get_fwid_name(self): 65 return self._fwid_name 66 67 def get_version(self): 68 return self._version 69 70 def get_flags(self): 71 return self._flags 72 73 def get_sha(self): 74 return self._sha 75 76 def get_sig_sha(self): 77 return self._sig_sha 78 79 def get_datakey_version(self): 80 return self._datakey_version 81 82 def get_kernel_subkey_version(self): 83 return self._kernel_subkey_version 84 85 def set_version(self, version): 86 self._version = version 87 88 def set_flags(self, flags): 89 self._flags = flags 90 91 def set_sha(self, sha): 92 self._sha = sha 93 94 def set_sig_sha(self, sha): 95 self._sig_sha = sha 96 97 def set_datakey_version(self, version): 98 self._datakey_version = version 99 100 def set_kernel_subkey_version(self, version): 101 self._kernel_subkey_version = version 102 103 104class FlashromHandlerError(Exception): 105 """An object to represent Flashrom errors""" 106 pass 107 108 109class _FlashromErrorWrapper(object): 110 """Wrap calls to flashrom, giving cleaner error messages. 111 112 @param description: The start of the failure message ("Failed to <verb>...") 113 """ 114 def __init__(self, description): 115 self.description = description 116 117 def __enter__(self): 118 """Enter the context""" 119 return self 120 121 def __exit__(self, exc_type, exc_val, exc_tb): 122 """Exit the context, converting CmdError into summarized errors. 123 124 @raise FlashromHandlerError: if the wrapped code raises CmdError. 125 """ 126 if not isinstance(exc_val, error.CmdError): 127 del exc_tb 128 return 129 result = exc_val.result_obj 130 lines = [result.command] 131 for line in result.stdout.splitlines(): 132 # grab anything mentioning 'status', but avoid very long lines 133 if len(line) < 80 and 'status' in line.lower(): 134 lines.append(line) 135 lines.extend(result.stderr.splitlines()) 136 137 err_msg = '%s.' % self.description.rstrip('.') 138 err_msg += ' [summarized output, check rpc server log for full]:' 139 err_msg += ' [rc=%s] ' % result.exit_status 140 err_msg += ' || '.join(lines) 141 err = FlashromHandlerError(err_msg) 142 try: 143 six.reraise(type(err), err, exc_tb) 144 finally: 145 del exc_tb 146 147 148class FlashromHandler(object): 149 """An object to provide logical services for automated flashrom testing.""" 150 151 DELTA = 1 # value to add to a byte to corrupt a section contents 152 153 # File in the state directory to store public root key. 154 PUB_KEY_FILE_NAME = 'root.pubkey' 155 FW_KEYBLOCK_FILE_NAME = 'firmware.keyblock' 156 FW_PRIV_DATA_KEY_FILE_NAME = 'firmware_data_key.vbprivk' 157 KERNEL_SUBKEY_FILE_NAME = 'kernel_subkey.vbpubk' 158 EC_EFS_KEY_FILE_NAME = 'key_ec_efs.vbprik2' 159 FWID_MOD_DELIMITER = '~' 160 161 def __init__( 162 self, 163 os_if, 164 pub_key_file=None, 165 dev_key_path='./', 166 target='bios', 167 subdir=None 168 ): 169 """The flashrom handler is not fully initialized upon creation 170 171 @param os_if: an object providing interface to OS services 172 @param pub_key_file: the name of the file contaning a public key to 173 use for verifying both existing and new firmware. 174 @param dev_key_path: path to directory containing *.vpubk and *.vbprivk 175 files, for use in signing 176 @param target: flashrom target ('bios' or 'ec') 177 @param subdir: name of subdirectory of state dir, to use for sections 178 Default: same as target, resulting in 179 '/usr/local/tmp/faft/bios' 180 @type os_if: client.cros.faft.utils.os_interface.OSInterface 181 @type pub_key_file: str | None 182 @type dev_key_path: str 183 @type target: str 184 """ 185 self.fum = None 186 self.image = '' 187 self.os_if = os_if 188 self.initialized = False 189 self._available = None 190 self._unavailable_err = None 191 192 if subdir is None: 193 subdir = target 194 self.subdir = subdir 195 196 self.pub_key_file = pub_key_file 197 self.dev_key_path = dev_key_path 198 199 self.target = target 200 if self.target == 'bios': 201 self.fum = saft_flashrom_util.flashrom_util( 202 self.os_if, target_is_ec=False) 203 self.fv_sections = { 204 'ro': FvSection(None, None, 'RO_FRID'), 205 'a': FvSection('VBOOTA', 'FVMAIN', 'RW_FWID_A'), 206 'b': FvSection('VBOOTB', 'FVMAINB', 'RW_FWID_B'), 207 'me_a': FvSection(None, 'ME_RW_A'), 208 'me_b': FvSection(None, 'ME_RW_B'), 209 'rec': FvSection(None, 'RECOVERY_MRC_CACHE'), 210 'ec_a': FvSection(None, 'ECMAINA'), 211 'ec_b': FvSection(None, 'ECMAINB'), 212 'rw_legacy': FvSection(None, 'RW_LEGACY'), 213 } 214 elif self.target == 'ec': 215 self.fum = saft_flashrom_util.flashrom_util( 216 self.os_if, target_is_ec=True) 217 self.fv_sections = { 218 'ro': FvSection(None, None, 'RO_FRID'), 219 'rw': FvSection(None, 'EC_RW', 'RW_FWID'), 220 'rw_b': FvSection(None, 'EC_RW_B'), 221 } 222 else: 223 raise FlashromHandlerError("Invalid target.") 224 225 def is_available(self): 226 """Check if the programmer is available, by specifying no commands. 227 228 @rtype: bool 229 """ 230 if self._available is None: 231 # Cache the status to avoid trying flashrom every time. 232 try: 233 self.fum.check_target() 234 self._available = True 235 except error.CmdError as e: 236 # First line: "Command <flashrom -p host> failed, rc=2" 237 self._unavailable_err = str(e).split('\n', 1)[0] 238 self._available = False 239 return self._available 240 241 def section_file(self, *paths): 242 """ 243 Return a full path for the given basename, in this handler's subdir. 244 Example: subdir 'bios' -> '/usr/local/tmp/faft/bios/FV_GBB' 245 246 @param paths: variable number of path pieces, same as in os.path.join 247 @return: an absolute path from this handler's subdir and the pieces. 248 """ 249 if any(os.path.isabs(x) for x in paths): 250 raise FlashromHandlerError( 251 "Absolute paths are not allowed in section_file()") 252 253 return os.path.join(self.os_if.state_dir, self.subdir, *paths) 254 255 def init(self, image_file=None, allow_fallback=False): 256 """Initialize the object, by reading the image. 257 258 This is separate from new_image, to isolate the implementation detail of 259 self.image being non-empty. 260 261 @param image_file: the path of the image file to read. 262 If None or empty string, read the flash device instead. 263 @param allow_fallback: if True, fall back to reading the flash device 264 if the image file doesn't exist. 265 @type image_file: str 266 @type allow_fallback: bool 267 268 @raise FlashromHandlerError: if no target flash device was usable. 269 """ 270 # Raise an exception early if there's no usable flash. 271 if not self.is_available(): 272 # Can't tell for sure whether it's broken or simply nonexistent. 273 raise FlashromHandlerError( 274 "Could not detect a usable %s flash device: %s." 275 % (self.target, self._unavailable_err)) 276 277 if image_file and allow_fallback and not os.path.isfile(image_file): 278 logging.info( 279 "Using %s flash contents instead of missing image: %s", 280 self.target.upper(), image_file) 281 image_file = None 282 283 self.new_image(image_file) 284 self.initialized = True 285 286 def deinit(self): 287 """Clear the in-memory image data, and mark self uninitialized.""" 288 self.image = '' 289 self.os_if.remove_dir(self.section_file()) 290 self.initialized = False 291 292 def dump_flash(self, target_filename): 293 """Copy the flash device's data into a file, but don't parse it. 294 295 @param target_filename: the file to create 296 """ 297 self.fum.dump_flash(target_filename) 298 299 def new_image(self, image_file=None): 300 """Parse the full flashrom image and store sections into files. 301 302 @param image_file: the name of the file containing a full ChromeOS 303 flashrom image. If not passed in or empty, the actual 304 flash device is read and its contents are saved into a 305 temporary file which is used instead. 306 @type image_file: str | None 307 308 The input file is parsed and the sections of importance (as defined in 309 self.fv_sections) are saved in separate files in the state directory 310 as defined in the os_if object. 311 """ 312 313 if image_file: 314 with open(image_file, 'rb') as image_f: 315 self.image = image_f.read() 316 self.fum.set_firmware_layout(image_file) 317 else: 318 self.image = self.fum.read_whole() 319 320 self.os_if.create_dir(self.section_file()) 321 322 for section in self.fv_sections.values(): 323 for subsection_name in section.names(): 324 if not subsection_name: 325 continue 326 blob = self.fum.get_section(self.image, subsection_name) 327 if blob: 328 blob_filename = self.section_file(subsection_name) 329 with open(blob_filename, 'wb') as blob_f: 330 blob_f.write(blob) 331 332 blob = self.fum.get_section(self.image, section.get_body_name()) 333 if blob: 334 s = hashlib.sha1() 335 s.update(blob) 336 section.set_sha(s.hexdigest()) 337 338 # If there is no "sig" subsection, skip reading version and flags. 339 if not section.get_sig_name(): 340 continue 341 342 # Now determine this section's version number. 343 vb_section = self.fum.get_section(self.image, 344 section.get_sig_name()) 345 346 section.set_version(self.os_if.retrieve_body_version(vb_section)) 347 section.set_flags(self.os_if.retrieve_preamble_flags(vb_section)) 348 section.set_datakey_version( 349 self.os_if.retrieve_datakey_version(vb_section)) 350 section.set_kernel_subkey_version( 351 self.os_if.retrieve_kernel_subkey_version(vb_section)) 352 353 s = hashlib.sha1() 354 s.update(self.fum.get_section(self.image, section.get_sig_name())) 355 section.set_sig_sha(s.hexdigest()) 356 357 if not self.pub_key_file: 358 self._retrieve_pub_key() 359 360 def _retrieve_pub_key(self): 361 """Retrieve root public key from the firmware GBB section.""" 362 363 gbb_header_format = '<4s20s2I' 364 pubk_header_format = '<2Q' 365 366 gbb_section = self.fum.get_section(self.image, 'FV_GBB') 367 368 # do some confidence checks 369 try: 370 sig, _, rootk_offs, rootk_size = struct.unpack_from( 371 gbb_header_format, gbb_section) 372 except struct.error as e: 373 raise FlashromHandlerError(e) 374 375 if sig != b'$GBB' or (rootk_offs + rootk_size) > len(gbb_section): 376 raise FlashromHandlerError("Bad gbb header sig:%s len:%s" % 377 (sig, len(gbb_section))) 378 379 key_body_offset, key_body_size = struct.unpack_from( 380 pubk_header_format, gbb_section, rootk_offs) 381 382 # Generally speaking the offset field can be anything, but in case of 383 # GBB section the key is stored as a standalone entity, so the offset 384 # of the key body is expected to be equal to the key header size of 385 # 0x20. 386 # Should this convention change, the check below would fail, which 387 # would be a good prompt for revisiting this test's behavior and 388 # algorithms. 389 if key_body_offset != 0x20 or key_body_size > rootk_size: 390 raise FlashromHandlerError('Bad public key format') 391 392 # All checks passed, let's store the key in a file. 393 self.pub_key_file = self.os_if.state_dir_file(self.PUB_KEY_FILE_NAME) 394 with open(self.pub_key_file, 'wb') as key_f: 395 key = gbb_section[rootk_offs:rootk_offs + key_body_offset + 396 key_body_size] 397 key_f.write(key) 398 399 def verify_image(self): 400 """Confirm the image's validity. 401 402 Using the file supplied to init() as the public key container verify 403 the two sections' (FirmwareA and FirmwareB) integrity. The contents of 404 the sections is taken from the files created by new_image() 405 406 In case there is an integrity error raises FlashromHandlerError 407 exception with the appropriate error message text. 408 """ 409 410 for section in self.fv_sections.values(): 411 if section.get_sig_name(): 412 cmd = 'vbutil_firmware --verify %s --signpubkey %s --fv %s' % ( 413 self.section_file(section.get_sig_name()), 414 self.pub_key_file, 415 self.section_file(section.get_body_name())) 416 self.os_if.run_shell_command(cmd) 417 418 def _get_subsection_name(self, section, body_or_sig): 419 """Get the subsection name of body or signature.""" 420 if section not in self.fv_sections: 421 raise FlashromHandlerError('Unknown FW section %s' % section) 422 423 # Get the appropriate section of the image. 424 if body_or_sig: 425 subsection_name = self.fv_sections[section].get_body_name() 426 else: 427 subsection_name = self.fv_sections[section].get_sig_name() 428 return subsection_name 429 430 def _get_subsection_one_byte(self, subsection): 431 """Get a specific byte within 2% of the subsection.""" 432 if not self.image: 433 raise FlashromHandlerError( 434 'Attempt at using an uninitialized object') 435 blob = self.fum.get_section(self.image, subsection) 436 offset = len(blob) // 50 437 return offset, blob[offset] 438 439 def get_firmware_sig_one_byte(self, section): 440 """Get a specific byte of firmware signature of the section.""" 441 subsection = self._get_subsection_name(section, body_or_sig=False) 442 return self._get_subsection_one_byte(subsection) 443 444 def get_firmware_body_one_byte(self, section): 445 """Get a specific byte of firmware body of the section.""" 446 subsection = self._get_subsection_name(section, body_or_sig=True) 447 return self._get_subsection_one_byte(subsection) 448 449 def _modify_subsection(self, subsection, offset, value): 450 """Modify a byte of subsection in the FLASHROM.""" 451 if not self.image: 452 raise FlashromHandlerError( 453 'Attempt at using an uninitialized object') 454 blob = self.fum.get_section(self.image, subsection) 455 blob_list = list(blob) 456 blob_list[offset] = value % 0x100 457 self.image = self.fum.put_section(self.image, subsection, 458 bytes(blob_list)) 459 self.fum.write_partial(self.image, (subsection, )) 460 461 def modify_firmware_sig(self, section, offset, value): 462 """Modify a byte in firmware signature in the FLASHROM.""" 463 subsection = self._get_subsection_name(section, body_or_sig=False) 464 self._modify_subsection(subsection, offset, value) 465 466 def modify_firmware_body(self, section, offset, value): 467 """Modify a byte in firmware body in the FLASHROM.""" 468 subsection = self._get_subsection_name(section, body_or_sig=True) 469 self._modify_subsection(subsection, offset, value) 470 471 def corrupt_firmware_body(self, section): 472 """Corrupt the whole firmware body in the FLASHROM.""" 473 subsection = self._get_subsection_name(section, body_or_sig=True) 474 if not self.image: 475 raise FlashromHandlerError( 476 'Attempt at using an uninitialized object') 477 blob = self.fum.get_section(self.image, subsection) 478 blob_list = [(x + self.DELTA) % 0x100 for x in blob] 479 self.image = self.fum.put_section(self.image, subsection, 480 bytes(blob_list)) 481 self.fum.write_partial(self.image, (subsection, )) 482 483 def corrupt_mrc_cache(self): 484 """Corrupt MRC cache in the FLASHROM.""" 485 self.corrupt_firmware_body('rec') 486 487 def firmware_sections_equal(self): 488 """Check if firmware sections A and B are equal. 489 490 This function presumes that the entire BIOS image integrity has been 491 verified, so different signature sections mean different images and 492 vice versa. 493 """ 494 sig_a = self.fum.get_section(self.image, 495 self.fv_sections['a'].get_sig_name()) 496 sig_b = self.fum.get_section(self.image, 497 self.fv_sections['b'].get_sig_name()) 498 return sig_a == sig_b 499 500 def copy_from_to(self, src, dst): 501 """Copy one firmware image section to another. 502 503 This function copies both signature and body of one firmware section 504 into another. After this function runs both sections are identical. 505 """ 506 src_sect = self.fv_sections[src] 507 dst_sect = self.fv_sections[dst] 508 self.image = self.fum.put_section( 509 self.image, dst_sect.get_body_name(), 510 self.fum.get_section(self.image, src_sect.get_body_name())) 511 # If there is no "sig" subsection, skip copying signature. 512 if src_sect.get_sig_name() and dst_sect.get_sig_name(): 513 self.image = self.fum.put_section( 514 self.image, dst_sect.get_sig_name(), 515 self.fum.get_section(self.image, src_sect.get_sig_name())) 516 self.write_whole() 517 518 def write_whole(self): 519 """Write the whole image into the flashrom.""" 520 521 if not self.image: 522 raise FlashromHandlerError( 523 'Attempt at using an uninitialized object') 524 self.fum.write_whole(self.image) 525 526 def write_partial(self, subsection_name, blob=None, write_through=True): 527 """Write the subsection part into the flashrom. 528 529 One can pass a blob to update the data of the subsection before write 530 it into the flashrom. 531 """ 532 533 if not self.image: 534 raise FlashromHandlerError( 535 'Attempt at using an uninitialized object') 536 537 if blob is not None: 538 self.image = self.fum.put_section(self.image, subsection_name, 539 blob) 540 541 if write_through: 542 self.dump_partial( 543 subsection_name, self.section_file(subsection_name)) 544 self.fum.write_partial(self.image, (subsection_name, )) 545 546 def dump_whole(self, filename): 547 """Write the whole image into a file.""" 548 549 if not self.image: 550 raise FlashromHandlerError( 551 'Attempt at using an uninitialized object') 552 open(filename, 'wb').write(self.image) 553 554 def dump_partial(self, subsection_name, filename): 555 """Write the subsection part into a file.""" 556 557 if not self.image: 558 raise FlashromHandlerError( 559 'Attempt at using an uninitialized object') 560 blob = self.fum.get_section(self.image, subsection_name) 561 open(filename, 'wb').write(blob) 562 563 def dump_section_body(self, section, filename): 564 """Write the body of a firmware section into a file""" 565 subsection_name = self.fv_sections[section].get_body_name() 566 self.dump_partial(subsection_name, filename) 567 568 def get_section_hash(self, section): 569 """Retrieve the hash of the body of a firmware section""" 570 ecrw = chip_utils.ecrw() 571 572 # add a dot to avoid set_from_file breaking if tmpname has an underscore 573 with tempfile.NamedTemporaryFile(prefix=ecrw.chip_name + '.') as f: 574 self.dump_section_body(section, f.name) 575 ecrw.set_from_file(f.name) 576 result = ecrw.compute_hash_bytes() 577 return result 578 579 def get_gbb_flags(self): 580 """Retrieve the GBB flags""" 581 gbb_header_format = '<12sL' 582 gbb_section = self.fum.get_section(self.image, 'FV_GBB') 583 try: 584 _, gbb_flags = struct.unpack_from(gbb_header_format, gbb_section) 585 except struct.error as e: 586 raise FlashromHandlerError(e) 587 return gbb_flags 588 589 def set_gbb_flags(self, flags, write_through=False): 590 """Retrieve the GBB flags""" 591 gbb_header_format = '<L' 592 section_name = 'FV_GBB' 593 gbb_section = self.fum.get_section(self.image, section_name) 594 try: 595 formatted_flags = struct.pack(gbb_header_format, flags) 596 except struct.error as e: 597 raise FlashromHandlerError(e) 598 gbb_section = gbb_section[:12] + formatted_flags + gbb_section[16:] 599 self.write_partial(section_name, gbb_section, write_through) 600 601 def enable_write_protect(self): 602 """Enable write protect of the flash chip""" 603 description = 'Failed to enable %s write-protect' % self.target 604 with _FlashromErrorWrapper(description): 605 self.fum.enable_write_protect() 606 607 def disable_write_protect(self): 608 """Disable write protect of the flash chip""" 609 description = 'Failed to disable %s write-protect' % self.target 610 with _FlashromErrorWrapper(description): 611 self.fum.disable_write_protect() 612 613 def set_write_protect_region(self, region, enabled=None): 614 """ 615 Set write protection region by name, using current image's layout. 616 617 The name should match those seen in `futility dump_fmap <image>`, and 618 is not checked against self.firmware_layout, due to different naming. 619 620 @param region: Region to set (usually WP_RO) 621 @param enabled: If True, run --wp-enable; if False, run --wp-disable. 622 If None (default), don't specify either one. 623 """ 624 if region is None: 625 raise FlashromHandlerError("Region must not be None") 626 image_file = self.os_if.create_temp_file('wp_') 627 self.os_if.write_file(image_file, self.image) 628 629 if enabled is None: 630 verb = 'set' 631 else: 632 verb = 'enable' if enabled else 'disable' 633 msg = 'Failed to %s %s write-protect region (%s)' % ( 634 verb, self.target, region) 635 636 with _FlashromErrorWrapper(msg): 637 self.fum.set_write_protect_region(image_file, region, enabled) 638 639 self.os_if.remove_file(image_file) 640 641 def set_write_protect_range(self, start, length, enabled=None): 642 """ 643 Set write protection range by offsets, using current image's layout. 644 645 @param start: offset (bytes) from start of flash to start of range 646 @param length: offset (bytes) from start of range to end of range 647 @param enabled: If True, run --wp-enable; if False, run --wp-disable. 648 If None (default), don't specify either one. 649 """ 650 if enabled is None: 651 verb = 'set' 652 else: 653 verb = 'enable' if enabled else 'disable' 654 msg = 'Failed to %s %s write-protect range (start=%s, length=%s)' % ( 655 verb, self.target, start, length) 656 657 with _FlashromErrorWrapper(msg): 658 self.fum.set_write_protect_range(start, length, enabled) 659 660 def get_write_protect_status(self): 661 """Get a dict describing the status of the write protection 662 663 @return: {'enabled': True/False, 'start': '0x0', 'length': '0x0', ...} 664 @rtype: dict 665 """ 666 return self.fum.get_write_protect_status() 667 668 def get_section_sig_sha(self, section): 669 """Retrieve SHA1 hash of a firmware vblock section""" 670 return self.fv_sections[section].get_sig_sha() 671 672 def get_section_sha(self, section): 673 """Retrieve SHA1 hash of a firmware body section""" 674 return self.fv_sections[section].get_sha() 675 676 def get_section_version(self, section): 677 """Retrieve version number of a firmware section""" 678 return self.fv_sections[section].get_version() 679 680 def get_section_flags(self, section): 681 """Retrieve preamble flags of a firmware section""" 682 return self.fv_sections[section].get_flags() 683 684 def get_section_datakey_version(self, section): 685 """Retrieve data key version number of a firmware section""" 686 return self.fv_sections[section].get_datakey_version() 687 688 def get_section_kernel_subkey_version(self, section): 689 """Retrieve kernel subkey version number of a firmware section""" 690 return self.fv_sections[section].get_kernel_subkey_version() 691 692 def get_section_body(self, section): 693 """Retrieve body of a firmware section""" 694 subsection_name = self.fv_sections[section].get_body_name() 695 blob = self.fum.get_section(self.image, subsection_name) 696 return blob 697 698 def has_section_body(self, section): 699 """Return True if the section body is in the image""" 700 return bool(self.get_section_body(section)) 701 702 def get_section_sig(self, section): 703 """Retrieve vblock of a firmware section""" 704 subsection_name = self.fv_sections[section].get_sig_name() 705 blob = self.fum.get_section(self.image, subsection_name) 706 return blob 707 708 def get_section_fwid(self, section, strip_null=True): 709 """ 710 Retrieve fwid blob of a firmware section. 711 712 @param section: Name of the section whose fwid to return. 713 @param strip_null: If True, remove \0 from the end of the blob. 714 @return: fwid of the section 715 716 @type section: str 717 @type strip_null: bool 718 @rtype: bytes | None 719 720 """ 721 subsection_name = self.fv_sections[section].get_fwid_name() 722 if not subsection_name: 723 return None 724 blob = self.fum.get_section(self.image, subsection_name) 725 if strip_null: 726 blob = blob.rstrip(b'\0') 727 return blob 728 729 def set_section_body(self, section, blob, write_through=False): 730 """Put the supplied blob to the body of the firmware section""" 731 subsection_name = self.fv_sections[section].get_body_name() 732 self.write_partial(subsection_name, blob, write_through) 733 734 def set_section_sig(self, section, blob, write_through=False): 735 """Put the supplied blob to the vblock of the firmware section""" 736 subsection_name = self.fv_sections[section].get_sig_name() 737 self.write_partial(subsection_name, blob, write_through) 738 739 def set_section_fwid(self, section, blob, write_through=False): 740 """Put the supplied blob to the fwid of the firmware section""" 741 subsection_name = self.fv_sections[section].get_fwid_name() 742 self.write_partial(subsection_name, blob, write_through) 743 744 def resign_ec_rwsig(self): 745 """Resign the EC image using rwsig.""" 746 key_ec_efs = os.path.join(self.dev_key_path, self.EC_EFS_KEY_FILE_NAME) 747 # Dump whole EC image to a file and execute the sign command. 748 with tempfile.NamedTemporaryFile() as f: 749 self.dump_whole(f.name) 750 self.os_if.run_shell_command( 751 'futility sign --type rwsig --prikey %s %s' % (key_ec_efs, 752 f.name)) 753 self.new_image(f.name) 754 755 def set_section_version(self, section, version, flags, 756 write_through=False): 757 """ 758 Re-sign the firmware section using the supplied version number and 759 flag. 760 """ 761 if (self.get_section_version(section) == version 762 and self.get_section_flags(section) == flags): 763 logging.info(f"Nothing to do existing " 764 f"version {self.get_section_version(section)} " 765 f"flags {self.get_section_flags(section)}") 766 return # No version or flag change, nothing to do. 767 if version < 0: 768 raise FlashromHandlerError( 769 'Attempt to set version %d on section %s' % (version, 770 section)) 771 fv_section = self.fv_sections[section] 772 sig_name = self.section_file(fv_section.get_sig_name()) 773 sig_size = os.path.getsize(sig_name) 774 775 # Construct the command line 776 args = ['--vblock %s' % sig_name] 777 args.append('--keyblock %s' % os.path.join(self.dev_key_path, 778 self.FW_KEYBLOCK_FILE_NAME)) 779 args.append('--fv %s' % self.section_file(fv_section.get_body_name())) 780 args.append('--version %d' % version) 781 args.append('--kernelkey %s' % os.path.join( 782 self.dev_key_path, self.KERNEL_SUBKEY_FILE_NAME)) 783 args.append('--signprivate %s' % os.path.join( 784 self.dev_key_path, self.FW_PRIV_DATA_KEY_FILE_NAME)) 785 args.append('--flags %d' % flags) 786 cmd = 'vbutil_firmware %s' % ' '.join(args) 787 self.os_if.run_shell_command(cmd) 788 789 # Pad the new signature. 790 with open(sig_name, 'ab') as sig_f: 791 f_size = os.fstat(sig_f.fileno()).st_size 792 pad = b'\0' * (sig_size - f_size) 793 sig_f.write(pad) 794 795 # Inject the new signature block into the image 796 with open(sig_name, 'rb') as sig_f: 797 new_sig = sig_f.read() 798 self.write_partial(fv_section.get_sig_name(), new_sig, write_through) 799 800 def _modify_section_fwid(self, section): 801 """Modify a section's fwid on the handler, adding a tilde and the 802 section name (in caps) to the end: ~RO, ~RW, ~A, ~B. 803 804 @param section: the single section to act on 805 @return: the new fwid 806 807 @type section: str 808 @rtype: str 809 """ 810 811 fwid = self.get_section_fwid(section, strip_null=False) 812 813 if fwid is None: 814 return None 815 816 fwid_size = len(fwid) 817 818 if not fwid: 819 raise FlashromHandlerError( 820 "FWID (%s, %s) is empty: %s" % 821 (self.target.upper(), section.upper(), repr(fwid))) 822 823 fwid = fwid.rstrip(b'\0') 824 suffix = bytes(self.FWID_MOD_DELIMITER + section.upper(), 'utf-8') 825 826 if suffix in fwid: 827 raise FlashromHandlerError( 828 "FWID (%s, %s) is already modified: %s" % 829 (self.target.upper(), section.upper(), repr(fwid))) 830 831 # Append a suffix, after possibly chopping off characters to make room. 832 if len(fwid) + len(suffix) > fwid_size: 833 fwid = fwid[:fwid_size - len(suffix)] 834 fwid += suffix 835 836 padded_fwid = fwid.ljust(fwid_size, b'\0') 837 self.set_section_fwid(section, padded_fwid) 838 return fwid 839 840 def _strip_section_fwid(self, section, write_through=True): 841 """Modify a section's fwid on the handler, stripping any suffix added 842 by _modify_section_fwid: ~RO, ~RW, ~A, ~B. 843 844 @param section: the single section to act on 845 @param write_through: if True (default), write to flash immediately 846 @return: the suffix that was stripped 847 848 @type section: str 849 @type write_through: bool 850 @rtype: str | None 851 """ 852 853 fwid = self.get_section_fwid(section, strip_null=False) 854 if fwid is None: 855 return None 856 857 fwid_size = len(fwid) 858 859 if not fwid: 860 raise FlashromHandlerError( 861 "FWID (%s, %s) is empty: %s" % 862 (self.target.upper(), section.upper(), repr(fwid))) 863 864 fwid = fwid.rstrip(b'\0') 865 mod_indicator = bytes(self.FWID_MOD_DELIMITER + section.upper(), 866 'utf-8') 867 868 # Remove any suffix, and return the suffix if found. 869 if mod_indicator in fwid: 870 (stripped_fwid, remainder) = fwid.split(mod_indicator, 1) 871 872 padded_fwid = stripped_fwid.ljust(fwid_size, b'\0') 873 self.set_section_fwid(section, padded_fwid, write_through) 874 875 return fwid 876 return None 877 878 def modify_fwids(self, sections): 879 """Modify the fwid in the in-memory image. 880 881 @param sections: section(s) to modify. 882 @return: fwids for the modified sections, as {section: fwid} 883 884 @type sections: tuple | list 885 @rtype: dict 886 """ 887 fwids = {} 888 for section in sections: 889 fwids[section] = self._modify_section_fwid(section) 890 891 return fwids 892 893 def strip_modified_fwids(self): 894 """Strip any trailing suffixes (from modify_fwids) out of the FWIDs. 895 896 @return: a dict of any fwids that were adjusted, by section (ro, a, b) 897 @rtype: dict 898 """ 899 900 suffixes = {} 901 for section in self.fv_sections: 902 suffix = self._strip_section_fwid(section) 903 if suffix is not None: 904 suffixes[section] = suffix 905 906 return suffixes 907 908 def get_write_cmd(self, image=None): 909 """Get the command needed to write the whole image to the device. 910 911 @param image: the filename (empty to use current handler data) 912 """ 913 if image: 914 return self.fum.get_write_cmd(image) 915 else: 916 tmp_image = self.os_if.create_temp_file('tmp_%s.bin.' % self.target) 917 self.os_if.write_file(tmp_image, self.image) 918 cmd = self.fum.get_write_cmd(tmp_image) 919 return '%s; rm %s' % (cmd, tmp_image) 920