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