• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7import re
8
9from autotest_lib.client.bin import test, utils
10from autotest_lib.client.common_lib import error
11
12
13class firmware_LockedME(test.test):
14    """Validates that the Management Engine has been locked."""
15    # Needed by autotest
16    version = 1
17
18    # Temporary file to read BIOS image into. We run in a tempdir anyway, so it
19    # doesn't need a path.
20    BIOS_FILE = 'bios.bin'
21    RANDOM_FILE = 'newdata'
22    FLASHED_FILE = 'flasheddata'
23
24    def flashrom(self, ignore_status=False, args=()):
25        """Run flashrom, expect it to work. Fail if it doesn't"""
26        extra = ['-p', 'host'] + list(args)
27        return utils.run('flashrom', ignore_status=ignore_status, args=extra)
28
29    def determine_spi_rom_wp_status(self):
30        """Determine the AP SPI-ROM's write-protection status."""
31        flashrom_result = self.flashrom(args=('--wp-status',))
32        logging.info('The above flashrom command returns.... %s',
33                flashrom_result.stdout)
34        if (("disabled" in flashrom_result.stdout) and
35                ("start=0x00000000, len=0x0000000" in flashrom_result.stdout)):
36            return False
37        else:
38            return True
39
40    def md5sum(self, filename):
41        """Run md5sum on a file
42
43        @param filename: Filename to sum
44        @return: md5sum of the file as a 32-character hex string
45        """
46        r = utils.run('md5sum', ignore_status=False, args=[filename])
47        return r.stdout.split()[0]
48
49    def has_ME(self):
50        """See if we can detect an ME.
51        FREG* is printed only when HSFS_FDV is set, which means the descriptor
52        table is valid. If we're running a BIOS without a valid descriptor this
53        step will fail. Unfortunately, we don't know of a simple and reliable
54        way to identify systems that have ME hardware.
55        """
56        logging.info('See if we have an ME...')
57        r = self.flashrom(args=('-V',))
58        return r.stdout.find("FREG0") >= 0
59
60    def try_to_rewrite(self, sectname):
61        """If we can modify the ME section, restore it and raise an error."""
62        logging.info('Try to write section %s...', sectname)
63        size = os.stat(sectname).st_size
64        utils.run('dd', args=('if=/dev/urandom', 'of=%s' % (self.RANDOM_FILE),
65                              'count=1', 'bs=%d' % (size)))
66        self.flashrom(args=('-V', '-w', self.BIOS_FILE,
67                            '-i' , '%s:%s' % (sectname, self.RANDOM_FILE),
68                            '--noverify-all'),
69                      ignore_status=True)
70        self.flashrom(args=('-r',
71                            '-i', '%s:%s' % (sectname, self.FLASHED_FILE)))
72        md5sum_random = self.md5sum(filename=self.RANDOM_FILE)
73        md5sum_flashed = self.md5sum(filename=self.FLASHED_FILE)
74        if md5sum_random == md5sum_flashed:
75            logging.info('Oops, it worked! Put it back...')
76            self.flashrom(args=('-w', self.BIOS_FILE,
77                                '-i', '%s:%s' % (sectname, sectname),
78                                '--noverify-all'),
79                          ignore_status=True)
80            raise error.TestFail('%s is writable, ME is unlocked' % sectname)
81
82    def check_manufacturing_mode(self):
83        """Fail if manufacturing mode is not found or enbaled."""
84
85        # See if coreboot told us that the ME is still in Manufacturing Mode.
86        # It shouldn't be. We have to look only at the last thing it reports
87        # because it reports the values twice and the first one isn't always
88        # reliable.
89        logging.info('Check for Manufacturing Mode...')
90        last = None
91        with open('/sys/firmware/log') as infile:
92            for line in infile:
93                if re.search('ME: Manufacturing Mode', line):
94                    last = line
95        if last is not None and last.find("YES") >= 0:
96            raise error.TestFail("The ME is still in Manufacturing Mode")
97
98    def check_region_inaccessible(self, sectname):
99        """Test and ensure a region is not accessible by host CPU."""
100
101        self.try_to_rewrite(sectname)
102
103    def run_once(self, expect_me_present=True):
104        """Fail unless the ME is locked.
105
106        @param expect_me_present: False means the system has no ME.
107        """
108        cpu_arch = utils.get_cpu_arch()
109        if cpu_arch == "arm":
110            raise error.TestNAError('This test is not applicable, '
111                    'because an ARM device has been detected. '
112                    'ARM devices do not have an ME (Management Engine)')
113
114        cpu_family = utils.get_cpu_soc_family()
115        if cpu_family == "amd":
116            raise error.TestNAError('This test is not applicable, '
117                    'because an AMD device has been detected. '
118                    'AMD devices do not have an ME (Management Engine)')
119
120        # If the AP SPI-ROM is blocking writes to the ME regions, and the ME
121        # regions are unlocked, they won't be writable, so will appear locked
122        # (i.e. this will be a false PASS).
123        if self.determine_spi_rom_wp_status():
124            raise error.TestFail('Software wp is enabled on the AP\'s SPI-ROM, '
125                'or a protected range is set.  Please disable software wp and '
126                'clear the protected range prior to running this test.')
127
128        # See if the system even has an ME, and whether we expected that.
129        if self.has_ME():
130            if not expect_me_present:
131                raise error.TestFail('We expected no ME, but found one anyway')
132        else:
133            if expect_me_present:
134                raise error.TestNAError("No ME found. That's probably wrong.")
135            else:
136                logging.info('We expected no ME and we have no ME, so pass.')
137                return
138
139        # Make sure manufacturing mode is off.
140        self.check_manufacturing_mode()
141
142        # Read the image using flashrom.
143        self.flashrom(args=('-r', self.BIOS_FILE))
144
145        # Use 'IFWI' fmap region as a proxy for a device which doesn't
146        # have a dedicated ME region in the boot media.
147        r = utils.run('dump_fmap', args=('-p', self.BIOS_FILE))
148        is_IFWI_platform = r.stdout.find("IFWI") >= 0
149
150        # Get the bios image and extract the ME components
151        logging.info('Pull the ME components from the BIOS...')
152        dump_fmap_args = ['-x', self.BIOS_FILE, 'SI_DESC']
153        inaccessible_sections = []
154        if is_IFWI_platform:
155            inaccessible_sections.append('DEVICE_EXTENSION')
156        else:
157            inaccessible_sections.append('SI_ME')
158        dump_fmap_args.extend(inaccessible_sections)
159        utils.run('dump_fmap', args=tuple(dump_fmap_args))
160
161        # So far, so good, but we need to be certain. Rather than parse what
162        # flashrom tells us about the ME-related registers, we'll just try to
163        # change the ME components. We shouldn't be able to.
164        inaccessible_sections.append('SI_DESC')
165        for sectname in inaccessible_sections:
166            self.check_region_inaccessible(sectname)
167
168        # Okay, that's about all we can try. Looks like it's locked.
169