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