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