From 4361c3857393b7f7311e9b600d1ea5d4966cdb93 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Sep 2024 14:55:04 +0100 Subject: [PATCH 1/5] Core: Verify plugin requirements of plugins --- .../framework/configuration/requirements.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 1c0622574..931995bb8 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -585,6 +585,24 @@ class PluginRequirement(VersionRequirement): version=version, ) + def unsatisfied( + self, context: interfaces.context.ContextInterface, config_path: str + ) -> Dict[str, interfaces.configuration.RequirementInterface]: + result = super().unsatisfied(context, config_path) + if not result: + component: Type[interfaces.plugins.PluginInterface] = self._component + for requirement in component.get_requirements(): + if isinstance(requirement, PluginRequirement): + result.update( + requirement.unsatisfied( + context, + interfaces.configuration.path_join(config_path, self.name), + ) + ) + if result: + result[config_path] = self + return result + class ModuleRequirement( interfaces.configuration.ConstructableRequirementInterface, From f3085b6c59b05190320722feef58f276357d2155 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Sep 2024 16:06:23 +0100 Subject: [PATCH 2/5] Core: Move the pluginrequirement check to generic versionrequirement --- .../framework/configuration/requirements.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 931995bb8..f95dac307 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -527,12 +527,14 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): def __init__( self, name: str, - description: str = None, + description: Optional[str] = None, default: bool = False, optional: bool = False, component: Type[interfaces.configuration.VersionableInterface] = None, version: Optional[Tuple[int, ...]] = None, ) -> None: + if description is None: + description = f"Version {".".join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet" super().__init__( name=name, description=description, default=default, optional=optional ) @@ -550,9 +552,29 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): config_path = interfaces.configuration.path_join(config_path, self.name) if not self.matches_required(self._version, self._component.version): return {config_path: self} + + # Check for child requirements + if issubclass(self._component, interfaces.configuration.ConfigurableInterface): + result = {} + for requirement in self._component.get_requirements(): + if not requirement.optional and isinstance( + requirement, VersionRequirement + ): + result.update( + requirement.unsatisfied( + context, + config_path, + ) + ) + + if result: + result.update({config_path: self}) + return result + context.config[interfaces.configuration.path_join(config_path, self.name)] = ( True ) + return {} @classmethod @@ -585,24 +607,6 @@ class PluginRequirement(VersionRequirement): version=version, ) - def unsatisfied( - self, context: interfaces.context.ContextInterface, config_path: str - ) -> Dict[str, interfaces.configuration.RequirementInterface]: - result = super().unsatisfied(context, config_path) - if not result: - component: Type[interfaces.plugins.PluginInterface] = self._component - for requirement in component.get_requirements(): - if isinstance(requirement, PluginRequirement): - result.update( - requirement.unsatisfied( - context, - interfaces.configuration.path_join(config_path, self.name), - ) - ) - if result: - result[config_path] = self - return result - class ModuleRequirement( interfaces.configuration.ConstructableRequirementInterface, From ace590e8f0669797b5770773eeadcbcb27752a78 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Sep 2024 16:08:48 +0100 Subject: [PATCH 3/5] Core: Fix up f-string containing a string --- volatility3/framework/configuration/requirements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index f95dac307..3a862a132 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -534,7 +534,7 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): version: Optional[Tuple[int, ...]] = None, ) -> None: if description is None: - description = f"Version {".".join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet" + description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet" super().__init__( name=name, description=description, default=default, optional=optional ) From bf000ff0a0bafc0197ee1dd074f1df450362d02f Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Sep 2024 21:42:59 +0100 Subject: [PATCH 4/5] Core: Add recursion protection to VersionRequirement check --- .../framework/configuration/requirements.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 3a862a132..49ca49b59 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -546,13 +546,26 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): self._version = version def unsatisfied( - self, context: interfaces.context.ContextInterface, config_path: str + self, + context: interfaces.context.ContextInterface, + config_path: str, + accumulator: Optional[ + List[interfaces.configuration.VersionableInterface] + ] = None, ) -> Dict[str, interfaces.configuration.RequirementInterface]: # Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type config_path = interfaces.configuration.path_join(config_path, self.name) if not self.matches_required(self._version, self._component.version): return {config_path: self} + if accumulator is None: + accumulator = set([self._component]) + else: + if self._component in accumulator: + return {config_path: self} + else: + accumulator.add(self._component) + # Check for child requirements if issubclass(self._component, interfaces.configuration.ConfigurableInterface): result = {} @@ -562,8 +575,7 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): ): result.update( requirement.unsatisfied( - context, - config_path, + context, config_path, accumulator.copy() ) ) From 9152f33181fd347366468d00c47a2c22ae71cfc2 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 8 Sep 2024 21:58:09 +0100 Subject: [PATCH 5/5] Core: Allow circular dependencies as long as they are all met --- volatility3/framework/configuration/requirements.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 49ca49b59..dcb7505ea 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -558,16 +558,20 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): if not self.matches_required(self._version, self._component.version): return {config_path: self} + recurse = True if accumulator is None: accumulator = set([self._component]) else: if self._component in accumulator: - return {config_path: self} + recurse = False else: accumulator.add(self._component) # Check for child requirements - if issubclass(self._component, interfaces.configuration.ConfigurableInterface): + if ( + issubclass(self._component, interfaces.configuration.ConfigurableInterface) + and recurse + ): result = {} for requirement in self._component.get_requirements(): if not requirement.optional and isinstance(