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