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                 '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