Home | History | Annotate | Download | only in utils
      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 
      8 Utilizes services provided by saft_flashrom_util.py read/write the
      9 flashrom chip and to parse the flash rom image.
     10 
     11 See docstring for FlashromHandler class below.
     12 """
     13 
     14 import hashlib
     15 import os
     16 import struct
     17 import tempfile
     18 
     19 from autotest_lib.client.common_lib.cros import chip_utils
     20 
     21 class 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 
     87 class FlashromHandlerError(Exception):
     88     pass
     89 
     90 
     91 class 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