mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
f2dfc55488
Signed-off-by: Nathaniel Mitchell <nathaniel.p.mitchell@intel.com>
228 lines
8.8 KiB
Python
228 lines
8.8 KiB
Python
# CHIPSEC: Platform Security Assessment Framework
|
|
# Copyright (c) 2025, 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.
|
|
|
|
import xml.etree.ElementTree as ET
|
|
from typing import Dict, List, Tuple
|
|
import os.path as op
|
|
import sys
|
|
from os import listdir
|
|
|
|
sys.path.append(op.abspath(op.join(__file__, "..", "..")))
|
|
from chipsec.library.file import get_main_dir
|
|
from chipsec.library.defines import is_hex
|
|
|
|
CFG_PATH = op.join(get_main_dir(), 'chipsec', 'cfg') # Where to look for config files
|
|
|
|
"""
|
|
Usage: python xml_validator.py [config_file1] [config_file2] ...
|
|
Run with no arguments to vlidatate all XML config files in the default cfg directory.
|
|
"""
|
|
|
|
class ConfigAttributeValidator:
|
|
"""Validates XML configuration attributes against expected data types"""
|
|
|
|
def __init__(self):
|
|
# Define expected attribute types based on _config_convert_data function
|
|
self.integer_attrs = {'dev', 'fun', 'vid', 'did', 'rid', 'offset',
|
|
'bit', 'size', 'port', 'msr', 'value', 'address',
|
|
'fixed_address', 'base_align', 'align_bits', 'mask',
|
|
'reg_align', 'limit_align', 'regh_align', 'width', 'reg'}
|
|
|
|
self.boolean_attrs = {'req_pch'}
|
|
self.int_list_attrs = {'bus'}
|
|
self.str_list_attrs = {'config'}
|
|
self.range_list_attrs = {'detection_value'}
|
|
self.validation_errors = []
|
|
self.passed_file_count = 0
|
|
self.file_count = 0
|
|
|
|
|
|
def validate_integer_value(self, value: str) -> bool:
|
|
"""Validate if a string can be converted to integer (base 10 or 16)"""
|
|
try:
|
|
int(value, 0)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def validate_boolean_value(self, value: str) -> bool:
|
|
"""Validate if a string represents a boolean value"""
|
|
return value.lower() in ('true', 'false')
|
|
|
|
def validate_integer_list(self, value: str) -> bool:
|
|
"""Validate comma-separated integer values"""
|
|
try:
|
|
for item in value.split(','):
|
|
int(item.strip(), 0)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def validate_range_format(self, value: str) -> bool:
|
|
"""Validate range format (wildcards, ranges, single values)"""
|
|
try:
|
|
for item in value.split(','):
|
|
item = item.strip()
|
|
if item.upper().endswith('*'):
|
|
int(item.replace('*', '0'), 0)
|
|
elif '-' in item:
|
|
parts = item.split('-', 1)
|
|
if len(parts) != 2:
|
|
return False
|
|
int(parts[0], 0)
|
|
int(parts[1], 0)
|
|
else:
|
|
int(item, 0)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
def validate_attribute(self, attr_name: str, attr_value: str, element_tag: str, did_is_range: bool = False) -> bool:
|
|
"""Validate a single attribute based on its expected type"""
|
|
# Handle special case where 'did' can be range format
|
|
if did_is_range and attr_name == 'did':
|
|
if not self.validate_range_format(attr_value):
|
|
self.validation_errors.append(
|
|
f"Element '{element_tag}': Attribute '{attr_name}' has invalid range format: '{attr_value}'"
|
|
)
|
|
return False
|
|
elif attr_name in self.integer_attrs:
|
|
if not self.validate_integer_value(attr_value):
|
|
self.validation_errors.append(
|
|
f"Element '{element_tag}': Attribute '{attr_name}' must be integer: '{attr_value}'"
|
|
)
|
|
return False
|
|
elif attr_name in self.boolean_attrs:
|
|
if not self.validate_boolean_value(attr_value):
|
|
self.validation_errors.append(
|
|
f"Element '{element_tag}': Attribute '{attr_name}' must be boolean (true/false): '{attr_value}'"
|
|
)
|
|
return False
|
|
elif attr_name in self.int_list_attrs:
|
|
if not self.validate_integer_list(attr_value):
|
|
self.validation_errors.append(
|
|
f"Element '{element_tag}': Attribute '{attr_name}' must be comma-separated integers: '{attr_value}'"
|
|
)
|
|
return False
|
|
elif attr_name in self.range_list_attrs:
|
|
if not self.validate_range_format(attr_value):
|
|
self.validation_errors.append(
|
|
f"Element '{element_tag}': Attribute '{attr_name}' has invalid range format: '{attr_value}'"
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
def validate_xml_element(self, element: ET.Element, did_is_range: bool = False) -> bool:
|
|
"""Validate all attributes of an XML element"""
|
|
element_valid = True
|
|
|
|
for attr_name, attr_value in element.attrib.items():
|
|
if not self.validate_attribute(attr_name, attr_value, element.tag, did_is_range):
|
|
element_valid = False
|
|
|
|
return element_valid
|
|
|
|
def validate_xml_file(self, file_path: str) -> Tuple[bool, List[str]]:
|
|
"""Validate an entire XML configuration file"""
|
|
self.validation_errors = []
|
|
|
|
try:
|
|
tree = ET.parse(file_path)
|
|
root = tree.getroot()
|
|
except ET.ParseError as e:
|
|
self.validation_errors.append(f"XML parsing error in '{file_path}': {e}")
|
|
return False, self.validation_errors
|
|
except FileNotFoundError:
|
|
self.validation_errors.append(f"File not found: '{file_path}'")
|
|
return False, self.validation_errors
|
|
|
|
file_valid = True
|
|
|
|
# Validate different element types with appropriate settings
|
|
for element in root.iter():
|
|
if element.tag in ('device', 'sku'):
|
|
# These elements can have 'did' as range
|
|
if not self.validate_xml_element(element, did_is_range=True):
|
|
file_valid = False
|
|
else:
|
|
if not self.validate_xml_element(element):
|
|
file_valid = False
|
|
|
|
return file_valid, self.validation_errors
|
|
|
|
def print_validation_report(self, file_path: str):
|
|
"""Print a validation report for a configuration file"""
|
|
is_valid, errors = self.validate_xml_file(file_path)
|
|
self.file_count += 1
|
|
message = "\t"
|
|
if is_valid:
|
|
message += f"✓ Configuration file is valid for: {file_path}"
|
|
self.passed_file_count += 1
|
|
else:
|
|
message += f"✗ Found {len(errors)} validation errors in {file_path}:"
|
|
for error in errors:
|
|
message += f"\n\t- {error}"
|
|
print(message)
|
|
|
|
def print_summary(self):
|
|
"""Print a summary of validation results"""
|
|
print(f"\nValidation Summary: {self.passed_file_count}/{self.file_count} files passed validation.")
|
|
|
|
|
|
def validate_configuration_files(file_paths: List[str]) -> Dict[str, bool]:
|
|
"""Validate multiple configuration files and return results"""
|
|
validator = ConfigAttributeValidator()
|
|
results = {}
|
|
print("Validation Report:\n")
|
|
for file_path in file_paths:
|
|
is_valid, _ = validator.validate_xml_file(file_path)
|
|
results[file_path] = is_valid
|
|
validator.print_validation_report(file_path)
|
|
validator.print_summary()
|
|
return results
|
|
|
|
|
|
def find_xml_files(paths: List[str]) -> List[str]:
|
|
files = []
|
|
dirs = []
|
|
for path in paths:
|
|
for cfg_file in listdir(path):
|
|
filepath = op.join(path, cfg_file)
|
|
if op.isdir(filepath):
|
|
dirs.append(filepath)
|
|
if filepath.endswith('.xml'):
|
|
files.append(filepath)
|
|
if dirs:
|
|
files.extend(find_xml_files(dirs))
|
|
return files
|
|
|
|
|
|
def find_base_config_dirs() -> List[str]:
|
|
vid_list = [f for f in listdir(CFG_PATH) if op.isdir(op.join(CFG_PATH, f)) and is_hex(f)]
|
|
base_dirs = [op.join(CFG_PATH, vid) for vid in vid_list]
|
|
return base_dirs
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
|
|
if len(sys.argv) < 2:
|
|
results = validate_configuration_files(find_xml_files(find_base_config_dirs()))
|
|
else:
|
|
results = validate_configuration_files(sys.argv[1:])
|
|
|
|
if all(results.values()):
|
|
sys.exit(0)
|
|
else:
|
|
sys.exit(1)
|