• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""A module to support automated testing of ChromeOS firmware.
7
8Utilizes services provided by saft_flashrom_util.py read/write the
9flashrom chip and to parse the flash rom image.
10
11See docstring for FlashromHandler class below.
12"""
13
14import hashlib
15import os
16import struct
17import tempfile
18
19from autotest_lib.client.common_lib.cros import chip_utils
20
21class FvSection(object):
22    """An object to hold information about a firmware section.
23
24    This includes file names for the signature header and the body, and the
25    version number.
26    """
27
28    def __init__(self, sig_name, body_name, fwid_name=None):
29        self._sig_name = sig_name
30        self._body_name = body_name
31        self._fwid_name = fwid_name
32        self._version = -1  # Is not set on construction.
33        self._flags = 0  # Is not set on construction.
34        self._sha = None  # Is not set on construction.
35        self._sig_sha = None # Is not set on construction.
36        self._datakey_version = -1 # Is not set on construction.
37        self._kernel_subkey_version = -1 # Is not set on construction.
38
39    def names(self):
40        return (self._sig_name, self._body_name, self._fwid_name)
41
42    def get_sig_name(self):
43        return self._sig_name
44
45    def get_body_name(self):
46        return self._body_name
47
48    def get_fwid_name(self):
49        return self._fwid_name
50
51    def get_version(self):
52        return self._version
53
54    def get_flags(self):
55        return self._flags
56
57    def get_sha(self):
58        return self._sha
59
60    def get_sig_sha(self):
61        return self._sig_sha
62
63    def get_datakey_version(self):
64        return self._datakey_version
65
66    def get_kernel_subkey_version(self):
67        return self._kernel_subkey_version
68
69    def set_version(self, version):
70        self._version = version
71
72    def set_flags(self, flags):
73        self._flags = flags
74
75    def set_sha(self, sha):
76        self._sha = sha
77
78    def set_sig_sha(self, sha):
79        self._sig_sha = sha
80
81    def set_datakey_version(self, version):
82        self._datakey_version = version
83
84    def set_kernel_subkey_version(self, version):
85        self._kernel_subkey_version = version
86
87class FlashromHandlerError(Exception):
88    pass
89
90
91class FlashromHandler(object):
92    """An object to provide logical services for automated flashrom testing."""
93
94    DELTA = 1  # value to add to a byte to corrupt a section contents
95
96    # File in the state directory to store public root key.
97    PUB_KEY_FILE_NAME = 'root.pubkey'
98    FW_KEYBLOCK_FILE_NAME = 'firmware.keyblock'
99    FW_PRIV_DATA_KEY_FILE_NAME = 'firmware_data_key.vbprivk'
100    KERNEL_SUBKEY_FILE_NAME = 'kernel_subkey.vbpubk'
101    EC_EFS_KEY_FILE_NAME = 'key_ec_efs.vbprik2'
102
103    def __init__(self):
104    # make sure it does not accidentally overwrite the image.
105        self.fum = None
106        self.os_if = None
107        self.image = ''
108        self.pub_key_file = ''
109
110    def init(self, flashrom_util_module,
111             os_if,
112             pub_key_file=None,
113             dev_key_path='./',
114             target='bios'):
115        """Flashrom handler initializer.
116
117        Args:
118          flashrom_util_module - a module providing flashrom access utilities.
119          os_if - a module providing interface to OS services
120          pub_key_file - a string, name of the file contaning a public key to
121                         use for verifying both existing and new firmware.
122        """
123        if target == 'bios':
124            self.fum = flashrom_util_module.flashrom_util(
125                    os_if, target_is_ec=False)
126            self.fv_sections = {
127                '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