• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python3
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"""A module to support automated testing of ChromeOS firmware.
6
7Utilizes services provided by saft_flashrom_util.py read/write the
8flashrom chip and to parse the flash rom image.
9
10See docstring for FlashromHandler class below.
11"""
12
13import hashlib
14import logging
15import os
16import struct
17import tempfile
18
19import six
20
21import common
22
23from autotest_lib.client.common_lib import error
24from autotest_lib.client.common_lib.cros import chip_utils
25from autotest_lib.client.cros.faft.utils import saft_flashrom_util
26
27
28class FvSection(object):
29    """An object to hold information about a firmware section.
30
31    This includes file names for the signature header and the body, and the
32    version number.
33    """
34
35    def __init__(self, sig_name, body_name, fwid_name=None):
36        """
37        @param sig_name: name of signature section in fmap
38        @param body_name: name of body section in fmap
39        @param fwid_name: name of fwid section in fmap
40        @type sig_name: str | None
41        @type body_name: str | None
42        @type fwid_name: str | None
43        """
44        self._sig_name = sig_name
45        self._body_name = body_name
46        self._fwid_name = fwid_name
47        self._version = -1  # Is not set on construction.
48        self._flags = 0  # Is not set on construction.
49        self._sha = None  # Is not set on construction.
50        self._sig_sha = None  # Is not set on construction.
51        self._datakey_version = -1  # Is not set on construction.
52        self._kernel_subkey_version = -1  # Is not set on construction.
53
54    def names(self):
55        """Return the desired file names for the signature, body, and fwid."""
56        return (self._sig_name, self._body_name, self._fwid_name)
57
58    def get_sig_name(self):
59        return self._sig_name
60
61    def get_body_name(self):
62        return self._body_name
63
64    def get_fwid_name(self):
65        return self._fwid_name
66
67    def get_version(self):
68        return self._version
69
70    def get_flags(self):
71        return self._flags
72
73    def get_sha(self):
74        return self._sha
75
76    def get_sig_sha(self):
77        return self._sig_sha
78
79    def get_datakey_version(self):
80        return self._datakey_version
81
82    def get_kernel_subkey_version(self):
83        return self._kernel_subkey_version
84
85    def set_version(self, version):
86        self._version = version
87
88    def set_flags(self, flags):
89        self._flags = flags
90
91    def set_sha(self, sha):
92        self._sha = sha
93
94    def set_sig_sha(self, sha):
95        self._sig_sha = sha
96
97    def set_datakey_version(self, version):
98        self._datakey_version = version
99
100    def set_kernel_subkey_version(self, version):
101        self._kernel_subkey_version = version
102
103
104class FlashromHandlerError(Exception):
105    """An object to represent Flashrom errors"""
106    pass
107
108
109class _FlashromErrorWrapper(object):
110    """Wrap calls to flashrom, giving cleaner error messages.
111
112    @param description: The start of the failure message ("Failed to <verb>...")
113    """
114    def __init__(self, description):
115        self.description = description
116
117    def __enter__(self):
118        """Enter the context"""
119        return self
120
121    def __exit__(self, exc_type, exc_val, exc_tb):
122        """Exit the context, converting CmdError into summarized errors.
123
124        @raise FlashromHandlerError: if the wrapped code raises CmdError.
125        """
126        if not isinstance(exc_val, error.CmdError):
127            del exc_tb
128            return
129        result = exc_val.result_obj
130        lines = [result.command]
131        for line in result.stdout.splitlines():
132            # grab anything mentioning 'status', but avoid very long lines
133            if len(line) < 80 and 'status' in line.lower():
134                lines.append(line)
135        lines.extend(result.stderr.splitlines())
136
137        err_msg = '%s.' % self.description.rstrip('.')
138        err_msg += '  [summarized output, check rpc server log for full]:'
139        err_msg += '  [rc=%s] ' % result.exit_status
140        err_msg += ' || '.join(lines)
141        err = FlashromHandlerError(err_msg)
142        try:
143            six.reraise(type(err), err, exc_tb)
144        finally:
145            del exc_tb
146
147
148class FlashromHandler(object):
149    """An object to provide logical services for automated flashrom testing."""
150
151    DELTA = 1  # value to add to a byte to corrupt a section contents
152
153    # File in the state directory to store public root key.
154    PUB_KEY_FILE_NAME = 'root.pubkey'
155    FW_KEYBLOCK_FILE_NAME = 'firmware.keyblock'
156    FW_PRIV_DATA_KEY_FILE_NAME = 'firmware_data_key.vbprivk'
157    KERNEL_SUBKEY_FILE_NAME = 'kernel_subkey.vbpubk'
158    EC_EFS_KEY_FILE_NAME = 'key_ec_efs.vbprik2'
159    FWID_MOD_DELIMITER = '~'
160
161    def __init__(
162            self,
163            os_if,
164            pub_key_file=None,
165            dev_key_path='./',
166            target='bios',
167            subdir=None
168    ):
169        """The flashrom handler is not fully initialized upon creation
170
171        @param os_if: an object providing interface to OS services
172        @param pub_key_file: the name of the file contaning a public key to
173                             use for verifying both existing and new firmware.
174        @param dev_key_path: path to directory containing *.vpubk and *.vbprivk
175                             files, for use in signing
176        @param target: flashrom target ('bios' or 'ec')
177        @param subdir: name of subdirectory of state dir, to use for sections
178                    Default: same as target, resulting in
179                    '/usr/local/tmp/faft/bios'
180        @type os_if: client.cros.faft.utils.os_interface.OSInterface
181        @type pub_key_file: str | None
182        @type dev_key_path: str
183        @type target: str
184        """
185        self.fum = None
186        self.image = ''
187        self.os_if = os_if
188        self.initialized = False
189        self._available = None
190        self._unavailable_err = None
191
192        if subdir is None:
193            subdir = target
194        self.subdir = subdir
195
196        self.pub_key_file = pub_key_file
197        self.dev_key_path = dev_key_path
198
199        self.target = target
200        if self.target == 'bios':
201            self.fum = saft_flashrom_util.flashrom_util(
202                    self.os_if, target_is_ec=False)
203            self.fv_sections = {
204                    'ro': FvSection(None, None, 'RO_FRID'),
205                    'a': FvSection('VBOOTA', 'FVMAIN', 'RW_FWID_A'),
206                    'b': FvSection('VBOOTB', 'FVMAINB', 'RW_FWID_B'),
207                    'me_a': FvSection(None, 'ME_RW_A'),
208                    'me_b': FvSection(None, 'ME_RW_B'),
209                    'rec': FvSection(None, 'RECOVERY_MRC_CACHE'),
210                    'ec_a': FvSection(None, 'ECMAINA'),
211                    'ec_b': FvSection(None, 'ECMAINB'),
212                    'rw_legacy': FvSection(None, 'RW_LEGACY'),
213            }
214        elif self.target == 'ec':
215            self.fum = saft_flashrom_util.flashrom_util(
216                    self.os_if, target_is_ec=True)
217            self.fv_sections = {
218                    'ro': FvSection(None, None, 'RO_FRID'),
219                    'rw': FvSection(None, 'EC_RW', 'RW_FWID'),
220                    'rw_b': FvSection(None, 'EC_RW_B'),
221            }
222        else:
223            raise FlashromHandlerError("Invalid target.")
224
225    def is_available(self):
226        """Check if the programmer is available, by specifying no commands.
227
228        @rtype: bool
229        """
230        if self._available is None:
231            # Cache the status to avoid trying flashrom every time.
232            try:
233                self.fum.check_target()
234                self._available = True
235            except error.CmdError as e:
236                # First line: "Command <flashrom -p host> failed, rc=2"
237                self._unavailable_err = str(e).split('\n', 1)[0]
238                self._available = False
239        return self._available
240
241    def section_file(self, *paths):
242        """
243        Return a full path for the given basename, in this handler's subdir.
244        Example: subdir 'bios' -> '/usr/local/tmp/faft/bios/FV_GBB'
245
246        @param paths: variable number of path pieces, same as in os.path.join
247        @return: an absolute path from this handler's subdir and the pieces.
248        """
249        if any(os.path.isabs(x) for x in paths):
250            raise FlashromHandlerError(
251                    "Absolute paths are not allowed in section_file()")
252
253        return os.path.join(self.os_if.state_dir, self.subdir, *paths)
254
255    def init(self, image_file=None, allow_fallback=False):
256        """Initialize the object, by reading the image.
257
258        This is separate from new_image, to isolate the implementation detail of
259        self.image being non-empty.
260
261        @param image_file: the path of the image file to read.
262                If None or empty string, read the flash device instead.
263        @param allow_fallback: if True, fall back to reading the flash device
264                if the image file doesn't exist.
265        @type image_file: str
266        @type allow_fallback: bool
267
268        @raise FlashromHandlerError: if no target flash device was usable.
269        """
270        # Raise an exception early if there's no usable flash.
271        if not self.is_available():
272            # Can't tell for sure whether it's broken or simply nonexistent.
273            raise FlashromHandlerError(
274                    "Could not detect a usable %s flash device: %s."
275                    % (self.target, self._unavailable_err))
276
277        if image_file and allow_fallback and not os.path.isfile(image_file):
278            logging.info(
279                    "Using %s flash contents instead of missing image: %s",
280                    self.target.upper(), image_file)
281            image_file = None
282
283        self.new_image(image_file)
284        self.initialized = True
285
286    def deinit(self):
287        """Clear the in-memory image data, and mark self uninitialized."""
288        self.image = ''
289        self.os_if.remove_dir(self.section_file())
290        self.initialized = False
291
292    def dump_flash(self, target_filename):
293        """Copy the flash device's data into a file, but don't parse it.
294
295        @param target_filename: the file to create
296        """
297        self.fum.dump_flash(target_filename)
298
299    def new_image(self, image_file=None):
300        """Parse the full flashrom image and store sections into files.
301
302        @param image_file: the name of the file containing a full ChromeOS
303                       flashrom image. If not passed in or empty, the actual
304                       flash device is read and its contents are saved into a
305                       temporary file which is used instead.
306        @type image_file: str | None
307
308        The input file is parsed and the sections of importance (as defined in
309        self.fv_sections) are saved in separate files in the state directory
310        as defined in the os_if object.
311        """
312
313        if image_file:
314            with open(image_file, 'rb') as image_f:
315                self.image = image_f.read()
316            self.fum.set_firmware_layout(image_file)
317        else:
318            self.image = self.fum.read_whole()
319
320        self.os_if.create_dir(self.section_file())
321
322        for section in self.fv_sections.values():
323            for subsection_name in section.names():
324                if not subsection_name:
325                    continue
326                blob = self.fum.get_section(self.image, subsection_name)
327                if blob:
328                    blob_filename = self.section_file(subsection_name)
329                    with open(blob_filename, 'wb') as blob_f:
330                        blob_f.write(blob)
331
332            blob = self.fum.get_section(self.image, section.get_body_name())
333            if blob:
334                s = hashlib.sha1()
335                s.update(blob)
336                section.set_sha(s.hexdigest())
337
338            # If there is no "sig" subsection, skip reading version and flags.
339            if not section.get_sig_name():
340                continue
341
342            # Now determine this section's version number.
343            vb_section = self.fum.get_section(self.image,
344                                              section.get_sig_name())
345
346            section.set_version(self.os_if.retrieve_body_version(vb_section))
347            section.set_flags(self.os_if.retrieve_preamble_flags(vb_section))
348            section.set_datakey_version(
349                    self.os_if.retrieve_datakey_version(vb_section))
350            section.set_kernel_subkey_version(
351                    self.os_if.retrieve_kernel_subkey_version(vb_section))
352
353            s = hashlib.sha1()
354            s.update(self.fum.get_section(self.image, section.get_sig_name()))
355            section.set_sig_sha(s.hexdigest())
356
357        if not self.pub_key_file:
358            self._retrieve_pub_key()
359
360    def _retrieve_pub_key(self):
361        """Retrieve root public key from the firmware GBB section."""
362
363        gbb_header_format = '<4s20s2I'
364        pubk_header_format = '<2Q'
365
366        gbb_section = self.fum.get_section(self.image, 'FV_GBB')
367
368        # do some confidence checks
369        try:
370            sig, _, rootk_offs, rootk_size = struct.unpack_from(
371                    gbb_header_format, gbb_section)
372        except struct.error as e:
373            raise FlashromHandlerError(e)
374
375        if sig != b'$GBB' or (rootk_offs + rootk_size) > len(gbb_section):
376            raise FlashromHandlerError("Bad gbb header sig:%s len:%s" %
377                                       (sig, len(gbb_section)))
378
379        key_body_offset, key_body_size = struct.unpack_from(
380                pubk_header_format, gbb_section, rootk_offs)
381
382        # Generally speaking the offset field can be anything, but in case of
383        # GBB section the key is stored as a standalone entity, so the offset
384        # of the key body is expected to be equal to the key header size of
385        # 0x20.
386        # Should this convention change, the check below would fail, which
387        # would be a good prompt for revisiting this test's behavior and
388        # algorithms.
389        if key_body_offset != 0x20 or key_body_size > rootk_size:
390            raise FlashromHandlerError('Bad public key format')
391
392        # All checks passed, let's store the key in a file.
393        self.pub_key_file = self.os_if.state_dir_file(self.PUB_KEY_FILE_NAME)
394        with open(self.pub_key_file, 'wb') as key_f:
395            key = gbb_section[rootk_offs:rootk_offs + key_body_offset +
396                              key_body_size]
397            key_f.write(key)
398
399    def verify_image(self):
400        """Confirm the image's validity.
401
402        Using the file supplied to init() as the public key container verify
403        the two sections' (FirmwareA and FirmwareB) integrity. The contents of
404        the sections is taken from the files created by new_image()
405
406        In case there is an integrity error raises FlashromHandlerError
407        exception with the appropriate error message text.
408        """
409
410        for section in self.fv_sections.values():
411            if section.get_sig_name():
412                cmd = 'vbutil_firmware --verify %s --signpubkey %s  --fv %s' % (
413                        self.section_file(section.get_sig_name()),
414                        self.pub_key_file,
415                        self.section_file(section.get_body_name()))
416                self.os_if.run_shell_command(cmd)
417
418    def _get_subsection_name(self, section, body_or_sig):
419        """Get the subsection name of body or signature."""
420        if section not in self.fv_sections:
421            raise FlashromHandlerError('Unknown FW section %s' % section)
422
423        # Get the appropriate section of the image.
424        if body_or_sig:
425            subsection_name = self.fv_sections[section].get_body_name()
426        else:
427            subsection_name = self.fv_sections[section].get_sig_name()
428        return subsection_name
429
430    def _get_subsection_one_byte(self, subsection):
431        """Get a specific byte within 2% of the subsection."""
432        if not self.image:
433            raise FlashromHandlerError(
434                    'Attempt at using an uninitialized object')
435        blob = self.fum.get_section(self.image, subsection)
436        offset = len(blob) // 50
437        return offset, blob[offset]
438
439    def get_firmware_sig_one_byte(self, section):
440        """Get a specific byte of firmware signature of the section."""
441        subsection = self._get_subsection_name(section, body_or_sig=False)
442        return self._get_subsection_one_byte(subsection)
443
444    def get_firmware_body_one_byte(self, section):
445        """Get a specific byte of firmware body of the section."""
446        subsection = self._get_subsection_name(section, body_or_sig=True)
447        return self._get_subsection_one_byte(subsection)
448
449    def _modify_subsection(self, subsection, offset, value):
450        """Modify a byte of subsection in the FLASHROM."""
451        if not self.image:
452            raise FlashromHandlerError(
453                    'Attempt at using an uninitialized object')
454        blob = self.fum.get_section(self.image, subsection)
455        blob_list = list(blob)
456        blob_list[offset] = value % 0x100
457        self.image = self.fum.put_section(self.image, subsection,
458                                          bytes(blob_list))
459        self.fum.write_partial(self.image, (subsection, ))
460
461    def modify_firmware_sig(self, section, offset, value):
462        """Modify a byte in firmware signature in the FLASHROM."""
463        subsection = self._get_subsection_name(section, body_or_sig=False)
464        self._modify_subsection(subsection, offset, value)
465
466    def modify_firmware_body(self, section, offset, value):
467        """Modify a byte in firmware body in the FLASHROM."""
468        subsection = self._get_subsection_name(section, body_or_sig=True)
469        self._modify_subsection(subsection, offset, value)
470
471    def corrupt_firmware_body(self, section):
472        """Corrupt the whole firmware body in the FLASHROM."""
473        subsection = self._get_subsection_name(section, body_or_sig=True)
474        if not self.image:
475            raise FlashromHandlerError(
476                    'Attempt at using an uninitialized object')
477        blob = self.fum.get_section(self.image, subsection)
478        blob_list = [(x + self.DELTA) % 0x100 for x in blob]
479        self.image = self.fum.put_section(self.image, subsection,
480                                          bytes(blob_list))
481        self.fum.write_partial(self.image, (subsection, ))
482
483    def corrupt_mrc_cache(self):
484        """Corrupt MRC cache in the FLASHROM."""
485        self.corrupt_firmware_body('rec')
486
487    def firmware_sections_equal(self):
488        """Check if firmware sections A and B are equal.
489
490        This function presumes that the entire BIOS image integrity has been
491        verified, so different signature sections mean different images and
492        vice versa.
493        """
494        sig_a = self.fum.get_section(self.image,
495                                     self.fv_sections['a'].get_sig_name())
496        sig_b = self.fum.get_section(self.image,
497                                     self.fv_sections['b'].get_sig_name())
498        return sig_a == sig_b
499
500    def copy_from_to(self, src, dst):
501        """Copy one firmware image section to another.
502
503        This function copies both signature and body of one firmware section
504        into another. After this function runs both sections are identical.
505        """
506        src_sect = self.fv_sections[src]
507        dst_sect = self.fv_sections[dst]
508        self.image = self.fum.put_section(
509                self.image, dst_sect.get_body_name(),
510                self.fum.get_section(self.image, src_sect.get_body_name()))
511        # If there is no "sig" subsection, skip copying signature.
512        if src_sect.get_sig_name() and dst_sect.get_sig_name():
513            self.image = self.fum.put_section(
514                    self.image, dst_sect.get_sig_name(),
515                    self.fum.get_section(self.image, src_sect.get_sig_name()))
516        self.write_whole()
517
518    def write_whole(self):
519        """Write the whole image into the flashrom."""
520
521        if not self.image:
522            raise FlashromHandlerError(
523                    'Attempt at using an uninitialized object')
524        self.fum.write_whole(self.image)
525
526    def write_partial(self, subsection_name, blob=None, write_through=True):
527        """Write the subsection part into the flashrom.
528
529        One can pass a blob to update the data of the subsection before write
530        it into the flashrom.
531        """
532
533        if not self.image:
534            raise FlashromHandlerError(
535                    'Attempt at using an uninitialized object')
536
537        if blob is not None:
538            self.image = self.fum.put_section(self.image, subsection_name,
539                                              blob)
540
541        if write_through:
542            self.dump_partial(
543                    subsection_name, self.section_file(subsection_name))
544            self.fum.write_partial(self.image, (subsection_name, ))
545
546    def dump_whole(self, filename):
547        """Write the whole image into a file."""
548
549        if not self.image:
550            raise FlashromHandlerError(
551                    'Attempt at using an uninitialized object')
552        open(filename, 'wb').write(self.image)
553
554    def dump_partial(self, subsection_name, filename):
555        """Write the subsection part into a file."""
556
557        if not self.image:
558            raise FlashromHandlerError(
559                    'Attempt at using an uninitialized object')
560        blob = self.fum.get_section(self.image, subsection_name)
561        open(filename, 'wb').write(blob)
562
563    def dump_section_body(self, section, filename):
564        """Write the body of a firmware section into a file"""
565        subsection_name = self.fv_sections[section].get_body_name()
566        self.dump_partial(subsection_name, filename)
567
568    def get_section_hash(self, section):
569        """Retrieve the hash of the body of a firmware section"""
570        ecrw = chip_utils.ecrw()
571
572        # add a dot to avoid set_from_file breaking if tmpname has an underscore
573        with tempfile.NamedTemporaryFile(prefix=ecrw.chip_name + '.') as f:
574            self.dump_section_body(section, f.name)
575            ecrw.set_from_file(f.name)
576            result = ecrw.compute_hash_bytes()
577        return result
578
579    def get_gbb_flags(self):
580        """Retrieve the GBB flags"""
581        gbb_header_format = '<12sL'
582        gbb_section = self.fum.get_section(self.image, 'FV_GBB')
583        try:
584            _, gbb_flags = struct.unpack_from(gbb_header_format, gbb_section)
585        except struct.error as e:
586            raise FlashromHandlerError(e)
587        return gbb_flags
588
589    def set_gbb_flags(self, flags, write_through=False):
590        """Retrieve the GBB flags"""
591        gbb_header_format = '<L'
592        section_name = 'FV_GBB'
593        gbb_section = self.fum.get_section(self.image, section_name)
594        try:
595            formatted_flags = struct.pack(gbb_header_format, flags)
596        except struct.error as e:
597            raise FlashromHandlerError(e)
598        gbb_section = gbb_section[:12] + formatted_flags + gbb_section[16:]
599        self.write_partial(section_name, gbb_section, write_through)
600
601    def enable_write_protect(self):
602        """Enable write protect of the flash chip"""
603        description = 'Failed to enable %s write-protect' % self.target
604        with _FlashromErrorWrapper(description):
605            self.fum.enable_write_protect()
606
607    def disable_write_protect(self):
608        """Disable write protect of the flash chip"""
609        description = 'Failed to disable %s write-protect' % self.target
610        with _FlashromErrorWrapper(description):
611            self.fum.disable_write_protect()
612
613    def set_write_protect_region(self, region, enabled=None):
614        """
615        Set write protection region by name, using current image's layout.
616
617        The name should match those seen in `futility dump_fmap <image>`, and
618        is not checked against self.firmware_layout, due to different naming.
619
620        @param region: Region to set (usually WP_RO)
621        @param enabled: If True, run --wp-enable; if False, run --wp-disable.
622                        If None (default), don't specify either one.
623        """
624        if region is None:
625            raise FlashromHandlerError("Region must not be None")
626        image_file = self.os_if.create_temp_file('wp_')
627        self.os_if.write_file(image_file, self.image)
628
629        if enabled is None:
630            verb = 'set'
631        else:
632            verb = 'enable' if enabled else 'disable'
633        msg = 'Failed to %s %s write-protect region (%s)' % (
634            verb, self.target, region)
635
636        with _FlashromErrorWrapper(msg):
637            self.fum.set_write_protect_region(image_file, region, enabled)
638
639        self.os_if.remove_file(image_file)
640
641    def set_write_protect_range(self, start, length, enabled=None):
642        """
643        Set write protection range by offsets, using current image's layout.
644
645        @param start: offset (bytes) from start of flash to start of range
646        @param length: offset (bytes) from start of range to end of range
647        @param enabled: If True, run --wp-enable; if False, run --wp-disable.
648                        If None (default), don't specify either one.
649        """
650        if enabled is None:
651            verb = 'set'
652        else:
653            verb = 'enable' if enabled else 'disable'
654        msg = 'Failed to %s %s write-protect range (start=%s, length=%s)' % (
655            verb, self.target, start, length)
656
657        with _FlashromErrorWrapper(msg):
658            self.fum.set_write_protect_range(start, length, enabled)
659
660    def get_write_protect_status(self):
661        """Get a dict describing the status of the write protection
662
663        @return: {'enabled': True/False, 'start': '0x0', 'length': '0x0', ...}
664        @rtype: dict
665        """
666        return self.fum.get_write_protect_status()
667
668    def get_section_sig_sha(self, section):
669        """Retrieve SHA1 hash of a firmware vblock section"""
670        return self.fv_sections[section].get_sig_sha()
671
672    def get_section_sha(self, section):
673        """Retrieve SHA1 hash of a firmware body section"""
674        return self.fv_sections[section].get_sha()
675
676    def get_section_version(self, section):
677        """Retrieve version number of a firmware section"""
678        return self.fv_sections[section].get_version()
679
680    def get_section_flags(self, section):
681        """Retrieve preamble flags of a firmware section"""
682        return self.fv_sections[section].get_flags()
683
684    def get_section_datakey_version(self, section):
685        """Retrieve data key version number of a firmware section"""
686        return self.fv_sections[section].get_datakey_version()
687
688    def get_section_kernel_subkey_version(self, section):
689        """Retrieve kernel subkey version number of a firmware section"""
690        return self.fv_sections[section].get_kernel_subkey_version()
691
692    def get_section_body(self, section):
693        """Retrieve body of a firmware section"""
694        subsection_name = self.fv_sections[section].get_body_name()
695        blob = self.fum.get_section(self.image, subsection_name)
696        return blob
697
698    def has_section_body(self, section):
699        """Return True if the section body is in the image"""
700        return bool(self.get_section_body(section))
701
702    def get_section_sig(self, section):
703        """Retrieve vblock of a firmware section"""
704        subsection_name = self.fv_sections[section].get_sig_name()
705        blob = self.fum.get_section(self.image, subsection_name)
706        return blob
707
708    def get_section_fwid(self, section, strip_null=True):
709        """
710        Retrieve fwid blob of a firmware section.
711
712        @param section: Name of the section whose fwid to return.
713        @param strip_null: If True, remove \0 from the end of the blob.
714        @return: fwid of the section
715
716        @type section: str
717        @type strip_null: bool
718        @rtype: bytes | None
719
720        """
721        subsection_name = self.fv_sections[section].get_fwid_name()
722        if not subsection_name:
723            return None
724        blob = self.fum.get_section(self.image, subsection_name)
725        if strip_null:
726            blob = blob.rstrip(b'\0')
727        return blob
728
729    def set_section_body(self, section, blob, write_through=False):
730        """Put the supplied blob to the body of the firmware section"""
731        subsection_name = self.fv_sections[section].get_body_name()
732        self.write_partial(subsection_name, blob, write_through)
733
734    def set_section_sig(self, section, blob, write_through=False):
735        """Put the supplied blob to the vblock of the firmware section"""
736        subsection_name = self.fv_sections[section].get_sig_name()
737        self.write_partial(subsection_name, blob, write_through)
738
739    def set_section_fwid(self, section, blob, write_through=False):
740        """Put the supplied blob to the fwid of the firmware section"""
741        subsection_name = self.fv_sections[section].get_fwid_name()
742        self.write_partial(subsection_name, blob, write_through)
743
744    def resign_ec_rwsig(self):
745        """Resign the EC image using rwsig."""
746        key_ec_efs = os.path.join(self.dev_key_path, self.EC_EFS_KEY_FILE_NAME)
747        # Dump whole EC image to a file and execute the sign command.
748        with tempfile.NamedTemporaryFile() as f:
749            self.dump_whole(f.name)
750            self.os_if.run_shell_command(
751                    'futility sign --type rwsig --prikey %s %s' % (key_ec_efs,
752                                                                   f.name))
753            self.new_image(f.name)
754
755    def set_section_version(self, section, version, flags,
756                            write_through=False):
757        """
758        Re-sign the firmware section using the supplied version number and
759        flag.
760        """
761        if (self.get_section_version(section) == version
762                    and self.get_section_flags(section) == flags):
763            logging.info(f"Nothing to do existing "
764                         f"version {self.get_section_version(section)} "
765                         f"flags {self.get_section_flags(section)}")
766            return  # No version or flag change, nothing to do.
767        if version < 0:
768            raise FlashromHandlerError(
769                    'Attempt to set version %d on section %s' % (version,
770                                                                 section))
771        fv_section = self.fv_sections[section]
772        sig_name = self.section_file(fv_section.get_sig_name())
773        sig_size = os.path.getsize(sig_name)
774
775        # Construct the command line
776        args = ['--vblock %s' % sig_name]
777        args.append('--keyblock %s' % os.path.join(self.dev_key_path,
778                                                   self.FW_KEYBLOCK_FILE_NAME))
779        args.append('--fv %s' % self.section_file(fv_section.get_body_name()))
780        args.append('--version %d' % version)
781        args.append('--kernelkey %s' % os.path.join(
782                self.dev_key_path, self.KERNEL_SUBKEY_FILE_NAME))
783        args.append('--signprivate %s' % os.path.join(
784                self.dev_key_path, self.FW_PRIV_DATA_KEY_FILE_NAME))
785        args.append('--flags %d' % flags)
786        cmd = 'vbutil_firmware %s' % ' '.join(args)
787        self.os_if.run_shell_command(cmd)
788
789        #  Pad the new signature.
790        with open(sig_name, 'ab') as sig_f:
791            f_size = os.fstat(sig_f.fileno()).st_size
792            pad = b'\0' * (sig_size - f_size)
793            sig_f.write(pad)
794
795        # Inject the new signature block into the image
796        with open(sig_name, 'rb') as sig_f:
797            new_sig = sig_f.read()
798        self.write_partial(fv_section.get_sig_name(), new_sig, write_through)
799
800    def _modify_section_fwid(self, section):
801        """Modify a section's fwid on the handler, adding a tilde and the
802        section name (in caps) to the end: ~RO, ~RW, ~A, ~B.
803
804        @param section: the single section to act on
805        @return: the new fwid
806
807        @type section: str
808        @rtype: str
809        """
810
811        fwid = self.get_section_fwid(section, strip_null=False)
812
813        if fwid is None:
814            return None
815
816        fwid_size = len(fwid)
817
818        if not fwid:
819            raise FlashromHandlerError(
820                    "FWID (%s, %s) is empty: %s" %
821                    (self.target.upper(), section.upper(), repr(fwid)))
822
823        fwid = fwid.rstrip(b'\0')
824        suffix = bytes(self.FWID_MOD_DELIMITER + section.upper(), 'utf-8')
825
826        if suffix in fwid:
827            raise FlashromHandlerError(
828                    "FWID (%s, %s) is already modified: %s" %
829                    (self.target.upper(), section.upper(), repr(fwid)))
830
831        # Append a suffix, after possibly chopping off characters to make room.
832        if len(fwid) + len(suffix) > fwid_size:
833            fwid = fwid[:fwid_size - len(suffix)]
834        fwid += suffix
835
836        padded_fwid = fwid.ljust(fwid_size, b'\0')
837        self.set_section_fwid(section, padded_fwid)
838        return fwid
839
840    def _strip_section_fwid(self, section, write_through=True):
841        """Modify a section's fwid on the handler, stripping any suffix added
842        by _modify_section_fwid: ~RO, ~RW, ~A, ~B.
843
844        @param section: the single section to act on
845        @param write_through: if True (default), write to flash immediately
846        @return: the suffix that was stripped
847
848        @type section: str
849        @type write_through: bool
850        @rtype: str | None
851        """
852
853        fwid = self.get_section_fwid(section, strip_null=False)
854        if fwid is None:
855            return None
856
857        fwid_size = len(fwid)
858
859        if not fwid:
860            raise FlashromHandlerError(
861                    "FWID (%s, %s) is empty: %s" %
862                    (self.target.upper(), section.upper(), repr(fwid)))
863
864        fwid = fwid.rstrip(b'\0')
865        mod_indicator = bytes(self.FWID_MOD_DELIMITER + section.upper(),
866                              'utf-8')
867
868        # Remove any suffix, and return the suffix if found.
869        if mod_indicator in fwid:
870            (stripped_fwid, remainder) = fwid.split(mod_indicator, 1)
871
872            padded_fwid = stripped_fwid.ljust(fwid_size, b'\0')
873            self.set_section_fwid(section, padded_fwid, write_through)
874
875            return fwid
876        return None
877
878    def modify_fwids(self, sections):
879        """Modify the fwid in the in-memory image.
880
881        @param sections: section(s) to modify.
882        @return: fwids for the modified sections, as {section: fwid}
883
884        @type sections: tuple | list
885        @rtype: dict
886        """
887        fwids = {}
888        for section in sections:
889            fwids[section] = self._modify_section_fwid(section)
890
891        return fwids
892
893    def strip_modified_fwids(self):
894        """Strip any trailing suffixes (from modify_fwids) out of the FWIDs.
895
896        @return: a dict of any fwids that were adjusted, by section (ro, a, b)
897        @rtype: dict
898        """
899
900        suffixes = {}
901        for section in self.fv_sections:
902            suffix = self._strip_section_fwid(section)
903            if suffix is not None:
904                suffixes[section] = suffix
905
906        return suffixes
907
908    def get_write_cmd(self, image=None):
909        """Get the command needed to write the whole image to the device.
910
911        @param image: the filename (empty to use current handler data)
912        """
913        if image:
914            return self.fum.get_write_cmd(image)
915        else:
916            tmp_image = self.os_if.create_temp_file('tmp_%s.bin.' % self.target)
917            self.os_if.write_file(tmp_image, self.image)
918            cmd = self.fum.get_write_cmd(tmp_image)
919            return '%s; rm %s' % (cmd, tmp_image)
920