1#!/usr/bin/python 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 6"""A module to support automated testing of ChromeOS firmware. 7 8Utilizes services provided by saft_flashrom_util.py read/write the 9flashrom chip and to parse the flash rom image. 10 11See docstring for FlashromHandler class below. 12""" 13 14import hashlib 15import os 16import struct 17import tempfile 18 19from autotest_lib.client.common_lib.cros import chip_utils 20 21class FvSection(object): 22 """An object to hold information about a firmware section. 23 24 This includes file names for the signature header and the body, and the 25 version number. 26 """ 27 28 def __init__(self, sig_name, body_name, fwid_name=None): 29 self._sig_name = sig_name 30 self._body_name = body_name 31 self._fwid_name = fwid_name 32 self._version = -1 # Is not set on construction. 33 self._flags = 0 # Is not set on construction. 34 self._sha = None # Is not set on construction. 35 self._sig_sha = None # Is not set on construction. 36 self._datakey_version = -1 # Is not set on construction. 37 self._kernel_subkey_version = -1 # Is not set on construction. 38 39 def names(self): 40 return (self._sig_name, self._body_name, self._fwid_name) 41 42 def get_sig_name(self): 43 return self._sig_name 44 45 def get_body_name(self): 46 return self._body_name 47 48 def get_fwid_name(self): 49 return self._fwid_name 50 51 def get_version(self): 52 return self._version 53 54 def get_flags(self): 55 return self._flags 56 57 def get_sha(self): 58 return self._sha 59 60 def get_sig_sha(self): 61 return self._sig_sha 62 63 def get_datakey_version(self): 64 return self._datakey_version 65 66 def get_kernel_subkey_version(self): 67 return self._kernel_subkey_version 68 69 def set_version(self, version): 70 self._version = version 71 72 def set_flags(self, flags): 73 self._flags = flags 74 75 def set_sha(self, sha): 76 self._sha = sha 77 78 def set_sig_sha(self, sha): 79 self._sig_sha = sha 80 81 def set_datakey_version(self, version): 82 self._datakey_version = version 83 84 def set_kernel_subkey_version(self, version): 85 self._kernel_subkey_version = version 86 87class FlashromHandlerError(Exception): 88 pass 89 90 91class FlashromHandler(object): 92 """An object to provide logical services for automated flashrom testing.""" 93 94 DELTA = 1 # value to add to a byte to corrupt a section contents 95 96 # File in the state directory to store public root key. 97 PUB_KEY_FILE_NAME = 'root.pubkey' 98 FW_KEYBLOCK_FILE_NAME = 'firmware.keyblock' 99 FW_PRIV_DATA_KEY_FILE_NAME = 'firmware_data_key.vbprivk' 100 KERNEL_SUBKEY_FILE_NAME = 'kernel_subkey.vbpubk' 101 EC_EFS_KEY_FILE_NAME = 'key_ec_efs.vbprik2' 102 103 def __init__(self): 104 # make sure it does not accidentally overwrite the image. 105 self.fum = None 106 self.os_if = None 107 self.image = '' 108 self.pub_key_file = '' 109 110 def init(self, flashrom_util_module, 111 os_if, 112 pub_key_file=None, 113 dev_key_path='./', 114 target='bios'): 115 """Flashrom handler initializer. 116 117 Args: 118 flashrom_util_module - a module providing flashrom access utilities. 119 os_if - a module providing interface to OS services 120 pub_key_file - a string, name of the file contaning a public key to 121 use for verifying both existing and new firmware. 122 """ 123 if target == 'bios': 124 self.fum = flashrom_util_module.flashrom_util( 125 os_if, target_is_ec=False) 126 self.fv_sections = { 127 'ro': FvSection(None, None, 'RO_FRID'), 128 'a': FvSection('VBOOTA', 'FVMAIN', 'RW_FWID_A'), 129 'b': FvSection('VBOOTB', 'FVMAINB', 'RW_FWID_B'), 130 'rec': FvSection(None, 'RECOVERY_MRC_CACHE'), 131 'ec_a': FvSection(None, 'ECMAINA'), 132 'ec_b': FvSection(None, 'ECMAINB'), 133 } 134 elif target == 'ec': 135 self.fum = flashrom_util_module.flashrom_util( 136 os_if, target_is_ec=True) 137 self.fv_sections = { 138 'rw': FvSection(None, 'EC_RW', 'RW_FWID'), 139 'rw_b': FvSection(None, 'EC_RW_B'), 140 } 141 else: 142 raise FlashromHandlerError("Invalid target.") 143 self.os_if = os_if 144 self.pub_key_file = pub_key_file 145 self.dev_key_path = dev_key_path 146 self.new_image() 147 148 def new_image(self, image_file=None): 149 """Parse the full flashrom image and store sections into files. 150 151 Args: 152 image_file - a string, the name of the file contaning full ChromeOS 153 flashrom image. If not passed in or empty - the actual 154 flashrom is read and its contents are saved into a 155 temporary file which is used instead. 156 157 The input file is parsed and the sections of importance (as defined in 158 self.fv_sections) are saved in separate files in the state directory 159 as defined in the os_if object. 160 """ 161 162 if image_file: 163 self.image = open(image_file, 'rb').read() 164 self.fum.set_firmware_layout(image_file) 165 else: 166 self.image = self.fum.read_whole() 167 168 for section in self.fv_sections.itervalues(): 169 for subsection_name in section.names(): 170 if not subsection_name: 171 continue 172 blob = self.fum.get_section(self.image, subsection_name) 173 if blob: 174 f = open(self.os_if.state_dir_file(subsection_name), 175 'wb') 176 f.write(blob) 177 f.close() 178 179 blob = self.fum.get_section(self.image, section.get_body_name()) 180 if blob: 181 s = hashlib.sha1() 182 s.update(blob) 183 section.set_sha(s.hexdigest()) 184 185 # If there is no "sig" subsection, skip reading version and flags. 186 if not section.get_sig_name(): 187 continue 188 189 # Now determine this section's version number. 190 vb_section = self.fum.get_section( 191 self.image, section.get_sig_name()) 192 193 section.set_version(self.os_if.retrieve_body_version(vb_section)) 194 section.set_flags(self.os_if.retrieve_preamble_flags(vb_section)) 195 section.set_datakey_version( 196 self.os_if.retrieve_datakey_version(vb_section)) 197 section.set_kernel_subkey_version( 198 self.os_if.retrieve_kernel_subkey_version(vb_section)) 199 200 s = hashlib.sha1() 201 s.update(self.fum.get_section(self.image, section.get_sig_name())) 202 section.set_sig_sha(s.hexdigest()) 203 204 if not self.pub_key_file: 205 self._retrieve_pub_key() 206 207 def _retrieve_pub_key(self): 208 """Retrieve root public key from the firmware GBB section.""" 209 210 gbb_header_format = '<4s20s2I' 211 pubk_header_format = '<2Q' 212 213 gbb_section = self.fum.get_section(self.image, 'FV_GBB') 214 215 # do some sanity checks 216 try: 217 sig, _, rootk_offs, rootk_size = struct.unpack_from( 218 gbb_header_format, gbb_section) 219 except struct.error, e: 220 raise FlashromHandlerError(e) 221 222 if sig != '$GBB' or (rootk_offs + rootk_size) > len(gbb_section): 223 raise FlashromHandlerError('Bad gbb header') 224 225 key_body_offset, key_body_size = struct.unpack_from( 226 pubk_header_format, gbb_section, rootk_offs) 227 228 # Generally speaking the offset field can be anything, but in case of 229 # GBB section the key is stored as a standalone entity, so the offset 230 # of the key body is expected to be equal to the key header size of 231 # 0x20. 232 # Should this convention change, the check below would fail, which 233 # would be a good prompt for revisiting this test's behavior and 234 # algorithms. 235 if key_body_offset != 0x20 or key_body_size > rootk_size: 236 raise FlashromHandlerError('Bad public key format') 237 238 # All checks passed, let's store the key in a file. 239 self.pub_key_file = self.os_if.state_dir_file(self.PUB_KEY_FILE_NAME) 240 keyf = open(self.pub_key_file, 'w') 241 key = gbb_section[ 242 rootk_offs:rootk_offs + key_body_offset + key_body_size] 243 keyf.write(key) 244 keyf.close() 245 246 def verify_image(self): 247 """Confirm the image's validity. 248 249 Using the file supplied to init() as the public key container verify 250 the two sections' (FirmwareA and FirmwareB) integrity. The contents of 251 the sections is taken from the files created by new_image() 252 253 In case there is an integrity error raises FlashromHandlerError 254 exception with the appropriate error message text. 255 """ 256 257 for section in self.fv_sections.itervalues(): 258 if section.get_sig_name(): 259 cmd = 'vbutil_firmware --verify %s --signpubkey %s --fv %s' % ( 260 self.os_if.state_dir_file(section.get_sig_name()), 261 self.pub_key_file, 262 self.os_if.state_dir_file(section.get_body_name())) 263 self.os_if.run_shell_command(cmd) 264 265 def _modify_section(self, section, delta, body_or_sig=False, 266 corrupt_all=False): 267 """Modify a firmware section inside the image, either body or signature. 268 269 If corrupt_all is set, the passed in delta is added to all bytes in the 270 section. Otherwise, the delta is added to the value located at 2% offset 271 into the section blob, either body or signature. 272 273 Calling this function again for the same section the complimentary 274 delta value would restore the section contents. 275 """ 276 277 if not self.image: 278 raise FlashromHandlerError( 279 'Attempt at using an uninitialized object') 280 if section not in self.fv_sections: 281 raise FlashromHandlerError('Unknown FW section %s' 282 % section) 283 284 # Get the appropriate section of the image. 285 if body_or_sig: 286 subsection_name = self.fv_sections[section].get_body_name() 287 else: 288 subsection_name = self.fv_sections[section].get_sig_name() 289 blob = self.fum.get_section(self.image, subsection_name) 290 291 # Modify the byte in it within 2% of the section blob. 292 modified_index = len(blob) / 50 293 if corrupt_all: 294 blob_list = [('%c' % ((ord(x) + delta) % 0x100)) for x in blob] 295 else: 296 blob_list = list(blob) 297 blob_list[modified_index] = ('%c' % 298 ((ord(blob[modified_index]) + delta) % 0x100)) 299 self.image = self.fum.put_section(self.image, 300 subsection_name, ''.join(blob_list)) 301 302 return subsection_name 303 304 def corrupt_section(self, section, corrupt_all=False): 305 """Corrupt a section signature of the image""" 306 307 return self._modify_section(section, self.DELTA, body_or_sig=False, 308 corrupt_all=corrupt_all) 309 310 def corrupt_section_body(self, section, corrupt_all=False): 311 """Corrupt a section body of the image""" 312 313 return self._modify_section(section, self.DELTA, body_or_sig=True, 314 corrupt_all=corrupt_all) 315 316 def restore_section(self, section, restore_all=False): 317 """Restore a previously corrupted section signature of the image.""" 318 319 return self._modify_section(section, -self.DELTA, body_or_sig=False, 320 corrupt_all=restore_all) 321 322 def restore_section_body(self, section, restore_all=False): 323 """Restore a previously corrupted section body of the image.""" 324 325 return self._modify_section(section, -self.DELTA, body_or_sig=True, 326 corrupt_all=restore_all) 327 328 def corrupt_firmware(self, section, corrupt_all=False): 329 """Corrupt a section signature in the FLASHROM!!!""" 330 331 subsection_name = self.corrupt_section(section, corrupt_all=corrupt_all) 332 self.fum.write_partial(self.image, (subsection_name, )) 333 334 def corrupt_firmware_body(self, section, corrupt_all=False): 335 """Corrupt a section body in the FLASHROM!!!""" 336 337 subsection_name = self.corrupt_section_body(section, 338 corrupt_all=corrupt_all) 339 self.fum.write_partial(self.image, (subsection_name, )) 340 341 def restore_firmware(self, section, restore_all=False): 342 """Restore the previously corrupted section sig in the FLASHROM!!!""" 343 344 subsection_name = self.restore_section(section, restore_all=restore_all) 345 self.fum.write_partial(self.image, (subsection_name, )) 346 347 def restore_firmware_body(self, section, restore_all=False): 348 """Restore the previously corrupted section body in the FLASHROM!!!""" 349 350 subsection_name = self.restore_section_body(section, 351 restore_all=False) 352 self.fum.write_partial(self.image, (subsection_name, )) 353 354 def firmware_sections_equal(self): 355 """Check if firmware sections A and B are equal. 356 357 This function presumes that the entire BIOS image integrity has been 358 verified, so different signature sections mean different images and 359 vice versa. 360 """ 361 sig_a = self.fum.get_section(self.image, 362 self.fv_sections['a'].get_sig_name()) 363 sig_b = self.fum.get_section(self.image, 364 self.fv_sections['b'].get_sig_name()) 365 return sig_a == sig_b 366 367 def copy_from_to(self, src, dst): 368 """Copy one firmware image section to another. 369 370 This function copies both signature and body of one firmware section 371 into another. After this function runs both sections are identical. 372 """ 373 src_sect = self.fv_sections[src] 374 dst_sect = self.fv_sections[dst] 375 self.image = self.fum.put_section( 376 self.image, 377 dst_sect.get_body_name(), 378 self.fum.get_section(self.image, src_sect.get_body_name())) 379 # If there is no "sig" subsection, skip copying signature. 380 if src_sect.get_sig_name() and dst_sect.get_sig_name(): 381 self.image = self.fum.put_section( 382 self.image, 383 dst_sect.get_sig_name(), 384 self.fum.get_section(self.image, src_sect.get_sig_name())) 385 self.write_whole() 386 387 def write_whole(self): 388 """Write the whole image into the flashrom.""" 389 390 if not self.image: 391 raise FlashromHandlerError( 392 'Attempt at using an uninitialized object') 393 self.fum.write_whole(self.image) 394 395 def write_partial(self, subsection_name, blob=None, write_through=True): 396 """Write the subsection part into the flashrom. 397 398 One can pass a blob to update the data of the subsection before write 399 it into the flashrom. 400 """ 401 402 if not self.image: 403 raise FlashromHandlerError( 404 'Attempt at using an uninitialized object') 405 406 if blob is not None: 407 self.image = self.fum.put_section(self.image, subsection_name, blob) 408 409 if write_through: 410 self.dump_partial(subsection_name, 411 self.os_if.state_dir_file(subsection_name)) 412 self.fum.write_partial(self.image, (subsection_name, )) 413 414 def dump_whole(self, filename): 415 """Write the whole image into a file.""" 416 417 if not self.image: 418 raise FlashromHandlerError( 419 'Attempt at using an uninitialized object') 420 open(filename, 'w').write(self.image) 421 422 def dump_partial(self, subsection_name, filename): 423 """Write the subsection part into a file.""" 424 425 if not self.image: 426 raise FlashromHandlerError( 427 'Attempt at using an uninitialized object') 428 blob = self.fum.get_section(self.image, subsection_name) 429 open(filename, 'w').write(blob) 430 431 def dump_section_body(self, section, filename): 432 """Write the body of a firmware section into a file""" 433 subsection_name = self.fv_sections[section].get_body_name() 434 self.dump_partial(subsection_name, filename) 435 436 def get_section_hash(self, section): 437 """Retrieve the hash of the body of a firmware section""" 438 ecrw = chip_utils.ecrw() 439 with tempfile.NamedTemporaryFile(prefix=ecrw.chip_name) as f: 440 self.dump_section_body(section, f.name) 441 ecrw.set_from_file(f.name) 442 result = ecrw.compute_hash_bytes() 443 return result 444 445 def get_gbb_flags(self): 446 """Retrieve the GBB flags""" 447 gbb_header_format = '<12sL' 448 gbb_section = self.fum.get_section(self.image, 'FV_GBB') 449 try: 450 _, gbb_flags = struct.unpack_from(gbb_header_format, gbb_section) 451 except struct.error, e: 452 raise FlashromHandlerError(e) 453 return gbb_flags 454 455 def set_gbb_flags(self, flags, write_through=False): 456 """Retrieve the GBB flags""" 457 gbb_header_format = '<L' 458 section_name = 'FV_GBB' 459 gbb_section = self.fum.get_section(self.image, section_name) 460 try: 461 formatted_flags = struct.pack(gbb_header_format, flags) 462 except struct.error, e: 463 raise FlashromHandlerError(e) 464 gbb_section = gbb_section[:12] + formatted_flags + gbb_section[16:] 465 self.write_partial(section_name, gbb_section, write_through) 466 467 def enable_write_protect(self): 468 """Enable write protect of the flash chip""" 469 self.fum.enable_write_protect() 470 471 def disable_write_protect(self): 472 """Disable write protect of the flash chip""" 473 self.fum.disable_write_protect() 474 475 def get_section_sig_sha(self, section): 476 """Retrieve SHA1 hash of a firmware vblock section""" 477 return self.fv_sections[section].get_sig_sha() 478 479 def get_section_sha(self, section): 480 """Retrieve SHA1 hash of a firmware body section""" 481 return self.fv_sections[section].get_sha() 482 483 def get_section_version(self, section): 484 """Retrieve version number of a firmware section""" 485 return self.fv_sections[section].get_version() 486 487 def get_section_flags(self, section): 488 """Retrieve preamble flags of a firmware section""" 489 return self.fv_sections[section].get_flags() 490 491 def get_section_datakey_version(self, section): 492 """Retrieve data key version number of a firmware section""" 493 return self.fv_sections[section].get_datakey_version() 494 495 def get_section_kernel_subkey_version(self, section): 496 """Retrieve kernel subkey version number of a firmware section""" 497 return self.fv_sections[section].get_kernel_subkey_version() 498 499 def get_section_body(self, section): 500 """Retrieve body of a firmware section""" 501 subsection_name = self.fv_sections[section].get_body_name() 502 blob = self.fum.get_section(self.image, subsection_name) 503 return blob 504 505 def has_section_body(self, section): 506 """Return True if the section body is in the image""" 507 return bool(self.get_section_body(section)) 508 509 def get_section_sig(self, section): 510 """Retrieve vblock of a firmware section""" 511 subsection_name = self.fv_sections[section].get_sig_name() 512 blob = self.fum.get_section(self.image, subsection_name) 513 return blob 514 515 def get_section_fwid(self, section): 516 """Retrieve fwid blob of a firmware section""" 517 subsection_name = self.fv_sections[section].get_fwid_name() 518 blob = self.fum.get_section(self.image, subsection_name) 519 return blob 520 521 def set_section_body(self, section, blob, write_through=False): 522 """Put the supplied blob to the body of the firmware section""" 523 subsection_name = self.fv_sections[section].get_body_name() 524 self.write_partial(subsection_name, blob, write_through) 525 526 def set_section_sig(self, section, blob, write_through=False): 527 """Put the supplied blob to the vblock of the firmware section""" 528 subsection_name = self.fv_sections[section].get_sig_name() 529 self.write_partial(subsection_name, blob, write_through) 530 531 def set_section_fwid(self, section, blob, write_through=False): 532 """Put the supplied blob to the fwid of the firmware section""" 533 subsection_name = self.fv_sections[section].get_fwid_name() 534 self.write_partial(subsection_name, blob, write_through) 535 536 def resign_ec_rwsig(self): 537 """Resign the EC image using rwsig.""" 538 key_ec_efs = os.path.join(self.dev_key_path, self.EC_EFS_KEY_FILE_NAME) 539 # Dump whole EC image to a file and execute the sign command. 540 with tempfile.NamedTemporaryFile() as f: 541 self.dump_whole(f.name) 542 self.os_if.run_shell_command( 543 'futility sign --type rwsig --prikey %s %s' % ( 544 key_ec_efs, f.name)) 545 self.new_image(f.name) 546 547 def set_section_version(self, section, version, flags, 548 write_through=False): 549 """ 550 Re-sign the firmware section using the supplied version number and 551 flag. 552 """ 553 if (self.get_section_version(section) == version and 554 self.get_section_flags(section) == flags): 555 return # No version or flag change, nothing to do. 556 if version < 0: 557 raise FlashromHandlerError( 558 'Attempt to set version %d on section %s' % (version, section)) 559 fv_section = self.fv_sections[section] 560 sig_name = self.os_if.state_dir_file(fv_section.get_sig_name()) 561 sig_size = os.path.getsize(sig_name) 562 563 # Construct the command line 564 args = ['--vblock %s' % sig_name] 565 args.append('--keyblock %s' % os.path.join( 566 self.dev_key_path, self.FW_KEYBLOCK_FILE_NAME)) 567 args.append('--fv %s' % self.os_if.state_dir_file( 568 fv_section.get_body_name())) 569 args.append('--version %d' % version) 570 args.append('--kernelkey %s' % os.path.join( 571 self.dev_key_path, self.KERNEL_SUBKEY_FILE_NAME)) 572 args.append('--signprivate %s' % os.path.join( 573 self.dev_key_path, self.FW_PRIV_DATA_KEY_FILE_NAME)) 574 args.append('--flags %d' % flags) 575 cmd = 'vbutil_firmware %s' % ' '.join(args) 576 self.os_if.run_shell_command(cmd) 577 578 # Pad the new signature. 579 new_sig = open(sig_name, 'a') 580 pad = ('%c' % 0) * (sig_size - os.path.getsize(sig_name)) 581 new_sig.write(pad) 582 new_sig.close() 583 584 # Inject the new signature block into the image 585 new_sig = open(sig_name, 'r').read() 586 self.write_partial(fv_section.get_sig_name(), new_sig, write_through) 587