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