#CHIPSEC: Platform Security Assessment Framework #Copyright (c) 2010-2020, Intel Corporation # #This program is free software; you can redistribute it and/or #modify it under the terms of the GNU General Public License #as published by the Free Software Foundation; Version 2. # #This program is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. # #You should have received a copy of the GNU General Public License #along with this program; if not, write to the Free Software #Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # #Contact information: #chipsec@intel.com # """ `UEFI 2.4 spec Section 28 `_ Verify that all Secure Boot key UEFI variables are authenticated (BS+RT+AT) and protected from unauthorized modification. Use '-a modify' option for the module to also try to write/corrupt the variables. """ from chipsec.module_common import BaseModule, ModuleResult, MTAG_SECUREBOOT, OPT_MODIFY from chipsec.hal.uefi import UEFI, SECURE_BOOT_VARIABLES, IS_VARIABLE_ATTRIBUTE, EFI_VAR_NAME_SecureBoot, SECURE_BOOT_KEY_VARIABLES from chipsec.hal.uefi import EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS, EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS from chipsec.hal.uefi_common import StatusCode # ############################################################ # SPECIFY PLATFORMS THIS MODULE IS APPLICABLE TO # ############################################################ _MODULE_NAME = 'variables' TAGS = [MTAG_SECUREBOOT] class variables(BaseModule): def __init__(self): BaseModule.__init__(self) self._uefi = UEFI( self.cs ) def is_supported( self ): supported = self.cs.helper.EFI_supported() if not supported: self.logger.log_skipped_check( "OS does not support UEFI Runtime API" ) return supported def can_modify( self, name, guid, data, attrs ): self.logger.log( " > attempting to modify variable {}:{}".format(guid, name) ) datalen = len(data) #print_buffer( data ) baddata = chr( ord(data[0]) ^ 0xFF ) + data[1:] #if datalen > 1: baddata = baddata[:datalen-1] + chr( ord(baddata[datalen-1]) ^ 0xFF ) status = self._uefi.set_EFI_variable( name, guid, baddata ) if StatusCode.EFI_SUCCESS != status: self.logger.log( ' < modification of {} returned error 0x{:X}'.format(name, status) ) else: self.logger.log( ' < modification of {} returned succees'.format(name) ) self.logger.log( ' > checking variable {} contents after modification..'.format(name) ) newdata = self._uefi.get_EFI_variable( name, guid ) #print_buffer( newdata ) #chipsec.file.write_file( name+'_'+guid+'.bin', data ) #chipsec.file.write_file( name+'_'+guid+'.bin.bad', baddata ) #chipsec.file.write_file( name+'_'+guid+'.bin.new', newdata ) _changed = (data != newdata) if _changed: self.logger.log_bad( "EFI variable {} has been modified. Restoring original contents..".format(name) ) self._uefi.set_EFI_variable( name, guid, data ) # checking if restored correctly restoreddata = self._uefi.get_EFI_variable( name, guid ) #print_buffer( restoreddata ) if (restoreddata != data): self.logger.error( "Failed to restore contents of variable {} failed!".format(name) ) else: self.logger.log( " contents of variable {} have been restored".format(name) ) else: self.logger.log_good( "Could not modify UEFI variable {}:{}".format(guid, name) ) return _changed ## check_secureboot_variable_attributes # checks authentication attributes of Secure Boot EFI variables def check_secureboot_variable_attributes( self, do_modify ): res = ModuleResult.ERROR not_found = 0 not_auth = 0 not_wp = 0 is_secureboot_enabled = False sbvars = self._uefi.list_EFI_variables() if sbvars is None: self.logger.log_warn_check( 'Could not enumerate UEFI variables.' ) return ModuleResult.SKIPPED for name in SECURE_BOOT_VARIABLES: if name in sbvars.keys() and sbvars[name] is not None: if len(sbvars[name]) > 1: self.logger.log_failed_check( 'There should only be one instance of variable {}'.format(name) ) return ModuleResult.FAILED for (off, buf, hdr, data, guid, attrs) in sbvars[name]: self.logger.log( "[*] Checking protections of UEFI variable {}:{}".format(guid, name) ) # check the status of Secure Boot if EFI_VAR_NAME_SecureBoot == name: is_secureboot_enabled = (data is not None and len(data) == 1 and ord(data) == 0x1) # # Verify if the Secure Boot key/database variable is authenticated # if name in SECURE_BOOT_KEY_VARIABLES: if IS_VARIABLE_ATTRIBUTE( attrs, EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS ): self.logger.log_good( 'Variable {}:{} is authenticated (AUTHENTICATED_WRITE_ACCESS)'.format(guid, name) ) elif IS_VARIABLE_ATTRIBUTE( attrs, EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS ): self.logger.log_good( 'Variable {}:{} is authenticated (TIME_BASED_AUTHENTICATED_WRITE_ACCESS)'.format(guid, name) ) else: not_auth += 1 self.logger.log_bad( 'Variable {}:{} is not authenticated'.format(guid, name) ) # # Attempt to modify contents of the variables # if do_modify: if self.can_modify( name, guid, data, attrs ): not_wp += 1 else: not_found += 1 self.logger.log_important( 'Secure Boot variable {} is not found'.format(name) ) continue self.logger.log( '' ) self.logger.log( '[*] Secure Boot appears to be {}abled'.format('en' if is_secureboot_enabled else 'dis') ) if len(SECURE_BOOT_VARIABLES) == not_found: # None of Secure Boot variables were not found self.logger.log_skipped_check( 'None of required Secure Boot variables found. Secure Boot is not enabled' ) return ModuleResult.SKIPPED else: # Some Secure Boot variables exist sb_vars_failed = (not_found > 0) or (not_auth > 0) or (not_wp > 0) if sb_vars_failed: if not_found > 0: self.logger.log_bad( "Some required Secure Boot variables are missing" ) if not_auth > 0: self.logger.log_bad( 'Some Secure Boot keying variables are not authenticated' ) if not_wp > 0: self.logger.log_bad( 'Some Secure Boot variables can be modified' ) if is_secureboot_enabled: self.logger.log_failed_check( 'Not all Secure Boot UEFI variables are protected' ) return ModuleResult.FAILED else: self.logger.log_warn_check( 'Not all Secure Boot UEFI variables are protected' ) return ModuleResult.WARNING else: self.logger.log_passed_check( 'All Secure Boot UEFI variables are protected' ) return ModuleResult.PASSED # -------------------------------------------------------------------------- # run( module_argv ) # Required function: run here all tests from this module # -------------------------------------------------------------------------- def run( self, module_argv ): self.logger.start_test( "Attributes of Secure Boot EFI Variables" ) do_modify = (len(module_argv) > 0 and module_argv[0] == OPT_MODIFY) return self.check_secureboot_variable_attributes( do_modify )