mirror of
https://github.com/chipsec/chipsec
synced 2026-06-08 13:31:00 +00:00
79f81bbb7e
This commit fixes whitespace errors from checkers W291,W292,W293,W391. They were found by running "flake8 ." from the root directory. These are errors defined by "pycodestyle", and have no functional impact to the source. This is simply fixing whitespace as defined by CHIPSEC's .flake8 configuration. background: https://www.flake8rules.com/rules/W291.html https://www.flake8rules.com/rules/W292.html https://www.flake8rules.com/rules/W293.html https://www.flake8rules.com/rules/W391.html tool versions: flake8 v7.3.0, python v3.13.11 Signed-off-by: William Leara <william.leara@dell.com>
541 lines
18 KiB
Python
541 lines
18 KiB
Python
# CHIPSEC: Platform Security Assessment Framework
|
|
# Copyright (c) 2010-2021, 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
|
|
#
|
|
|
|
"""
|
|
Logging functions
|
|
"""
|
|
|
|
import csv
|
|
import logging
|
|
import platform
|
|
import string
|
|
import sys
|
|
import os
|
|
from time import localtime, strftime
|
|
from typing import Tuple, Dict, List, Optional
|
|
from enum import Enum
|
|
|
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
BASE_PATH = os.path.join(dir_path, os.pardir, os.pardir)
|
|
LOGGER_NAME = 'CHIPSEC_LOGGER'
|
|
|
|
|
|
class level(Enum):
|
|
DEBUG = 10
|
|
HELPER = 11
|
|
HAL = 12
|
|
VERBOSE = 13
|
|
INFO = 20
|
|
GOOD = 21
|
|
BAD = 22
|
|
IMPORTANT = 23
|
|
WARNING = 30
|
|
ERROR = 40
|
|
CRITICAL = 50
|
|
EXCEPTION = 60
|
|
|
|
|
|
class chipsecFilter(logging.Filter):
|
|
def __init__(self, name: str = ...) -> None:
|
|
super().__init__(name)
|
|
|
|
def filter(self, record):
|
|
if record.levelno == level.ERROR.value:
|
|
record.additional = 'ERROR: '
|
|
elif record.levelno == level.WARNING.value:
|
|
record.additional = 'WARNING: '
|
|
elif record.levelno == level.IMPORTANT.value:
|
|
record.additional = '[!] '
|
|
elif record.levelno == level.GOOD.value:
|
|
record.additional = '[+] '
|
|
elif record.levelno == level.BAD.value:
|
|
record.additional = '[-] '
|
|
elif record.levelno == level.DEBUG.value:
|
|
record.additional = '[*] [DEBUG] '
|
|
elif record.levelno == level.VERBOSE.value:
|
|
record.additional = '[*] [VERBOSE] '
|
|
elif record.levelno == level.HAL.value:
|
|
record.additional = '[*] [HAL] '
|
|
elif record.levelno == level.HELPER.value:
|
|
record.additional = '[*] [HELPER] '
|
|
else:
|
|
record.additional = ''
|
|
return True
|
|
|
|
|
|
class chipsecLogFormatter(logging.Formatter):
|
|
def __init__(self, fmt: Optional[str] = ..., datefmt: Optional[str] = ..., style='%') -> None:
|
|
super().__init__(fmt, datefmt, style)
|
|
self.infmt = fmt
|
|
|
|
def format(self, record):
|
|
if record.args:
|
|
record.args = tuple()
|
|
formatter = logging.Formatter(self.infmt)
|
|
return formatter.format(record)
|
|
|
|
|
|
class chipsecStreamFormatter(logging.Formatter):
|
|
try:
|
|
is_atty = sys.stdout.isatty()
|
|
except AttributeError:
|
|
is_atty = False
|
|
# Respect https://no-color.org/ convention, and disable colorization
|
|
# when the output is not a terminal (eg. redirection to a file)
|
|
mPlatform = platform.system().lower()
|
|
if is_atty and os.getenv('NO_COLOR') is None and (("windows" == mPlatform) or "linux" == mPlatform):
|
|
if mPlatform == 'windows':
|
|
_ = os.system('color')
|
|
colors = {
|
|
'GREY': '\033[90m',
|
|
'RED': '\033[91m',
|
|
'GREEN': '\033[92m',
|
|
'YELLOW': '\033[93m',
|
|
'BLUE': '\033[94m',
|
|
'PURPLE': '\033[95m',
|
|
'CYAN': '\033[96m',
|
|
'WHITE': '\033[97m',
|
|
'END': '\033[0m'}
|
|
else:
|
|
colors = {}
|
|
|
|
def __init__(self, fmt: Optional[str] = ..., datefmt: Optional[str] = ..., style='%') -> None:
|
|
super().__init__(fmt, datefmt, style)
|
|
self.infmt = fmt
|
|
self.levelfmt = '[%(levelname)s] %(message)s'
|
|
|
|
def format(self, record):
|
|
if record.levelno == level.DEBUG.value:
|
|
color = 'BLUE'
|
|
elif record.levelno in [level.VERBOSE.value, level.HAL.value, level.HELPER.value]:
|
|
color = 'GREY'
|
|
elif record.levelno == level.GOOD.value:
|
|
color = 'GREEN'
|
|
elif record.levelno == level.IMPORTANT.value:
|
|
color = 'CYAN'
|
|
elif record.levelno == level.WARNING.value:
|
|
color = 'YELLOW'
|
|
elif record.levelno in [level.ERROR.value, level.BAD.value]:
|
|
color = 'RED'
|
|
elif record.levelno in [level.EXCEPTION.value, level.CRITICAL.value]:
|
|
color = 'PURPLE'
|
|
else:
|
|
color = 'WHITE'
|
|
if record.args:
|
|
if record.args[0] is not None and record.args[0] in self.colors:
|
|
color = record.args[0]
|
|
record.args = tuple()
|
|
if color in self.colors:
|
|
log_fmt = f'{self.colors[color]}{self.infmt}{self.colors["END"]}'
|
|
else:
|
|
log_fmt = self.infmt
|
|
formatter = logging.Formatter(log_fmt)
|
|
return formatter.format(record)
|
|
|
|
|
|
class Logger:
|
|
"""Class for logging to console, text file, XML."""
|
|
|
|
def __init__(self):
|
|
"""The Constructor."""
|
|
self.mytime = localtime()
|
|
self.logfile = None
|
|
self.ALWAYS_FLUSH = False
|
|
self.LOG_PATH = os.path.join(BASE_PATH, "logs")
|
|
self.logstream = logging.StreamHandler(sys.stdout)
|
|
self.chipsecLogger = logging.getLogger(LOGGER_NAME)
|
|
self.chipsecLogger.setLevel(logging.INFO)
|
|
if not self.chipsecLogger.handlers:
|
|
self.chipsecLogger.addHandler(self.logstream)
|
|
if not self.chipsecLogger.filters:
|
|
self.chipsecLogger.addFilter(chipsecFilter(LOGGER_NAME))
|
|
self.chipsecLogger.propagate = False
|
|
logging.addLevelName(level.VERBOSE.value, level.VERBOSE.name)
|
|
logging.addLevelName(level.HAL.value, level.HAL.name)
|
|
logging.addLevelName(level.HELPER.value, level.HELPER.name)
|
|
streamFormatter = chipsecStreamFormatter('%(additional)s%(message)s')
|
|
self.logstream.setFormatter(streamFormatter)
|
|
self.logFormatter = chipsecLogFormatter('%(additional)s%(message)s')
|
|
|
|
def log(self, text: str, level: level = level.INFO, color: Optional[str] = ...) -> None:
|
|
"""Sends plain text to logging."""
|
|
self.chipsecLogger.log(level.value, text, color)
|
|
|
|
def log_verbose(self, text: str) -> None: # Use log('text', level.VERBOSE)
|
|
"""Logs a Verbose message"""
|
|
self.log(text, level.VERBOSE)
|
|
|
|
def log_hal(self, text: str) -> None: # Use log("text", level.HAL)
|
|
"""Logs a hal message"""
|
|
self.log(text, level.HAL)
|
|
|
|
def log_helper(self, text: str) -> None:
|
|
"""Logs a helper message"""
|
|
self.log(text, level.HELPER)
|
|
|
|
def log_debug(self, text: str) -> None: # Use log("text", level.DEBUG)
|
|
"""Logs a debug message"""
|
|
self.log(text, level.DEBUG)
|
|
|
|
def set_log_level(self, verbose: bool, hal: bool, debug: bool, vverbose: bool) -> None:
|
|
self.VERBOSE = True if verbose or vverbose else self.VERBOSE
|
|
self.HAL = True if hal or vverbose else self.HAL
|
|
self.DEBUG = True if debug or vverbose else self.DEBUG
|
|
self.setlevel()
|
|
|
|
def setlevel(self) -> None:
|
|
if self.DEBUG:
|
|
self.chipsecLogger.setLevel(level.DEBUG.value)
|
|
elif self.HAL:
|
|
self.chipsecLogger.setLevel(level.HAL.value)
|
|
elif self.VERBOSE:
|
|
self.chipsecLogger.setLevel(level.VERBOSE.value)
|
|
else:
|
|
self.chipsecLogger.setLevel(level.INFO.value)
|
|
|
|
def create_logs_folder(self):
|
|
if not os.path.exists(self.LOG_PATH):
|
|
try:
|
|
os.mkdir(self.LOG_PATH)
|
|
except (FileExistsError, FileNotFoundError):
|
|
self.log('Unable to create logs folder')
|
|
return False
|
|
return True
|
|
|
|
def set_autolog_file(self, prefix: str="") -> None:
|
|
if self.create_logs_folder():
|
|
log_file_name = f'{prefix}{"-" if prefix else ""}{strftime("%Y%b%d-%H%M%S")}.log'
|
|
log_path = os.path.join(self.LOG_PATH, log_file_name)
|
|
file_handler = logging.FileHandler(log_path)
|
|
self.chipsecLogger.addHandler(file_handler)
|
|
file_handler.setFormatter(self.logFormatter)
|
|
else:
|
|
self.log('Unable to autolog')
|
|
|
|
def get_terminators(self) -> List[str]:
|
|
terms = []
|
|
for clhandler in self.chipsecLogger.handlers:
|
|
terms.append(clhandler.terminator)
|
|
return terms
|
|
|
|
def set_terminators(self, term: str) -> None:
|
|
for clhandler in self.chipsecLogger.handlers:
|
|
clhandler.terminator = term
|
|
|
|
def log_inline(self, msg: str) -> None:
|
|
"""Logs a plain message without a newline charater at the end"""
|
|
orig_term = self.get_terminators().pop()
|
|
self.set_terminators('')
|
|
self.log(msg)
|
|
self.set_terminators(orig_term)
|
|
|
|
def log_csv(self, file_name, test_cases):
|
|
fields = ['name', 'result', 'code', 'output']
|
|
if not file_name.endswith('.csv'):
|
|
file_name = file_name + '.csv'
|
|
with open(file_name, 'w') as csv_file:
|
|
results_csv = csv.DictWriter(csv_file, fieldnames=fields)
|
|
results_csv.writeheader()
|
|
for test_case in test_cases:
|
|
results_csv.writerow(test_case.get_fields())
|
|
|
|
def set_log_file(self, name: str, tologpath: bool = True):
|
|
"""Sets the log file for the output."""
|
|
# Close current log file if it's opened
|
|
self.disable()
|
|
|
|
# specifying empty string (name='') effectively disables logging to file
|
|
if name and self.create_logs_folder():
|
|
if tologpath:
|
|
self.LOG_FILE_NAME = os.path.join(self.LOG_PATH, name)
|
|
else:
|
|
self.LOG_FILE_NAME = name
|
|
# Open new log file and keep it opened
|
|
try:
|
|
# creates FileHandler for log file
|
|
self.logfile = logging.FileHandler(filename=self.LOG_FILE_NAME, mode='a')
|
|
except Exception:
|
|
self.log(f'WARNING: Could not open log file: {self.LOG_FILE_NAME}')
|
|
else:
|
|
self.chipsecLogger.addHandler(self.logfile)
|
|
self.logfile.setFormatter(self.logFormatter)
|
|
self.LOG_TO_FILE = True
|
|
self.chipsecLogger.removeHandler(self.logstream)
|
|
else:
|
|
try:
|
|
self.chipsecLogger.addHandler(self.logstream)
|
|
except Exception:
|
|
pass
|
|
|
|
def close(self) -> None:
|
|
"""Closes the log file."""
|
|
if self.logfile:
|
|
try:
|
|
self.chipsecLogger.removeHandler(self.logfile)
|
|
self.chipsecLogger.removeHandler(self.logstream)
|
|
self.logfile.close()
|
|
self.logstream.flush()
|
|
except Exception:
|
|
self.log('WARNING: Could not close log file')
|
|
finally:
|
|
self.logfile = None
|
|
|
|
def remove_chipsec_logger(self) -> None:
|
|
while self.chipsecLogger.filters:
|
|
self.chipsecLogger.removeFilter(self.chipsecLogger.filters[0])
|
|
while self.chipsecLogger.handlers:
|
|
self.chipsecLogger.removeHandler(self.chipsecLogger.handlers[0])
|
|
|
|
def disable(self) -> None:
|
|
"""Disables the logging to file and closes the file if any."""
|
|
self.LOG_TO_FILE = False
|
|
self.LOG_FILE_NAME = ''
|
|
self.close()
|
|
|
|
def flush(self) -> None:
|
|
sys.stdout.flush()
|
|
if self.LOG_TO_FILE and self.logfile is not None:
|
|
try:
|
|
self.chipsecLogger.removeHandler(self.logfile)
|
|
self.logfile.flush()
|
|
self.chipsecLogger.addHandler(self.logfile)
|
|
except Exception:
|
|
self.disable()
|
|
|
|
def set_always_flush(self, val) -> None:
|
|
self.ALWAYS_FLUSH = val
|
|
|
|
# -------------------------------------------------------
|
|
# These logger methods are deprecated and will be removed
|
|
# -------------------------------------------------------
|
|
|
|
def log_passed(self, text): # Use log("text", self.logger.GOOD)
|
|
"""Logs a passed message."""
|
|
text = f'PASSED: {text}'
|
|
self.log(text, level.GOOD)
|
|
|
|
def log_failed(self, text):
|
|
"""Logs a failed message."""
|
|
text = f'FAILED: {text}'
|
|
self.log(text, level.BAD)
|
|
|
|
def log_error(self, text): # Use log("text", level.ERROR)
|
|
"""Logs an Error message"""
|
|
self.log(text, level.ERROR)
|
|
|
|
def log_warning(self, text): # Use log("text", level.WARNING)
|
|
"""Logs an Warning message"""
|
|
self.log(text, level.WARNING)
|
|
|
|
def log_not_applicable(self, text):
|
|
"""Logs a NOT APPLICABLE message."""
|
|
text = f'NOT APPLICABLE: {text}'
|
|
self.log(text, level.INFO, "YELLOW")
|
|
|
|
def log_heading(self, text):
|
|
"""Logs a heading message."""
|
|
self.log(text, level.INFO, "BLUE")
|
|
|
|
def log_important(self, text): # Use log("text", level.IMPORTANT)
|
|
"""Logs an important message."""
|
|
self.log(text, level.IMPORTANT)
|
|
|
|
def log_bad(self, text): # Use log("text", level.BAD)
|
|
"""Logs a bad message, so it calls attention in the information displayed."""
|
|
self.log(text, level.BAD)
|
|
|
|
def log_good(self, text): # Use log("text", level.GOOD)
|
|
"""Logs a message, if colors available, displays in green."""
|
|
self.log(text, level.GOOD)
|
|
|
|
def log_unknown(self, text):
|
|
"""Logs a message with a question mark."""
|
|
text = f'[?] {text}'
|
|
self.log(text)
|
|
|
|
def log_information(self, text): # Use log("text")
|
|
"""Logs a message with information message"""
|
|
text = f'[#] INFORMATION: {text}'
|
|
self.log(text)
|
|
|
|
# -----------------------------
|
|
# End deprecated logger methods
|
|
# -----------------------------
|
|
|
|
def start_test(self, test_name: str) -> None:
|
|
"""Logs the start point of a Test"""
|
|
text = '[x][ =======================================================================\n'
|
|
text = f'{text}[x][ Module: {test_name}\n'
|
|
text = f'{text}[x][ ======================================================================='
|
|
self.log(text, level.INFO, 'BLUE')
|
|
|
|
VERBOSE: bool = False
|
|
UTIL_TRACE: bool = False
|
|
HAL: bool = False
|
|
DEBUG: bool = False
|
|
|
|
LOG_TO_STATUS_FILE: bool = False
|
|
LOG_STATUS_FILE_NAME: str = ''
|
|
LOG_TO_FILE: bool = False
|
|
LOG_FILE_NAME: str = ''
|
|
|
|
|
|
_logger = Logger()
|
|
|
|
|
|
def logger() -> Logger:
|
|
"""Returns a Logger instance."""
|
|
return _logger
|
|
|
|
|
|
def aligned_column_spacing(table_data: List[Tuple[str, Dict[str, str]]]) -> Tuple[int, ...]:
|
|
clean_data = clean_data_table(table_data)
|
|
all_column_widths = get_column_widths(clean_data)
|
|
required_widths = find_required_col_widths(all_column_widths)
|
|
return tuple(required_widths)
|
|
|
|
|
|
def clean_data_table(data_table: List[Tuple[str, Dict[str, str]]]) -> List[List[str]]:
|
|
clean_table = [extract_column_values(row) for row in data_table]
|
|
return clean_table
|
|
|
|
|
|
def extract_column_values(row_data: Tuple[str, Dict[str, str]]) -> List[str]:
|
|
clean_row = [row_data[0]]
|
|
additional_column_values = row_data[1].values()
|
|
[clean_row.append(value) for value in additional_column_values]
|
|
return clean_row
|
|
|
|
|
|
def get_column_widths(data: List[List[str]]) -> List[List[int]]:
|
|
col_widths = [[len(col) for col in row] for row in data]
|
|
return col_widths
|
|
|
|
|
|
def find_required_col_widths(col_data: List[List[int]], minimum_width=2) -> List[int]:
|
|
columns_per_row = len(col_data[0])
|
|
max_widths = ([(max(rows[i] for rows in col_data)) for i in range(columns_per_row)])
|
|
for i in range(len(max_widths)):
|
|
max_widths[i] = max_widths[i] if max_widths[i] > minimum_width else minimum_width
|
|
return max_widths
|
|
|
|
##################################################################################
|
|
# Hex dump functions
|
|
##################################################################################
|
|
|
|
|
|
def bytes2string(buffer, length=16):
|
|
"""Generate text string based on str with ASCII side panel"""
|
|
output = []
|
|
num_string = []
|
|
ascii_string = []
|
|
index = 1
|
|
for c in buffer:
|
|
num_string += [f'{ord(c):02X} ']
|
|
if not (c in string.printable) or (c in string.whitespace):
|
|
ascii_string += [' ']
|
|
else:
|
|
ascii_string += [f'{c}']
|
|
if (index % length) == 0:
|
|
num_string += ['| ']
|
|
num_string += ascii_string
|
|
output.append(''.join(num_string))
|
|
ascii_string = []
|
|
num_string = []
|
|
index += 1
|
|
if 0 != (len(buffer) % length):
|
|
num_string += [(length - len(buffer) % length) * 3 * ' ']
|
|
num_string += ['| ']
|
|
num_string += ascii_string
|
|
output.append(''.join(num_string))
|
|
return '\n'.join(output)
|
|
|
|
|
|
def dump_buffer(arr, length=8):
|
|
"""Dumps the buffer (str) with ASCII"""
|
|
return bytes2string(arr, length)
|
|
|
|
|
|
def print_buffer(arr, length=16):
|
|
"""Prints the buffer (str) with ASCII"""
|
|
prt_str = bytes2string(arr, length)
|
|
logger().log(prt_str)
|
|
|
|
|
|
def dump_buffer_bytes(arr, length=8):
|
|
"""Dumps the buffer (bytes, bytearray) with ASCII"""
|
|
output = []
|
|
num_string = []
|
|
ascii_string = []
|
|
index = 1
|
|
for c in arr:
|
|
num_string += [f'{c:02X} ']
|
|
if not (chr(c) in string.printable) or (chr(c) in string.whitespace):
|
|
ascii_string += [' ']
|
|
else:
|
|
ascii_string += [chr(c)]
|
|
if (index % length) == 0:
|
|
num_string += ['| ']
|
|
num_string += ascii_string
|
|
output.append(''.join(num_string))
|
|
ascii_string = []
|
|
num_string = []
|
|
index += 1
|
|
if 0 != (len(arr) % length):
|
|
num_string += [(length - len(arr) % length) * 3 * ' ']
|
|
num_string += ['| ']
|
|
num_string += ascii_string
|
|
output.append(''.join(num_string))
|
|
return '\n'.join(output)
|
|
|
|
|
|
def print_buffer_bytes(arr, length=16):
|
|
"""Prints the buffer (bytes, bytearray) with ASCII"""
|
|
prt_str = dump_buffer_bytes(arr, length)
|
|
logger().log(prt_str)
|
|
|
|
|
|
def pretty_print_hex_buffer(arr, length=16):
|
|
"""Prints the buffer (bytes, bytearray) in a grid"""
|
|
_str = [' _']
|
|
for n in range(length):
|
|
_str += [f'{n:02X}__']
|
|
for n in range(len(arr)):
|
|
if (n % length) == 0:
|
|
_str += [f'\n{n:02X} | ']
|
|
_str += [f'{arr[n]:02X} ']
|
|
logger().log(''.join(_str))
|
|
|
|
|
|
def dump_data(data, length=16):
|
|
"""Dumps the buffer with ASCII"""
|
|
if isinstance(data, str):
|
|
dump_buffer(data, length)
|
|
else:
|
|
dump_buffer_bytes(data, length)
|
|
|
|
|
|
def print_data(data, length=16):
|
|
"""Prints the buffer with ASCII"""
|
|
if isinstance(data, str):
|
|
print_buffer(data, length)
|
|
else:
|
|
print_buffer_bytes(data, length)
|