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