depthcharge.checker
This subpackage provides functionality for building U-Boot “security checker” tooling.
- class depthcharge.checker.ConfigChecker(uboot_version: str, enable_builtins=True)
Create a new configuration checker instance.
The uboot_version parameter should list the U-Boot version being inspected. This will be used to inform version-specific checks, such as whether a particular feature has CVEs associated with it.
Examples of acceptable version strings:
2020.10
(A release version)2021.01-rc1
(A release candidate)v2020.10
(The `v` prefix is ignored)1.3.0
(U-Boot version prior to use of `YYYY.MM[-rcN]` convention)
With enable_builtins set to its default value of
True
, the configuration handlers built into Depthcharge will be used.Set this to
False
if you want the configuration checker to only report risks identified by handlers registered viaregister_handler()
.- register_handler(config_key: str, match, security_risk, user_data=None)
This method allows
UBootConfigChecker
to be extended in order to report additional configuration-related security risks, beyond those built in to Depthcharge. For instance, one could leverage this to maintain their own out-of-tree collections ofSecurityRisk
definitions and corresponding handlers specific to certain silicon or product vendors’ U-Boot forks.Given a configuration item named
config_key
(e.g.'CONFIG_SOME_FEATURE'
), thematch
parameter shall determine whether the specifiedsecurity_risk
– an instance of theSecurityRisk
class – shall be reported.The
match
parameter may be one of the following:A boolean (
True
orFalse
) value. A value ofTrue
corresponds to a configuration value being enabled andFalse
if it is disabled. This should only be used for options that have a boolean state.A string that is compared with the configuration value in question. The comparison is case-sensitive.
A compiled regular expression. This can be used to match a value whose format is known in advance, but not easily matched with a string literal.
A callable function or method that takes two parameters: the configuration value and the
user_data
parameter supplied to this method. The registered handler must returnTrue
if thesecurity_risk
should be reported andFalse
otherwise. This provides the most flexibility, with a bit more complexity.
For
SecurityRisk
objects that contain affected version ranges, the provided match criteria will only be evaluated if the U-Boot version provided to theConfigChecker
constructor falls within the relevant range.Handlers may be registered either before or after a call to
load()
. The latter allows allows function-based handlers to have access to configuration information (by way ofuser_data
) collected when the checker parses the file.More than one handler can be registered for a given configuration item. All of the handlers will be executed, allowing a single configuration item to have multiple risks associated with it.
- load(filename: str) dict
Subclasses implement this function to load the relevant file, parse it, and return a dictionary describing the U-Boot configuration settings.
The keys in this dictionary are the CONFIG_* items ingested from the loaded file. The value associated with each key is a tuple:
(cfg_val, source)
.The cfg_val is
True
orFalse
for boolean configuration items. Otherwise, it is a verbatim string from the source file, containing any quotation marks.The source item is a string describing where cfg_val was obtained from (e.g. line number).
- class depthcharge.checker.UBootConfigChecker(uboot_version: str, enable_builtins=True)
Inspect U-Boot build configurations present in .config files produced by more modern, Kconfig-driven U-Boot builds. (i.e. those produced from
make <platform>_defconfig
)- load(filename: str) dict
Load and parse the specified U-Boot build configuration file and return a dictionary in the format defined by
ConfigChecker.load()
.The provided file should be a .config file produced by running
make <platform>_defconfig
within the U-Boot codebase - not just the platform’s defconfig file, doesn’t include all the configuration items inherited by default settings.Calling
load()
multiple times will aggregate the configurations present across all loaded files. When re-defined configuration items are encountered their values are ignored and a warning is printed.
- class depthcharge.checker.UBootHeaderChecker(uboot_version: str, include_paths: list, config_defs: Optional[dict] = None, cpp: Optional[str] = None, dummy_headers: Optional[list] = None, enable_builtins=True)
Inspect a platform’s U-Boot build configuration header.
This is applicable for older U-Boot version. See
UBootConfigChecker
when working with version that have been more fully migrated to Kconfig.If you find yourself with a version of U-Boot that has some
CONFIG_*
items defined in Kconfig files, and other in headers – worry not!You should first use a
UBootConfigChecker
. Pass the configuration dictionary returned byUBootConfigChecker.load()
to this class’s constructor via the config_defs keyword argument. These definitions may provide additional definitions that will be used when preprocessing the target platform header file.From there, you can invoke each checker’s
audit()
method and then merge their reports. (SeeReport.merge()
.)The include_paths parameter should contain a list of paths to search for header files when
#include
directives are encountered.Should you encounter problematic header files that prevent preprocessing from completed, but are not of interest to you, you can specify their names in the dummy_headers list. This class will automatically create empty files in a temporary directory, placed at higher precedence in the include search path. You may supply eithr single file names (
regs.h
) or relative paths (asm/arch/imx-regs.h
) – whichever is needed to fulfil a failing#include
. (Note: Paths with ``..`` are not currently supported.)By default, this class will invoke the cpp program located in one’s
PATH
. If you would like to use a different preprocessor program, you may override this using the cpp keyword argument. Depthcharge assumes that it will take the same arguments, however.- load(filename: str) dict
Load and parse the specified U-Boot platform configuration header file and return a dictionary in the format defined by
ConfigChecker.load()
.Calling
load()
multiple times with different files will aggregate the configurations present across all loaded files.
- class depthcharge.checker.Report
A set of of
SecurityRisk
objects that can be exported to multiple formats. This class can be used to aggregate results from custom “checkers” and tools.The number of results currently within the
Report
can be determined vialen(report_instance)
.- add(risk) bool
Record a new security risk. This argument should be either a
SecurityRisk
object or a dictionary that can be passed to theSecurityRisk
constructor.Each added
SecurityRisk
must have a unique identifier in order to be treated as a unique result. If aSecurityRisk
with a given identifier is already in the report, no change will be made. The previously added item will remain in place.This methods returns
True
if theSecurityRisk
was newly added.False
is returned when risk is a duplicate of an already-recorded item.
- security_risks(high_impact_first=True)
This is a generator that provides each of the
SecurityRisk
objects contained in theReport
.The default value of
high_impact_first=True
ensures that theSecurityRisk
objects are ordered highest-to-lowest impact.Bear in mind, however, that “risk” and “impact” are just generalizations – ultimately the target device’s threat model and surrounding context actually define these. (Use your brain, don’t just rely upon automated tools like this one.)
It is also possible to iterate over the report in a highest-impact-first order as follows:
- merge(*others)
Merge
SecurityRisk
items from one or moreReport
instances (others) into this report.Items having the same
SecurityRisk.identifer
value will not be duplicated. Only new items fromother
will be added. (i.e. it’s a set union)The |= operator has the same effect.
report |= other
- save_csv(filename: str, write_header=True, columns=None, **kwargs)
Write checker results to a CSV file with the name specified by filename.
If filename is
None
or'-'
, the CSV is written to stdout.A header row will be written unless write_header is set to
False
.The columns argument controls the order and presence of columns in the produced CSV. This should be provided as a tuple of strings. Below are the supported names. Those followed by an asterisk are note included by default when
columns=None
Identifier
Impact
Source
Summary
Description (*)
Recommendation (*)
Any additional keyword arguments are passed to
csv.writer()
.
- save_html(filename: str, write_header=True, columns=None, **kwargs)
Write checker results to a simple HTML file with the name specified by filename.
If filename is
None
or'-'
, the output is written to stdout.A header row will be written unless write_header is set to
False
.The columns argument controls the order and presence of columns in the produced HTML table. This should be provided as a tuple of strings. Below are the supported names. Those followed by an asterisk are note included by default when
columns=None
Identifier
Impact
Source
Summary
Description (*)
Recommendation (*)
Additional keyword arguments are ignored, but not used at this time. This API reserves keyword arguments named with a single underscore prefix (e.g.
_foo='bar'
) for internal use.
- save_markdown(filename, **kwargs)
Writer checker results to a Markdown file with the name specified by filename.
Additional keyword arguments are ignored, but not used at this time. This API reserves keyword arguments named with a single underscore prefix (e.g.
_foo='bar'
) for internal use.
- class depthcharge.checker.SecurityRisk(identifier, impact, source, summary, description, recommendation, affected_versions: Optional[tuple] = None)
Encapsulates information about a potential security risk.
- Important
Identifiers must be unique, as these are used to differentiate one
SecurityRisk
from another when coalescing results.
- property identifier: str
A short string that uniquely identifies a specific security risk.
- property summary: str
A concise summary of the security risk, suitable for use as a title heading.
- property impact
The potential impact of a security vulnerability, encoded as a
SecurityImpact
.
- property impact_str: str
The potential impact of a security vulnerability, represented as a string of one or more abbreviations.
- property source: str
A string denoting the file and/or location that was used to deduce the presence of a potential security risk.
- property description: str
A string that provides a more detailed explanation of the potential security risk - what it is, why it matters, and how it could impact stakeholders and users.
- property recommendation: str
A string containing high-level and generic guidance for eliminating or mitigating a reported security risk.
This is almost never appropriate as one-size-fits-all guidance; all products and threat models vary in their own unique ways. In many cases, the recommendation is “just turn this off”, which obviously doesn’t account for how any relevant requirements (e.g. for failure analysis of returned units) can be satisfied in a more secure manner.
Instead, consider is this a starting point for further discussion about remediation efforts.
- to_dict() dict
Return the
SecurityRisk
object as a dictionary with the following keys, each associated with a value of the listed property.'identifier'
-identifier()
'summary'
-summary()
'impact'
-impact()
'source'
-source()
'description'
-description()
'recommendation'
-recommendation()
- classmethod from_dict(d: dict)
Create a
SecurityRisk
from a dictionary.Refer to
to_dict()
for the supported key-value pairs.
- applicable_to_version(version: str) bool
Returns
True
if this security risk may be applicable to the specified U-Boot version, andFalse
otherwise.
- class depthcharge.checker.SecurityImpact(value)
Enumerated flags (
enum.IntFlag
) that describe the impact of aSecurityRisk
.The integer value of each flag roughly increases with impact. Of course, context matters and ultimately the context surrounding a device and its threat model ultimately define this. Think of this as simply an approximation that can be used to sort lists in a reasonable manner.
- NONE = 0
No immediate security risk; informational note.
- UNKNOWN = 1
The security impact is unclear or otherwise not yet known.
- DOS = 2
The system is rendered temporarily inoperable and must be power cycled to recover.
- PDOS = 4
The system is rendered permanently inoperable and cannot be recovered by users. In more severe cases, the system may not be recoverable even with vendor support.
- INFO_LEAK = 64
Behavior discloses information that may aid in reverse engineering efforts or exploiting security vulnerabilities on the system.
- ATTACK_SURFACE = 128
Feature or behavior increases bootloader’s attack surface beyond that which is necessitated by its functional requirements.
- RD_MEM = 256
Operation can be abused to read memory at an attacker-controlled address.
- LIMITED_WR_MEM = 512
Operation can be abused to perform a limited, but attacker-controlled memory write. The address and/or value written are constrained, or the write is otherwise unlikely to result in arbitrary code execution due to other mitigating factors.
- WEAK_AUTH = 1024
Authentication mechanism or underlying algorithm contains known weaknesses or its use is otherwise inconsistent with modern security standards and best practices.
- VERIFICATION_BYPASS = 65536
One or more security-critical verifications (or operations) can be bypassed.
- EXEC = 8388608
Operation can be abused to directly execute arbitrary code, provided that there exists a means to load code.
- WR_MEM = 16777216
Operation can be abused to read write memory at an attacker-controlled address, potentially leading to execution of attacker-supplied code.
- describe(html=False) str
Return a string describing the aggregate security impact.