Files
chipsec-chipsec/chipsec/chipset.py
2026-05-26 11:15:30 -07:00

506 lines
18 KiB
Python

# 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