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 
     18 class 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 
     80 class FlashromHandlerError(Exception):
     81     pass
     82 
     83 
     84 class 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