mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-12 20:57:39 +02:00
Change the requirement validation system to return all unsatisfied requirements, to provide more feedback.
This commit is contained in:
@@ -126,11 +126,10 @@ class CommandLine(object):
|
||||
# Clever magic figures out how to fulfill each requirement that might not be fulfilled
|
||||
automagic.run(automagics, ctx, plugin, "plugins", progress_callback = progress_callback)
|
||||
|
||||
print("CONFIG", ctx.config)
|
||||
|
||||
# Check all the requirements and/or go back to the automagic step
|
||||
if not plugin.validate(ctx, plugin_config_path):
|
||||
raise RuntimeError("Unable to validate the plugin configuration")
|
||||
unsatisfied = plugin.unsatisfied(ctx, plugin_config_path)
|
||||
if unsatisfied:
|
||||
raise RuntimeError("Unable to validate the plugin configuration: {}".format(unsatisfied))
|
||||
|
||||
print("\n\n")
|
||||
|
||||
|
||||
@@ -19,23 +19,23 @@ class ConstructionMagic(interfaces.automagic.AutomagicInterface):
|
||||
priority = 0
|
||||
|
||||
def __call__(self, context, config_path, requirement, progress_callback = None, optional = False):
|
||||
if not requirement.validate(context, config_path):
|
||||
result = []
|
||||
if requirement.unsatisfied(context, config_path):
|
||||
# Having called validate at the top level tells us both that we need to dig deeper
|
||||
# but also ensures that TranslationLayerRequirements have got the correct subrequirements if their class is populated
|
||||
|
||||
success = True
|
||||
subreq_config_path = interfaces.configuration.path_join(config_path, requirement.name)
|
||||
for subreq in requirement.requirements.values():
|
||||
self(context, subreq_config_path, subreq, optional or subreq.optional)
|
||||
valid = subreq.validate(context, subreq_config_path)
|
||||
invalid = subreq.unsatisfied(context, subreq_config_path)
|
||||
# We want to traverse optional paths, so don't check until we've tried to validate
|
||||
# We also don't want to emit a debug message when a parent is optional, hence the optional parameter
|
||||
if not valid and not (optional or subreq.optional):
|
||||
if invalid and not (optional or subreq.optional):
|
||||
vollog.log(constants.LOGLEVEL_V, "Failed on requirement: {}".format(subreq_config_path))
|
||||
success = False
|
||||
if not success:
|
||||
return False
|
||||
result.append(interfaces.configuration.path_join(subreq_config_path, subreq.name))
|
||||
if result:
|
||||
return result
|
||||
elif isinstance(requirement, interfaces.configuration.ConstructableRequirementInterface):
|
||||
# We know all the subrequirements are filled, so let's populate
|
||||
requirement.construct(context, config_path)
|
||||
return True
|
||||
return []
|
||||
|
||||
@@ -175,7 +175,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
results = []
|
||||
if isinstance(requirement, interfaces.configuration.SymbolRequirement):
|
||||
# TODO: check if this is a windows symbol requirement, otherwise ignore it
|
||||
if not requirement.validate(context, config_path):
|
||||
if requirement.unsatisfied(context, config_path):
|
||||
results.append((config_path, sub_config_path, requirement))
|
||||
else:
|
||||
for subreq in requirement.requirements.values():
|
||||
|
||||
@@ -39,12 +39,13 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
"""Runs the automagic over the configurable"""
|
||||
|
||||
# Quick exit if we're not needed
|
||||
if requirement.validate(context, config_path):
|
||||
if not requirement.unsatisfied(context, config_path):
|
||||
return
|
||||
|
||||
# Bow out quickly if the UI hasn't provided a single_location
|
||||
if not self.validate(self.context, self.config_path):
|
||||
return
|
||||
unsatisfied = self.unsatisfied(self.context, self.config_path)
|
||||
if unsatisfied:
|
||||
return unsatisfied
|
||||
location = self.config["single_location"]
|
||||
self._check_type(location, str)
|
||||
self._check_type(requirement, interfaces.configuration.RequirementInterface)
|
||||
@@ -111,11 +112,11 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
:rtype: (str, str)"""
|
||||
child_config_path = interfaces.configuration.path_join(config_path, requirement.name)
|
||||
if isinstance(requirement, interfaces.configuration.TranslationLayerRequirement):
|
||||
if not requirement.validate(context, config_path):
|
||||
if requirement.unsatisfied(context, config_path):
|
||||
original_setting = context.config.get(child_config_path, None)
|
||||
for layer_name in stacked_layers:
|
||||
context.config[child_config_path] = layer_name
|
||||
if requirement.validate(context, config_path):
|
||||
if not requirement.unsatisfied(context, config_path):
|
||||
return child_config_path, layer_name
|
||||
else:
|
||||
# Clean-up to restore the config
|
||||
|
||||
@@ -234,7 +234,7 @@ class WintelHelper(interfaces.automagic.AutomagicInterface):
|
||||
# Determine if a class has been chosen
|
||||
# Once an appropriate class has been chosen, attempt to determine the page_map_offset value
|
||||
if ("memory_layer" in requirement.requirements and
|
||||
requirement.requirements["memory_layer"].validate(context, sub_config_path)):
|
||||
not requirement.requirements["memory_layer"].unsatisfied(context, sub_config_path)):
|
||||
physical_layer = requirement.requirements["memory_layer"].config_value(context, sub_config_path)
|
||||
hits = context.memory[physical_layer].scan(context, PageMapScanner(useful), progress_callback)
|
||||
for test, dtb in hits:
|
||||
|
||||
@@ -24,8 +24,8 @@ class MultiRequirement(interfaces_configuration.RequirementInterface):
|
||||
Technically the Interface could handle this, but it's an interface, so this is a concrete implementation.
|
||||
"""
|
||||
|
||||
def validate(self, context, config_path):
|
||||
return self.validate_children(context, config_path)
|
||||
def unsatisfied(self, context, config_path):
|
||||
return self.unsatisfied_children(context, config_path)
|
||||
|
||||
|
||||
class BooleanRequirement(interfaces_configuration.InstanceRequirement):
|
||||
@@ -63,13 +63,13 @@ class ChoiceRequirement(interfaces_configuration.RequirementInterface):
|
||||
raise TypeError("ChoiceRequirement takes a list of strings as choices")
|
||||
self.choices = choices
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Validates the provided value to ensure it is one of the available choices"""
|
||||
value = self.config_value(context, config_path)
|
||||
if value not in self.choices:
|
||||
vollog.log(constants.LOGLEVEL_V, "ValueError - Value is not within the set of available choices")
|
||||
return False
|
||||
return True
|
||||
return [interfaces_configuration.path_join(config_path, self.name)]
|
||||
return []
|
||||
|
||||
|
||||
class ListRequirement(interfaces_configuration.RequirementInterface):
|
||||
@@ -99,14 +99,19 @@ class ListRequirement(interfaces_configuration.RequirementInterface):
|
||||
self.min_elements = min_elements
|
||||
self.max_elements = max_elements
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Check the types on each of the returned values and their number and then call the element type's check for each one"""
|
||||
value = self.config_value(context, config_path)
|
||||
self._check_type(value, list)
|
||||
if not (self.min_elements <= len(value) <= self.max_elements):
|
||||
vollog.log(constants.LOGLEVEL_V, "TypeError - List option provided more or less elements than allowed.")
|
||||
return False
|
||||
return [interfaces_configuration.path_join(config_path, self.name)]
|
||||
if not all([self._check_type(element, self.element_type) for element in value]):
|
||||
vollog.log(constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.")
|
||||
return False
|
||||
return all([self.element_type.validate(context, element) for element in value])
|
||||
return [interfaces_configuration.path_join(config_path, self.name)]
|
||||
result = []
|
||||
for element in value:
|
||||
subresult = self.element_type.unsatisfied(context, element)
|
||||
for subvalue in subresult:
|
||||
result.append(interfaces_configuration.path_join(config_path, subvalue))
|
||||
return result
|
||||
|
||||
@@ -263,17 +263,22 @@ class RequirementInterface(validity.ValidityRoutines, metaclass = ABCMeta):
|
||||
self._check_type(requirement, RequirementInterface)
|
||||
del self._requirements[requirement.name]
|
||||
|
||||
def validate_children(self, context, config_path):
|
||||
def unsatisfied_children(self, context, config_path):
|
||||
"""Method that will validate all child requirements"""
|
||||
return all([requirement.validate(context, path_join(config_path, self._name)) for requirement in
|
||||
self.requirements.values() if not requirement.optional])
|
||||
result = []
|
||||
for requirement in self.requirements.values():
|
||||
if not requirement.optional:
|
||||
subresult = requirement.unsatisfied(context, path_join(config_path, self._name))
|
||||
for value in subresult:
|
||||
result.append(path_join(config_path, value))
|
||||
return result
|
||||
|
||||
# Validation routines
|
||||
@abstractmethod
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Method to validate the value stored at config_path for the configuration object against a context
|
||||
|
||||
Returns False when an item is invalid
|
||||
Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid
|
||||
"""
|
||||
|
||||
|
||||
@@ -289,7 +294,7 @@ class InstanceRequirement(RequirementInterface):
|
||||
"""Always raises a TypeError as instance requirements cannot have children"""
|
||||
raise TypeError("Instance Requirements cannot have subrequirements")
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Validates the instance requirement based upon its `instance_type`."""
|
||||
value = self.config_value(context, config_path, None)
|
||||
if not isinstance(value, self.instance_type):
|
||||
@@ -297,8 +302,8 @@ class InstanceRequirement(RequirementInterface):
|
||||
"TypeError - {} requirements only accept {} type: {}".format(self.name,
|
||||
self.instance_type.__name__,
|
||||
value))
|
||||
return False
|
||||
return True
|
||||
return [path_join(config_path, self.name)]
|
||||
return []
|
||||
|
||||
|
||||
class ClassRequirement(RequirementInterface):
|
||||
@@ -313,7 +318,7 @@ class ClassRequirement(RequirementInterface):
|
||||
def cls(self):
|
||||
return self._cls
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Checks to see if a class can be recovered"""
|
||||
value = self.config_value(context, config_path, None)
|
||||
self._cls = None
|
||||
@@ -327,7 +332,9 @@ class ClassRequirement(RequirementInterface):
|
||||
else:
|
||||
if value in globals():
|
||||
self._cls = globals()[value]
|
||||
return self._cls is not None
|
||||
if self._cls is None:
|
||||
return [path_join(config_path, self.name)]
|
||||
return []
|
||||
|
||||
|
||||
class ConstructableRequirementInterface(RequirementInterface):
|
||||
@@ -349,13 +356,13 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
def construct(self, context, config_path):
|
||||
"""Method for constructing within the context any required elements from subrequirements"""
|
||||
|
||||
def _check_class(self, context, config_path):
|
||||
def _validate_class(self, context, config_path):
|
||||
"""Method to check if the class Requirement is valid and if so populate the other requirements
|
||||
(but no need to validate, since we're invalid already)
|
||||
"""
|
||||
class_req = self.requirements['class']
|
||||
subreq_config_path = path_join(config_path, self.name)
|
||||
if class_req.validate(context, subreq_config_path):
|
||||
if not class_req.unsatisfied(context, subreq_config_path):
|
||||
# We have a class, and since it's validated we can construct our requirements from it
|
||||
if issubclass(class_req.cls, ConfigurableInterface):
|
||||
# In case the class has changed, clear out the old requirements
|
||||
@@ -446,9 +453,24 @@ class ConfigurableInterface(validity.ValidityRoutines, metaclass = ABCMeta):
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def validate(cls, context, config_path):
|
||||
return all([requirement.validate(context, config_path) for requirement in cls.get_requirements() if
|
||||
not requirement.optional])
|
||||
def unsatisfied(cls, context, config_path):
|
||||
"""Returns a list of the names of all unsatisfied requirements
|
||||
|
||||
Since a satisfied set of requirements will return [], it can be used in tests as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
unmet = configurable.unsatisfied(context, config_path)
|
||||
if unmet:
|
||||
raise RuntimeError("Unsatisfied requirements: {}".format(unmet)
|
||||
"""
|
||||
result = []
|
||||
for requirement in cls.get_requirements():
|
||||
if not requirement.optional:
|
||||
subresult = requirement.unsatisfied(context, config_path)
|
||||
for value in subresult:
|
||||
result.append(path_join(config_path, value))
|
||||
return result
|
||||
|
||||
|
||||
class TranslationLayerRequirement(ConstructableRequirementInterface):
|
||||
@@ -468,28 +490,28 @@ class TranslationLayerRequirement(ConstructableRequirementInterface):
|
||||
# TODO: Add requirements: acceptable OSes from the address_space information
|
||||
# TODO: Add requirements: acceptable arches from the available layers
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Validate that the value is a valid layer name and that the layer adheres to the requirements"""
|
||||
value = self.config_value(context, config_path, None)
|
||||
if isinstance(value, str):
|
||||
if value not in context.memory:
|
||||
vollog.log(9, "IndexError - Layer not found in memory space: {}".format(value))
|
||||
return False
|
||||
return True
|
||||
return [path_join(config_path, self.name)]
|
||||
return []
|
||||
|
||||
if value is not None:
|
||||
vollog.log(constants.LOGLEVEL_V,
|
||||
"TypeError - Translation Layer Requirement only accepts string labels: {}".format(value))
|
||||
return False
|
||||
return [path_join(config_path, self.name)]
|
||||
|
||||
# TODO: check that the space in the context lives up to the requirements for arch/os etc
|
||||
|
||||
### NOTE: This validate method has side effects (the dependencies can change)!!!
|
||||
|
||||
self._check_class(context, config_path)
|
||||
self._validate_class(context, config_path)
|
||||
vollog.log(constants.LOGLEVEL_V,
|
||||
"IndexError - No configuration provided: {}".format(config_path + CONFIG_SEPARATOR + self.name))
|
||||
return False
|
||||
return [path_join(config_path, self.name)]
|
||||
|
||||
def construct(self, context, config_path):
|
||||
"""Constructs the appropriate layer and adds it based on the class parameter"""
|
||||
@@ -506,8 +528,8 @@ class TranslationLayerRequirement(ConstructableRequirementInterface):
|
||||
"config_path": config_path,
|
||||
"name": name}
|
||||
|
||||
if not all([subreq.validate(context, config_path) for subreq in self.requirements.values() if
|
||||
not subreq.optional]):
|
||||
if any([subreq.unsatisfied(context, config_path) for subreq in self.requirements.values() if
|
||||
not subreq.optional]):
|
||||
return False
|
||||
|
||||
obj = self._construct_class(context, config_path, args)
|
||||
@@ -520,19 +542,19 @@ class TranslationLayerRequirement(ConstructableRequirementInterface):
|
||||
class SymbolRequirement(ConstructableRequirementInterface):
|
||||
"""Class maintaining the limitations on what sort of symbol spaces are acceptable"""
|
||||
|
||||
def validate(self, context, config_path):
|
||||
def unsatisfied(self, context, config_path):
|
||||
"""Validate that the value is a valid within the symbol space of the provided context"""
|
||||
value = self.config_value(context, config_path, None)
|
||||
if not isinstance(value, str):
|
||||
vollog.log(constants.LOGLEVEL_V,
|
||||
"TypeError - SymbolRequirement only accepts string labels: {}".format(value))
|
||||
return False
|
||||
return [path_join(config_path, self.name)]
|
||||
if value not in context.symbol_space:
|
||||
# This is an expected situation, so return False rather than raise
|
||||
vollog.log(constants.LOGLEVEL_V,
|
||||
"IndexError - Value not present in the symbol space: {}".format(value or ""))
|
||||
return False
|
||||
return True
|
||||
return [path_join(config_path, self.name)]
|
||||
return []
|
||||
|
||||
def construct(self, context, config_path):
|
||||
"""Constructs the symbol space within the context based on the subrequirements"""
|
||||
@@ -546,8 +568,8 @@ class SymbolRequirement(ConstructableRequirementInterface):
|
||||
"config_path": config_path,
|
||||
"name": name}
|
||||
|
||||
if not all([subreq.validate(context, config_path) for subreq in self.requirements.values() if
|
||||
not subreq.optional]):
|
||||
if any([subreq.unsatisfied(context, config_path) for subreq in self.requirements.values() if
|
||||
not subreq.optional]):
|
||||
return False
|
||||
|
||||
# Fill out the parameter for class creation
|
||||
|
||||
@@ -38,7 +38,7 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, validity.V
|
||||
super().__init__(context, config_path)
|
||||
# Plugins self validate on construction, it makes it more difficult to work with them, but then
|
||||
# the validation doesn't need to be repeated over and over again by externals
|
||||
if not self.validate(context, config_path):
|
||||
if self.unsatisfied(context, config_path):
|
||||
vollog.warning("Plugin failed validation")
|
||||
raise exceptions.PluginRequirementException("The plugin configuration failed to validate")
|
||||
|
||||
@@ -47,13 +47,6 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, validity.V
|
||||
"""Returns a list of Requirement objects for this plugin"""
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
def validate(cls, context, config_path):
|
||||
"""Ensures that the plugin's requirements have been met appropriately"""
|
||||
result_set = [(config_path + "." + requirement.name, requirement.validate(context, config_path)) for requirement
|
||||
in cls.get_requirements() if not requirement.optional]
|
||||
return all([r for _, r in result_set])
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
"""Executes the functionality of the code
|
||||
|
||||
Reference in New Issue
Block a user