Start building a dependency resolver for translation layers.

This commit is contained in:
Mike Auty
2015-12-29 22:13:10 +00:00
parent f7cc2ef00e
commit cf66d0b447
6 changed files with 56 additions and 135 deletions
+11
View File
@@ -1,4 +1,5 @@
"""Volatility 3 framework"""
import inspect
# ##
#
@@ -36,4 +37,14 @@ def require_version(*args):
".".join([str(x) for x in args[0:2]]))
def class_subclasses(cls):
"""Returns all the (recursive) subclasses of a given class"""
if not inspect.isclass(cls):
raise TypeError(repr(cls) + " is not a class.")
for clazz in cls.__subclasses__():
yield clazz
for return_value in class_subclasses(clazz):
yield return_value
from volatility.framework import interfaces, symbols, layers, contexts, configuration
+7 -49
View File
@@ -4,15 +4,13 @@ Created on 7 May 2013
@author: mike
"""
from volatility.framework.interfaces.configuration import ConfigurationSchemaNode, Configurable
from volatility.framework.interfaces.configuration import ConfigurationSchemaNode
class InstanceRequirement(ConfigurationSchemaNode):
instance_type = bool
def validate(self, value, _context, valid_children = None):
# Child_results can be ignored because no instance class should have children
# We don't ban child_results in case someone wants to extend this in some meaningful way
def validate(self, value, _context):
if not isinstance(value, self.instance_type):
raise TypeError(self.name + " input only accepts " + self.instance_type.__name__ + " type")
@@ -26,7 +24,7 @@ class StringRequirement(InstanceRequirement):
instance_type = str
class TranslationLayerRequirement(ConfigurationSchemaNode, Configurable):
class TranslationLayerRequirement(ConfigurationSchemaNode):
"""Class maintaining the limitations on what sort of address spaces are acceptable"""
def __init__(self, name, description = None, default = None,
@@ -40,19 +38,12 @@ class TranslationLayerRequirement(ConfigurationSchemaNode, Configurable):
:return:
"""
ConfigurationSchemaNode.__init__(name, description, default, optional)
Configurable.__init__(self)
self._layer_name = layer_name
@classmethod
def get_schema(cls):
# Runs through each of the available translation layers, determining what they're capable of,
# and adding their requirements (tagged with their class) to a Disjunction config schema node
pass
# TODO: Add requirements: acceptable OSes from the address_space information
# TODO: Add requirements: acceptable arches from the available layers
def validate(self, value, context, valid_children = None):
def validate(self, value, context):
"""Validate that the value is a valid layer name and that the layer adheres to the requirements"""
if not isinstance(value, str):
raise TypeError("TranslationLayerRequirements only accepts string labels")
@@ -61,8 +52,7 @@ class TranslationLayerRequirement(ConfigurationSchemaNode, Configurable):
class ChoiceRequirement(ConfigurationSchemaNode):
"""Allows one from a choice of strings
"""
"""Allows one from a choice of strings"""
def __init__(self, choices, *args, **kwargs):
ConfigurationSchemaNode.__init__(self, *args, **kwargs)
@@ -70,7 +60,7 @@ class ChoiceRequirement(ConfigurationSchemaNode):
raise TypeError("ChoiceRequirement takes a list of strings as choices")
self._choices = choices
def validate(self, value, context, valid_children = None):
def validate(self, value, context):
"""Validates the provided value to ensure it is one of the available choices"""
if value not in self._choices:
raise ValueError("Value is not within the set of available choices")
@@ -85,7 +75,7 @@ class ListRequirement(ConfigurationSchemaNode):
self.min_elements = min_elements
self.max_elements = max_elements
def validate(self, value, context, valid_children = None):
def validate(self, value, context):
"""Check the types on each of the returned values and then call the element type's check for each one"""
self._type_check(value, list)
if not all([self._type_check(element, self.element_type) for element in value]):
@@ -94,35 +84,3 @@ class ListRequirement(ConfigurationSchemaNode):
raise TypeError("List option provided more or less elements than allowed.")
for element in value:
self.element_type.validate(element, context)
class DisjunctionRequirement(ConfigurationSchemaNode):
"""Class allowing any of multiple requirements"""
def __init__(self, requirements, *args, **kwargs):
# TODO: Type check requirements to ensure it's a dictionary of requirements
ConfigurationSchemaNode.__init__(self, *args, **kwargs)
for requirement in requirements:
self.add_item(requirement)
def validate(self, value, context, valid_children = None):
if valid_children is None:
raise ValueError("A Disjunction requirement cannot exist without children")
if not any(valid_children):
raise ValueError("No valid children to support the Disjunction Requirement")
class ConjunctionRequirement(ConfigurationSchemaNode):
"""Class requiring all of multiple requirements"""
def __init__(self, requirements, *args, **kwargs):
# TODO: Type check requirements to ensure it's a dictionary of requirements
ConfigurationSchemaNode.__init__(self, *args, **kwargs)
for requirement in requirements:
self.add_item(requirement)
def validate(self, value, context, valid_children = None):
if valid_children is None:
raise ValueError("A Conjunction requirement cannot exist without children")
if not all(valid_children):
raise ValueError("An invalid child prevents the Conjunction Requirement")
@@ -0,0 +1,20 @@
import volatility.framework as framework
import volatility.framework.interfaces as interfaces
import volatility.framework.validity as validity
class TranslationLayerDependencyResolver(validity.ValidityRoutines):
def __init__(self):
# Maintain a cache of translation layers
self.layer_cache = []
for layer_class in framework.class_subclasses(interfaces.layers.DataLayerInterface):
# TODO: Improve this hard coded list with a way for layers to say they're abstract or not
if layer_class.__name__.endswith("Interface"):
self.layer_cache.append(layer_class)
def resolve_dependencies(self, configurable):
"""Takes a configurable and produces a priority ordered tree of possible solutions to satisfy the various requirements"""
self._type_check(configurable, interfaces.configuration.Configurable)
for requirement in configurable.get_schemas():
pass
@@ -9,22 +9,18 @@ SCHEMA_NAME_DIVIDER = "."
# Design requirements:
# Plugins can be queried for their requirements without instantiating them
# The context can record validated data
# Configurables can have multiple identical (but differently named) requirement trees (based off the same schema)
# Instantiated plugins can pre-populate the context with (validated) da (in order to force layer names)
# The context can record config data
# Translation layer requirements can specify a layer name (or generate one if not specified)
# Still not decided whether the layer holds the name chosen (and this can be pre-populated) or if it's specified
# as part of the requirement, which then returns true/false if it could be fulfilled
# It's specified as part of the requirement, which then validates true/false if it could be fulfilled
# Still need to link schema in to config values
# Need to allow config values to be recovered by complete sub-path
# Configurables need to hold a hook into the config tree to get their own sub components
# Non-instantiated plugin
# -> Requirement schema nodes (instantiated)
# (Translation layers contain all information required)
# Instantiate plugin
# -> Requirement schema nodes (instantiated) wrapped in value reader/writer/valdiator
# Dependency solver
# Attempts to fill all dependencies by traversing the various available classes to find a solution
def schema_name_join(pathlist):
@@ -49,7 +45,6 @@ class ConfigurationSchemaNode(validity.ValidityRoutines):
self._description = description or ""
self._default = default
self._optional = optional
self._children = {}
@property
def name(self):
@@ -71,80 +66,16 @@ class ConfigurationSchemaNode(validity.ValidityRoutines):
"""Whether the option is required for or not"""
return self._optional
# Child operations
def add_item(self, item):
"""Add a child to the configuration schema"""
self._type_check(item, ConfigurationSchemaNode)
self._children[item.name] = item
def __iter__(self):
"""Iterate through all the child configuration schemas"""
return iter(self._children)
def __getitem__(self, item):
"""Returns a single child configuration schema by name"""
self._type_check(item, str)
item_split = item.split(SCHEMA_NAME_DIVIDER)
if len(item_split) > 1:
return self._children[item_split[0]][schema_name_join(item_split[1:])]
# Let namespace produce the index error if necessary
return self._children[item_split[0]]
def __contains__(self, item):
"""Determine membership"""
item_split = item.split(SCHEMA_NAME_DIVIDER)
if len(item_split) > 1:
if item_split[0] in self._children:
return schema_name_join(item_split[1:]) in self._children[item_split[0]]
else:
return False
return item in self._children
def __len__(self):
return len(self._children)
# Validation routines
@abstractmethod
def validate(self, value, context, valid_children = None):
def validate(self, value, context):
"""Method to validate the value stored at config_location for the configuration object against a context
This must validate its own children (so that conjunction/disjunction can work)
Raises a ValueError based on whether the item is valid or not
"""
class ConfigurationItem(validity.ValidityRoutines):
"""Class for wrapping ConfigurationSchemaNodes to create specific configuration items"""
def __init__(self, schema, config_location_prefix = None):
""""""
validity.ValidityRoutines.__init__(self)
self._schema = self._type_check(schema, ConfigurationSchemaNode)
if config_location_prefix is None:
config_location_prefix = "core"
self._config_location = schema_name_join(
schema_name_split(self._type_check(config_location_prefix, str)) +
[self._schema.name])
self._children = []
for child in self._schema:
self._children.append(
ConfigurationItem(child, self._config_location))
def valid(self, context):
child_results = []
for child in self._children:
child_results.append(child.valid(context))
try:
self._schema.validate(context.config[self._config_location], context, child_results)
return True
except ValueError:
return False
class Configurable(metaclass = ABCMeta):
"""Class to allow objects to have requirements and populate the context config tree"""
@@ -152,3 +83,9 @@ class Configurable(metaclass = ABCMeta):
@abstractmethod
def get_schema(cls):
"""Returns a list of configuration schema nodes for this object"""
return []
def create_configuration(self, location, context):
"""Pins the configuration schemas to a location within the context's config storage"""
for requirement in self.get_schema():
pass
+7 -1
View File
@@ -6,11 +6,12 @@ Created on 4 May 2013
from volatility.framework import validity, exceptions
# We can't just import interfaces because we'd have a cycle going
from volatility.framework.interfaces import configuration
from volatility.framework.interfaces import context as context_module
from abc import ABCMeta, abstractmethod, abstractproperty
class DataLayerInterface(validity.ValidityRoutines, metaclass = ABCMeta):
class DataLayerInterface(validity.ValidityRoutines, configuration.Configurable, metaclass = ABCMeta):
"""A Layer that directly holds data (and does not translate it"""
def __init__(self, context, name):
@@ -60,6 +61,11 @@ class DataLayerInterface(validity.ValidityRoutines, metaclass = ABCMeta):
(exceptions will be thrown using a DataLayer after destruction)"""
pass
@abstractmethod
@classmethod
def get_schema(cls):
"""Returns a list of requirements for this type of layer"""
class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
@abstractmethod
-11
View File
@@ -1,7 +1,6 @@
# TODO: Code to import all the py/pyc files available (but not both).
# TODO: Code to return a none-instantiated list of plugin classes.
import inspect
import logging
import os
import sys
@@ -11,16 +10,6 @@ import volatility.plugins as plugins
logger = logging.getLogger(__name__)
def class_subclasses(cls):
"""Returns all the (recursive) subclasses of a given class"""
if not inspect.isclass(cls):
raise TypeError(repr(cls) + " is not a class.")
for clazz in cls.__subclasses__():
yield clazz
for return_value in class_subclasses(clazz):
yield return_value
def import_plugins():
"""Imports all plugins present under plugins path"""
if not isinstance(plugins.__path__, list):