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