Files
chipsec-chipsec/tests/xml_validator.py
Nathaniel Mitchell f2dfc55488 Add xml_validator script to check attribute types
Signed-off-by: Nathaniel Mitchell <nathaniel.p.mitchell@intel.com>
2026-01-29 11:59:38 -08:00

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)