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