169 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 | |
| # Use of this source code is governed by a BSD-style license that can be
 | |
| # found in the LICENSE file.
 | |
| 
 | |
| import logging
 | |
| import os
 | |
| import re
 | |
| 
 | |
| from autotest_lib.client.bin import test, utils
 | |
| from autotest_lib.client.common_lib import error
 | |
| 
 | |
| 
 | |
| class firmware_LockedME(test.test):
 | |
|     """Validates that the Management Engine has been locked."""
 | |
|     # Needed by autotest
 | |
|     version = 1
 | |
| 
 | |
|     # Temporary file to read BIOS image into. We run in a tempdir anyway, so it
 | |
|     # doesn't need a path.
 | |
|     BIOS_FILE = 'bios.bin'
 | |
|     RANDOM_FILE = 'newdata'
 | |
|     FLASHED_FILE = 'flasheddata'
 | |
| 
 | |
|     def flashrom(self, ignore_status=False, args=()):
 | |
|         """Run flashrom, expect it to work. Fail if it doesn't"""
 | |
|         extra = ['-p', 'host'] + list(args)
 | |
|         return utils.run('flashrom', ignore_status=ignore_status, args=extra)
 | |
| 
 | |
|     def determine_spi_rom_wp_status(self):
 | |
|         """Determine the AP SPI-ROM's write-protection status."""
 | |
|         flashrom_result = self.flashrom(args=('--wp-status',))
 | |
|         logging.info('The above flashrom command returns.... %s',
 | |
|                 flashrom_result.stdout)
 | |
|         if (("disabled" in flashrom_result.stdout) and
 | |
|                 ("start=0x00000000, len=0x0000000" in flashrom_result.stdout)):
 | |
|             return False
 | |
|         else:
 | |
|             return True
 | |
| 
 | |
|     def md5sum(self, filename):
 | |
|         """Run md5sum on a file
 | |
| 
 | |
|         @param filename: Filename to sum
 | |
|         @return: md5sum of the file as a 32-character hex string
 | |
|         """
 | |
|         r = utils.run('md5sum', ignore_status=False, args=[filename])
 | |
|         return r.stdout.split()[0]
 | |
| 
 | |
|     def has_ME(self):
 | |
|         """See if we can detect an ME.
 | |
|         FREG* is printed only when HSFS_FDV is set, which means the descriptor
 | |
|         table is valid. If we're running a BIOS without a valid descriptor this
 | |
|         step will fail. Unfortunately, we don't know of a simple and reliable
 | |
|         way to identify systems that have ME hardware.
 | |
|         """
 | |
|         logging.info('See if we have an ME...')
 | |
|         r = self.flashrom(args=('-V',))
 | |
|         return r.stdout.find("FREG0") >= 0
 | |
| 
 | |
|     def try_to_rewrite(self, sectname):
 | |
|         """If we can modify the ME section, restore it and raise an error."""
 | |
|         logging.info('Try to write section %s...', sectname)
 | |
|         size = os.stat(sectname).st_size
 | |
|         utils.run('dd', args=('if=/dev/urandom', 'of=%s' % (self.RANDOM_FILE),
 | |
|                               'count=1', 'bs=%d' % (size)))
 | |
|         self.flashrom(args=('-V', '-w', self.BIOS_FILE,
 | |
|                             '-i' , '%s:%s' % (sectname, self.RANDOM_FILE),
 | |
|                             '--fast-verify'),
 | |
|                       ignore_status=True)
 | |
|         self.flashrom(args=('-r',
 | |
|                             '-i', '%s:%s' % (sectname, self.FLASHED_FILE)))
 | |
|         md5sum_random = self.md5sum(filename=self.RANDOM_FILE)
 | |
|         md5sum_flashed = self.md5sum(filename=self.FLASHED_FILE)
 | |
|         if md5sum_random == md5sum_flashed:
 | |
|             logging.info('Oops, it worked! Put it back...')
 | |
|             self.flashrom(args=('-w', self.BIOS_FILE,
 | |
|                                 '-i', '%s:%s' % (sectname, sectname),
 | |
|                                 '--fast-verify'),
 | |
|                           ignore_status=True)
 | |
|             raise error.TestFail('%s is writable, ME is unlocked' % sectname)
 | |
| 
 | |
|     def check_manufacturing_mode(self):
 | |
|         """Fail if manufacturing mode is not found or enbaled."""
 | |
| 
 | |
|         # See if coreboot told us that the ME is still in Manufacturing Mode.
 | |
|         # It shouldn't be. We have to look only at the last thing it reports
 | |
|         # because it reports the values twice and the first one isn't always
 | |
|         # reliable.
 | |
|         logging.info('Check for Manufacturing Mode...')
 | |
|         last = None
 | |
|         with open('/sys/firmware/log') as infile:
 | |
|             for line in infile:
 | |
|                 if re.search('ME: Manufacturing Mode', line):
 | |
|                     last = line
 | |
|         if last is not None and last.find("YES") >= 0:
 | |
|             raise error.TestFail("The ME is still in Manufacturing Mode")
 | |
| 
 | |
|     def check_region_inaccessible(self, sectname):
 | |
|         """Test and ensure a region is not accessible by host CPU."""
 | |
| 
 | |
|         self.try_to_rewrite(sectname)
 | |
| 
 | |
|     def run_once(self, expect_me_present=True):
 | |
|         """Fail unless the ME is locked.
 | |
| 
 | |
|         @param expect_me_present: False means the system has no ME.
 | |
|         """
 | |
|         cpu_arch = utils.get_cpu_arch()
 | |
|         if cpu_arch == "arm":
 | |
|             raise error.TestNAError('This test is not applicable, '
 | |
|                     'because an ARM device has been detected. '
 | |
|                     'ARM devices do not have an ME (Management Engine)')
 | |
| 
 | |
|         cpu_family = utils.get_cpu_soc_family()
 | |
|         if cpu_family == "amd":
 | |
|             raise error.TestNAError('This test is not applicable, '
 | |
|                     'because an AMD device has been detected. '
 | |
|                     'AMD devices do not have an ME (Management Engine)')
 | |
| 
 | |
|         # If the AP SPI-ROM is blocking writes to the ME regions, and the ME
 | |
|         # regions are unlocked, they won't be writable, so will appear locked
 | |
|         # (i.e. this will be a false PASS).
 | |
|         if self.determine_spi_rom_wp_status():
 | |
|             raise error.TestFail('Software wp is enabled on the AP\'s SPI-ROM, '
 | |
|                 'or a protected range is set.  Please disable software wp and '
 | |
|                 'clear the protected range prior to running this test.')
 | |
| 
 | |
|         # See if the system even has an ME, and whether we expected that.
 | |
|         if self.has_ME():
 | |
|             if not expect_me_present:
 | |
|                 raise error.TestFail('We expected no ME, but found one anyway')
 | |
|         else:
 | |
|             if expect_me_present:
 | |
|                 raise error.TestNAError("No ME found. That's probably wrong.")
 | |
|             else:
 | |
|                 logging.info('We expected no ME and we have no ME, so pass.')
 | |
|                 return
 | |
| 
 | |
|         # Make sure manufacturing mode is off.
 | |
|         self.check_manufacturing_mode()
 | |
| 
 | |
|         # Read the image using flashrom.
 | |
|         self.flashrom(args=('-r', self.BIOS_FILE))
 | |
| 
 | |
|         # Use 'IFWI' fmap region as a proxy for a device which doesn't
 | |
|         # have a dedicated ME region in the boot media.
 | |
|         r = utils.run('dump_fmap', args=('-p', self.BIOS_FILE))
 | |
|         is_IFWI_platform = r.stdout.find("IFWI") >= 0
 | |
| 
 | |
|         # Get the bios image and extract the ME components
 | |
|         logging.info('Pull the ME components from the BIOS...')
 | |
|         dump_fmap_args = ['-x', self.BIOS_FILE, 'SI_DESC']
 | |
|         inaccessible_sections = []
 | |
|         if is_IFWI_platform:
 | |
|             inaccessible_sections.append('DEVICE_EXTENSION')
 | |
|         else:
 | |
|             inaccessible_sections.append('SI_ME')
 | |
|         dump_fmap_args.extend(inaccessible_sections)
 | |
|         utils.run('dump_fmap', args=tuple(dump_fmap_args))
 | |
| 
 | |
|         # So far, so good, but we need to be certain. Rather than parse what
 | |
|         # flashrom tells us about the ME-related registers, we'll just try to
 | |
|         # change the ME components. We shouldn't be able to.
 | |
|         inaccessible_sections.append('SI_DESC')
 | |
|         for sectname in inaccessible_sections:
 | |
|             self.check_region_inaccessible(sectname)
 | |
| 
 | |
|         # Okay, that's about all we can try. Looks like it's locked.
 |