# CHIPSEC: Platform Security Assessment Framework # Copyright (c) 2010-2022, 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 # """ Contains platform identification functions """ import errno import traceback import json from typing import Dict, Tuple, Type, Optional from chipsec.helper.oshelper import helper as os_helper from chipsec.helper.basehelper import Helper from chipsec.helper.nonehelper import NoneHelper from chipsec.hal.common.smbios import SMBIOS, SMBIOS_BIOS_INFO_ENTRY_ID, SMBIOS_SYSTEM_INFO_ENTRY_ID from chipsec.hal.hals import Hals # Hardware abstraction layer from chipsec.library.options import Options from chipsec.library.exceptions import UnknownChipsetError, OsHelperError from chipsec.library.logger import logger from chipsec.library.defines import ARCH_VID from chipsec.library.register import Register, RegData from chipsec.library.lock import Lock from chipsec.library.control import Control from chipsec.library.device import Device from chipsec.library.pci import PCI as pcilib from chipsec.config import Cfg, CHIPSET_CODE_UNKNOWN, PROC_FAMILY # DEBUG Flags QUIET_PCI_ENUM = True LOAD_COMMON = True # ############################################################################### # Functionality defining current chipset # ############################################################################### PCH_ADDRESS = { # Intel: 0:1F.0 ARCH_VID.INTEL: (0, 0x1F, 0), # AMD: 0:14.3 ARCH_VID.AMD: (0, 0x14, 3) } # Mapping from architecture VID to CPUID manufacturer string. Used to seed # Cfg.mfgid when running without a helper, so HAL dispatch (which keys off # mfgid) still selects the correct architecture-specific HALs. _MFGID_BY_VID = { ARCH_VID.INTEL: 'GenuineIntel', ARCH_VID.AMD: 'AuthenticAMD', } class Chipset: """Main chipset detection and configuration management class. This class provides the core functionality for platform identification, hardware abstraction layer management, and configuration parsing. """ def __init__(self): """Initialize the chipset object with default configuration.""" self.Cfg = Cfg() self.options = Options() self.logger = logger() self.helper = None self.os_helper = os_helper() self.init_hals_object() def init_hals_object(self): """Initialize or reinitialize the hardware abstraction layer.""" if hasattr(self, 'hals'): delattr(self, 'hals') self.hals = Hals(self) self._firmware_info: Optional[Dict[str, Optional[str]]] = None # ########################################################################### # Initialization # ########################################################################### def get_cpuid(self): """Get CPU identification information.""" return self.hals.cpuid.get_proc_info() def get_mfgid(self) -> str: """Get CPU manufacturer identification.""" return self.hals.cpuid.get_mfgid() @classmethod def basic_init_with_helper(cls, helper=None): """Create and initialize a chipset instance with a specific helper. Args: helper: The helper instance to use for hardware access Returns: Initialized Chipset instance """ _cs = cls() _cs.load_helper(helper) _cs.start_helper() return _cs def init(self, platform_code, req_pch_code, helper_name=None, start_helper=True, load_config=True, ignore_platform=False): """Initialize the chipset with platform detection and configuration. Args: platform_code: Platform code to force detection req_pch_code: PCH code to force detection helper_name: Name of helper to use for hardware access start_helper: Whether to start the helper immediately load_config: Whether to load platform configuration ignore_platform: Whether to skip platform detection Raises: UnknownChipsetError: If platform cannot be detected """ self.using_return_codes = False self.consistency_checking = False self.lock = Lock(self) self.register = Register(self) self.control = Control(self) self.device = Device(self) raise_unknown_platform = False msg = [] self.load_config = load_config _unknown_proc = True _unknown_pch = True # Platform detection cpuid = 0 if start_helper: self.load_helper(helper_name) self.start_helper() # Get CPUID only if using driver (otherwise it will cause problems) cpuid = self.get_cpuid() mfgid = self.get_mfgid() self.Cfg.set_cpuid(cpuid) self.Cfg.set_mfgid(mfgid) else: self.load_helper(NoneHelper()) self.Cfg.load() if load_config: self.init_cfg_bus() if start_helper: self.init_topology() else: # Seed a minimal topology so config parsers that reference CPU # (e.g. MSR scope handling) don't fail when running without a helper. self.Cfg.set_topology({'threads': 1, 'cores': {0: [0]}, 'packages': {0: [0]}}) self.Cfg.set_mfgid(_MFGID_BY_VID.get(self.Cfg.vid, 'GenuineIntel')) if not ignore_platform: self.Cfg.platform_detection(platform_code, req_pch_code, cpuid) _unknown_proc = not bool(self.Cfg.get_chipset_code()) pch_req_condition = (self.Cfg.is_pch_req() is False or self.Cfg.get_pch_code() != CHIPSET_CODE_UNKNOWN) if pch_req_condition: _unknown_pch = False if _unknown_proc: platform_msg = (f'Unknown Platform: VID = 0x{self.Cfg.vid:04X}, ' f'DID = 0x{self.Cfg.did:04X}, RID = 0x{self.Cfg.rid:02X}, ' f'CPUID = 0x{cpuid:X}') msg.append(platform_msg) if start_helper: self.logger.log_error(msg[-1]) raise_unknown_platform = True else: self.logger.log(f'[!] {msg}; Using Default.') # Don't initialize config if platform is unknown if not _unknown_proc: self.Cfg.load_platform_config() # Load Bus numbers for this platform. if self.logger.DEBUG: self.logger.log("[*] Discovering Bus Configuration:") if _unknown_pch: pch_msg = (f'Unknown PCH: VID = 0x{self.Cfg.pch_vid:04X}, DID = 0x{self.Cfg.pch_did:04X}, ' f'RID = 0x{self.Cfg.pch_rid:02X}') msg.append(pch_msg) if self.Cfg.is_pch_req() and start_helper: error_msg = f'Chipset requires a supported PCH to be loaded. {msg[-1]}' self.logger.log_error(error_msg) raise_unknown_platform = True else: self.logger.log(f'[!] {msg[-1]}; Using Default.') verbose_condition = (start_helper and ((self.logger.VERBOSE) or (load_config and (_unknown_pch or _unknown_proc)))) if verbose_condition: pcilib.print_pci_devices(self.hals.pci.enumerate_devices()) if _unknown_pch or _unknown_proc: msg.append('Results from this system may be incorrect.') self.logger.log(f'[!] {msg[-1]}') if raise_unknown_platform: raise UnknownChipsetError('\n'.join(msg)) def load_helper(self, helper_name): """Load and initialize a hardware helper. Args: helper_name: Name or instance of the helper to load Raises: OsHelperError: If the specified helper cannot be found or loaded """ if helper_name: if isinstance(helper_name, Helper): self.helper = helper_name else: self.helper = self.os_helper.get_helper(helper_name) if self.helper is None: error_msg = f'Helper named {helper_name} not found in available helpers' raise OsHelperError(error_msg, 1) else: self.helper = self.os_helper.get_default_helper() self.init_hals_object() def start_helper(self): """Start the hardware helper. Raises: OsHelperError: If the helper fails to start """ try: if not self.helper.create(): raise OsHelperError("failed to create OS helper", 1) if not self.helper.start(): raise OsHelperError("failed to start OS helper", 1) except Exception as msg: self.logger.log_debug(traceback.format_exc()) error_no = errno.ENXIO if hasattr(msg, 'errorcode'): error_no = msg.errorcode raise OsHelperError(f'Message: "{msg}"', error_no) def switch_helper(self, helper_name): """Switch to a different hardware helper. Args: helper_name: Name of the new helper to switch to Returns: str: Name of the previous helper """ oldName = self.helper.name self.destroy_helper() self.load_helper(helper_name) self.start_helper() return oldName def destroy_helper(self): """Clean up and destroy the current hardware helper.""" if not self.helper.stop(): self.logger.log_warning("failed to stop OS helper") else: if not self.helper.delete(): self.logger.log_warning("failed to delete OS helper") def is_core(self): """Check if platform is Core processor family.""" return self._check_proc_family("core") def is_server(self): """Check if platform is Xeon server processor family.""" return self._check_proc_family("xeon") def is_atom(self): """Check if platform is Atom processor family.""" return self._check_proc_family("atom") def _check_proc_family(self, proctype: str) -> bool: """Check if current platform belongs to specified processor family. Args: proctype: Processor family type to check Returns: bool: True if platform belongs to the specified family """ if proctype not in PROC_FAMILY: return False return self.Cfg.get_chipset_code() in PROC_FAMILY[proctype] def is_intel(self) -> bool: """Returns true if platform Vendor ID equals Intel VID.""" return self.is_arch(ARCH_VID.INTEL) def is_amd(self) -> bool: """Returns true if platform Vendor ID equals AMD VID.""" return self.is_arch(ARCH_VID.AMD) def is_arch(self, *arch_vid: int) -> bool: """Check support for multiple architecture VIDs. Args: *arch_vid: Variable number of architecture VIDs to check Returns: bool: True if platform VID matches any of the provided VIDs """ return self.Cfg.vid in arch_vid def init_cfg_bus(self) -> None: """Initialize PCI bus configuration by enumerating devices.""" enum_devices = {} self.logger.log_debug('[*] Loading device buses..') if QUIET_PCI_ENUM: old_log_state = self.save_log_state() self.set_log_state((False, False, False)) reuse_scan = self.options.get_section_data('PCI_Enum', 'reuse_platform_detection', None) if reuse_scan: try: enum_devices_filename = self.options.get_section_data('PCI_Enum', 'enum_devices_filename', None) enum_devices = json.load(open(enum_devices_filename)) except (IOError, json.JSONDecodeError): self.logger.log_debug('[*] Unable to load cached PCI configuration.') if not enum_devices: try: enum_devices = self.hals.pci.enumerate_devices() if reuse_scan: json.dump(enum_devices, open(enum_devices_filename, 'w')) except Exception: self.logger.log_debug('[*] Unable to enumerate PCI devices.') enum_devices = [] if QUIET_PCI_ENUM: self.set_log_state(old_log_state) self.Cfg.set_pci_data(enum_devices) def set_log_state(self, log_state: Tuple[bool, bool, bool]) -> None: """Set logger state for HAL, DEBUG, and VERBOSE flags.""" self.logger.HAL, self.logger.DEBUG, self.logger.VERBOSE = log_state self.logger.setlevel() def save_log_state(self) -> Tuple[bool, bool, bool]: """Save current logger state.""" return (self.logger.HAL, self.logger.DEBUG, self.logger.VERBOSE) def init_topology(self): """Initialize CPU topology information.""" self.logger.log_debug('[*] Gathering CPU Topology..') topology = self.hals.cpu.get_cpu_topology() self.Cfg.set_topology(topology) def is_all_value(self, regdata: Type[RegData], value: int, mask: Optional[int] = None) -> bool: """Check if all register data values match the specified value. Args: regdata: Register data to check value: Value to compare against mask: Optional mask to apply before comparison Returns: bool: True if all values match """ if mask is None: return all(n.value == value for n in regdata) else: newvalue = value & mask return all((n.value & mask) == newvalue for n in regdata) def is_any_value(self, regdata: Type[RegData], value: int, mask: Optional[int] = None) -> bool: """Check if any register data values match the specified value. Args: regdata: Register data to check value: Value to compare against mask: Optional mask to apply before comparison Returns: bool: True if any values match """ if mask is None: return any(n.value == value for n in regdata) else: newvalue = value & mask return any((n.value & mask) == newvalue for n in regdata) # ########################################################################### # Scoping functions # ########################################################################### def set_scope(self, scope): """Set the current scope for register access.""" self.Cfg.set_scope(scope) def clear_scope(self): """Clear the current scope for register access.""" self.Cfg.clear_scope() def _get_helper_firmware_info(self) -> Dict[str, Optional[str]]: if self.helper is None: return {'vendor': None, 'product': None, 'version': None, 'type': None} return { 'vendor': self.helper.firmware_vendor(), 'product': self.helper.firmware_product(), 'version': self.helper.firmware_version(), 'type': self.helper.firmware_type() } def _get_smbios_string(self, string_index: int, strings) -> Optional[str]: if string_index == 0 or strings is None or string_index > len(strings): return None value = strings[string_index - 1].strip() return value or None def _get_driver_firmware_info(self) -> Dict[str, Optional[str]]: info = {'vendor': None, 'product': None, 'version': None, 'type': None} smbios = SMBIOS(self) if not smbios.find_smbios_table(): return info bios_entries = smbios.get_decoded_structs(SMBIOS_BIOS_INFO_ENTRY_ID) if bios_entries: bios_info = bios_entries[0] info['vendor'] = self._get_smbios_string(bios_info.vendor_str, bios_info.strings) info['version'] = self._get_smbios_string(bios_info.version_str, bios_info.strings) system_entries = smbios.get_decoded_structs(SMBIOS_SYSTEM_INFO_ENTRY_ID) if system_entries: system_info = system_entries[0] info['product'] = self._get_smbios_string(system_info.product_str, system_info.strings) try: found, _, ect, _ = self.hals.uefi.find_EFI_Configuration_Table() info['type'] = 'UEFI' if found and ect is not None else 'BIOS' except Exception: pass return info def _get_firmware_info(self) -> Dict[str, Optional[str]]: if self._firmware_info is not None: return self._firmware_info helper_info = self._get_helper_firmware_info() if self.helper is not None and getattr(self.helper, 'driver_loaded', False): info = self._get_driver_firmware_info() for key, value in helper_info.items(): if info[key] is None: info[key] = value else: info = helper_info self._firmware_info = info return info def firmware_vendor(self) -> Optional[str]: return self._get_firmware_info()['vendor'] def firmware_product(self) -> Optional[str]: return self._get_firmware_info()['product'] def firmware_version(self) -> Optional[str]: return self._get_firmware_info()['version'] def firmware_type(self) -> Optional[str]: return self._get_firmware_info()['type'] # ############################################################################### # Global chipset management # ############################################################################### _chipset = None def clear_cs(): """Clear the global chipset instance.""" global _chipset _chipset = None def cs() -> Chipset: """Get or create the global chipset instance. Returns: Chipset: The global chipset instance """ global _chipset if _chipset is None: _chipset = Chipset() return _chipset