From 34a7dfc72fb3dced313e9a52b73b96dff31b778a Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Thu, 23 Jan 2025 10:42:52 +0100 Subject: [PATCH 01/19] split linux modules utilities --- .../symbols/linux/utilities/modules.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 volatility3/framework/symbols/linux/utilities/modules.py diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py new file mode 100644 index 000000000..ac9b2afaf --- /dev/null +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -0,0 +1,68 @@ +from typing import Iterator, List, Tuple + +from volatility3 import framework +from volatility3.framework import constants, interfaces +from volatility3.framework.objects import utility + + +class Modules(interfaces.configuration.VersionableInterface): + """Kernel modules related utilities.""" + + _version = (1, 0, 0) + _required_framework_version = (2, 0, 0) + + framework.require_interface_version(*_required_framework_version) + + @staticmethod + def mask_mods_list( + context: interfaces.context.ContextInterface, + layer_name: str, + mods: Iterator[interfaces.objects.ObjectInterface], + ) -> List[Tuple[str, int, int]]: + """ + A helper function to mask the starting and end address of kernel modules + """ + mask = context.layers[layer_name].address_mask + + return [ + ( + utility.array_to_string(mod.name), + mod.get_module_base() & mask, + (mod.get_module_base() & mask) + mod.get_core_size(), + ) + for mod in mods + ] + + @staticmethod + def lookup_module_address( + context: interfaces.context.ContextInterface, + kernel_module_name: str, + handlers: List[Tuple[str, int, int]], + target_address: int, + ) -> Tuple[str, str]: + """ + Searches between the start and end address of the kernel module using target_address. + Returns the module and symbol name of the address provided. + """ + kernel_module = context.modules[kernel_module_name] + mod_name = "UNKNOWN" + symbol_name = "N/A" + + for name, start, end in handlers: + if start <= target_address <= end: + mod_name = name + if name == constants.linux.KERNEL_NAME: + symbols = list( + kernel_module.get_symbols_by_absolute_location(target_address) + ) + + if len(symbols): + symbol_name = ( + symbols[0].split(constants.BANG)[1] + if constants.BANG in symbols[0] + else symbols[0] + ) + + break + + return mod_name, symbol_name From 43ab0b0c314832742c00f7821cd6f3327529894e Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Thu, 23 Jan 2025 10:43:14 +0100 Subject: [PATCH 02/19] add deprecation decorator --- .../framework/configuration/requirements.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 3e3608000..3af5601dc 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -11,6 +11,7 @@ expect to be in the context (such as particular layers or symboltables). import abc import logging import os +import functools from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Type from urllib import parse, request @@ -723,3 +724,25 @@ class ModuleRequirement( """Builds the appropriate configuration for the specified requirement.""" return context.modules[value].build_configuration() + + +def deprecated_method(replacement: str, additional_information: str = ""): + """A decorator for marking functions as deprecated. + + Args: + replacement: The replacement function overriding the deprecated API (full path preferred, starting from "volatility3."). String was preferred, for convenience and to prevent import conflicts on caller side. + additional_information: Information appended at the end of the deprecation message + """ + + def decorator(deprecated_func): + @functools.wraps(deprecated_func) + def wrapper(*args, **kwargs): + nonlocal replacement, additional_information + deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__name__}\" is deprecated, use \"{replacement}\" instead. {additional_information}" + vollog.warning(deprecation_msg) + # Return the wrapped function with its original arguments + return deprecated_func(*args, **kwargs) + + return wrapper + + return decorator From 7575aa5d6354419b51d1ac563c1de4db62da0ca1 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Thu, 23 Jan 2025 10:44:20 +0100 Subject: [PATCH 03/19] deprecate lookup_module_address and mask_mods_list --- .../framework/symbols/linux/__init__.py | 101 ++++++++---------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 423284b03..3dc744f78 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -8,11 +8,13 @@ import logging from abc import ABC, abstractmethod from typing import Iterator, List, Tuple, Optional, Union +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3 import framework from volatility3.framework import constants, exceptions, interfaces, objects from volatility3.framework.objects import utility from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux import extensions +from volatility3.framework.configuration import requirements vollog = logging.getLogger(__name__) @@ -81,7 +83,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable): class LinuxUtilities(interfaces.configuration.VersionableInterface): """Class with multiple useful linux functions.""" - _version = (2, 2, 0) + _version = (2, 2, 1) _required_framework_version = (2, 0, 0) framework.require_interface_version(*_required_framework_version) @@ -338,27 +340,6 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): yield fd_num, filp, full_path - @classmethod - def mask_mods_list( - cls, - context: interfaces.context.ContextInterface, - layer_name: str, - mods: Iterator[interfaces.objects.ObjectInterface], - ) -> List[Tuple[str, int, int]]: - """ - A helper function to mask the starting and end address of kernel modules - """ - mask = context.layers[layer_name].address_mask - - return [ - ( - utility.array_to_string(mod.name), - mod.get_module_base() & mask, - (mod.get_module_base() & mask) + mod.get_core_size(), - ) - for mod in mods - ] - @classmethod def generate_kernel_handler_info( cls, @@ -382,41 +363,9 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return [ (constants.linux.KERNEL_NAME, start_addr, end_addr) - ] + LinuxUtilities.mask_mods_list(context, kernel.layer_name, mods_list) - - @classmethod - def lookup_module_address( - cls, - kernel_module: interfaces.context.ModuleInterface, - handlers: List[Tuple[str, int, int]], - target_address: int, - ): - """ - Searches between the start and end address of the kernel module using target_address. - Returns the module and symbol name of the address provided. - """ - - mod_name = "UNKNOWN" - symbol_name = "N/A" - - for name, start, end in handlers: - if start <= target_address <= end: - mod_name = name - if name == constants.linux.KERNEL_NAME: - symbols = list( - kernel_module.get_symbols_by_absolute_location(target_address) - ) - - if len(symbols): - symbol_name = ( - symbols[0].split(constants.BANG)[1] - if constants.BANG in symbols[0] - else symbols[0] - ) - - break - - return mod_name, symbol_name + ] + linux_utilities_modules.Modules.mask_mods_list( + context, kernel.layer_name, mods_list + ) @classmethod def walk_internal_list(cls, vmlinux, struct_name, list_member, list_start): @@ -504,6 +453,44 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): [chr((code >> (i * 8)) & 0xFF) for i in range(code_bytes_length)] ) + ## Deprecated APIs ## + @classmethod + @requirements.deprecated_method( + replacement="volatility3.framework.symbols.linux.utilities.modules.Modules.mask_mods_list" + ) + def mask_mods_list( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + mods: Iterator[interfaces.objects.ObjectInterface], + ) -> List[Tuple[str, int, int]]: + """ + DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.mask_mods_list" instead. + + A helper function to mask the starting and end address of kernel modules + """ + return linux_utilities_modules.Modules.mask_mods_list(context, layer_name, mods) + + @classmethod + @requirements.deprecated_method( + replacement="volatility3.framework.symbols.linux.utilities.modules.Modules.lookup_module_address" + ) + def lookup_module_address( + cls, + kernel_module: interfaces.context.ModuleInterface, + handlers: List[Tuple[str, int, int]], + target_address: int, + ) -> Tuple[str, str]: + """ + DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.lookup_module_address" instead. + + Searches between the start and end address of the kernel module using target_address. + Returns the module and symbol name of the address provided. + """ + return linux_utilities_modules.Modules.lookup_module_address( + kernel_module.context, kernel_module.name, handlers, target_address + ) + class IDStorage(ABC): """Abstraction to support both XArray and RadixTree""" From fd77c041537b40f38db7428ce05a876ad5b1e08b Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Thu, 23 Jan 2025 10:46:42 +0100 Subject: [PATCH 04/19] move to linux_utilities_modules APIs --- volatility3/framework/plugins/linux/check_idt.py | 7 +++++-- .../framework/plugins/linux/keyboard_notifiers.py | 7 +++++-- volatility3/framework/plugins/linux/kthreads.py | 9 ++++++--- volatility3/framework/plugins/linux/netfilter.py | 7 +++++-- volatility3/framework/plugins/linux/tty_check.py | 7 +++++-- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index 07582e2c1..5859e73d6 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -5,6 +5,7 @@ import logging from typing import List +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import interfaces, renderers, symbols from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -99,8 +100,10 @@ class Check_idt(interfaces.plugins.PluginInterface): idt_addr = idt_addr & address_mask - module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( - vmlinux, handlers, idt_addr + module_name, symbol_name = ( + linux_utilities_modules.Modules.lookup_module_address( + self.context, vmlinux.name, handlers, idt_addr + ) ) yield ( diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index 72273a77b..c1b7572c6 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -4,6 +4,7 @@ import logging +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import interfaces, renderers, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -66,8 +67,10 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): ): call_addr = call_back.notifier_call - module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( - vmlinux, handlers, call_addr + module_name, symbol_name = ( + linux_utilities_modules.Modules.lookup_module_address( + self.context, vmlinux.name, handlers, call_addr + ) ) yield (0, [format_hints.Hex(call_addr), module_name, symbol_name]) diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 40e992069..2e1bbed47 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -4,6 +4,7 @@ import logging from typing import List +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins @@ -20,7 +21,7 @@ class Kthreads(plugins.PluginInterface): """Enumerates kthread functions""" _required_framework_version = (2, 11, 0) - _version = (1, 0, 2) + _version = (1, 0, 3) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -88,8 +89,10 @@ class Kthreads(plugins.PluginInterface): if kthread.has_member("full_name") else task_name ) - module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( - vmlinux, handlers, threadfn + module_name, symbol_name = ( + linux_utilities_modules.Modules.lookup_module_address( + self.context, vmlinux.name, handlers, threadfn + ) ) fields = [ diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 73496dfd9..ccb831b61 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -5,6 +5,7 @@ from dataclasses import dataclass, field from abc import ABC, abstractmethod import logging +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from typing import Iterator, List, Tuple from volatility3 import framework from volatility3.framework import ( @@ -263,8 +264,10 @@ class AbstractNetfilter(ABC): """Helper to obtain the module and symbol name in the format needed for the output of this plugin. """ - module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( - self.vmlinux, self.handlers, addr + module_name, symbol_name = ( + linux_utilities_modules.Modules.lookup_module_address( + self._context, self.vmlinux.name, self.handlers, addr + ) ) if module_name == "UNKNOWN": diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index 45238ef8c..f375968a4 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -5,6 +5,7 @@ import logging from typing import List +import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3.framework import interfaces, renderers, exceptions, constants from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins @@ -79,8 +80,10 @@ class tty_check(plugins.PluginInterface): recv_buf = tty_dev.ldisc.ops.receive_buf - module_name, symbol_name = linux.LinuxUtilities.lookup_module_address( - vmlinux, handlers, recv_buf + module_name, symbol_name = ( + linux_utilities_modules.Modules.lookup_module_address( + self.context, vmlinux.name, handlers, recv_buf + ) ) yield (0, (name, format_hints.Hex(recv_buf), module_name, symbol_name)) From 8b0165b6f6ea7a6ebb70ddb01b2de29b627ddcfd Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 14:45:21 +0100 Subject: [PATCH 05/19] add linux_utilities_modules requirement --- volatility3/framework/plugins/linux/check_idt.py | 5 +++++ volatility3/framework/plugins/linux/keyboard_notifiers.py | 5 +++++ volatility3/framework/plugins/linux/kthreads.py | 5 +++++ volatility3/framework/plugins/linux/netfilter.py | 5 +++++ volatility3/framework/plugins/linux/tty_check.py | 5 +++++ 5 files changed, 25 insertions(+) diff --git a/volatility3/framework/plugins/linux/check_idt.py b/volatility3/framework/plugins/linux/check_idt.py index 5859e73d6..ffb707af5 100644 --- a/volatility3/framework/plugins/linux/check_idt.py +++ b/volatility3/framework/plugins/linux/check_idt.py @@ -28,6 +28,11 @@ class Check_idt(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/linux/keyboard_notifiers.py b/volatility3/framework/plugins/linux/keyboard_notifiers.py index c1b7572c6..8577de848 100644 --- a/volatility3/framework/plugins/linux/keyboard_notifiers.py +++ b/volatility3/framework/plugins/linux/keyboard_notifiers.py @@ -27,6 +27,11 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 0, 0), + ), requirements.PluginRequirement( name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) ), diff --git a/volatility3/framework/plugins/linux/kthreads.py b/volatility3/framework/plugins/linux/kthreads.py index 2e1bbed47..bd0e895a4 100644 --- a/volatility3/framework/plugins/linux/kthreads.py +++ b/volatility3/framework/plugins/linux/kthreads.py @@ -31,6 +31,11 @@ class Kthreads(plugins.PluginInterface): description="Linux kernel", architectures=architectures.LINUX_ARCHS, ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 0, 0), + ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0) ), diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index ccb831b61..33a8ca7cc 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -691,6 +691,11 @@ class Netfilter(interfaces.plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 0, 0), + ), requirements.PluginRequirement( name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version ), diff --git a/volatility3/framework/plugins/linux/tty_check.py b/volatility3/framework/plugins/linux/tty_check.py index f375968a4..9bbca246c 100644 --- a/volatility3/framework/plugins/linux/tty_check.py +++ b/volatility3/framework/plugins/linux/tty_check.py @@ -30,6 +30,11 @@ class tty_check(plugins.PluginInterface): description="Linux kernel", architectures=["Intel32", "Intel64"], ), + requirements.VersionRequirement( + name="linux_utilities_modules", + component=linux_utilities_modules.Modules, + version=(1, 0, 0), + ), requirements.PluginRequirement( name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) ), From ce671bb2fa3a7d03042a993814b4c11f19650cf4 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 15:02:25 +0100 Subject: [PATCH 06/19] transfer deprecated_method into framework module --- volatility3/framework/__init__.py | 29 ++++++++++++++++++- .../framework/configuration/requirements.py | 23 --------------- .../framework/symbols/linux/__init__.py | 17 +++++++---- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/volatility3/framework/__init__.py b/volatility3/framework/__init__.py index a1925faef..12254ca77 100644 --- a/volatility3/framework/__init__.py +++ b/volatility3/framework/__init__.py @@ -11,7 +11,8 @@ import inspect import logging import os import traceback -from typing import Any, Dict, Generator, List, Optional, Tuple, Type, TypeVar +import functools +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar from volatility3.framework import constants, interfaces @@ -52,6 +53,32 @@ def require_interface_version(*args) -> None: ) +class Deprecation: + """Deprecation related methods.""" + + @staticmethod + def deprecated_method(replacement: Callable, additional_information: str = ""): + """A decorator for marking functions as deprecated. + + Args: + replacement: The replacement function overriding the deprecated API, in the form of a Callable (typically a method) + additional_information: Information appended at the end of the deprecation message + """ + + def decorator(deprecated_func): + @functools.wraps(deprecated_func) + def wrapper(*args, **kwargs): + nonlocal replacement, additional_information + deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__name__}\" is deprecated, use \"{replacement.__module__ + '.' + replacement.__name__}\" instead. {additional_information}" + vollog.warning(deprecation_msg) + # Return the wrapped function with its original arguments + return deprecated_func(*args, **kwargs) + + return wrapper + + return decorator + + class NonInheritable: def __init__(self, value: Any, cls: Type) -> None: self.default_value = value diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index 3af5601dc..3e3608000 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -11,7 +11,6 @@ expect to be in the context (such as particular layers or symboltables). import abc import logging import os -import functools from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Type from urllib import parse, request @@ -724,25 +723,3 @@ class ModuleRequirement( """Builds the appropriate configuration for the specified requirement.""" return context.modules[value].build_configuration() - - -def deprecated_method(replacement: str, additional_information: str = ""): - """A decorator for marking functions as deprecated. - - Args: - replacement: The replacement function overriding the deprecated API (full path preferred, starting from "volatility3."). String was preferred, for convenience and to prevent import conflicts on caller side. - additional_information: Information appended at the end of the deprecation message - """ - - def decorator(deprecated_func): - @functools.wraps(deprecated_func) - def wrapper(*args, **kwargs): - nonlocal replacement, additional_information - deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__name__}\" is deprecated, use \"{replacement}\" instead. {additional_information}" - vollog.warning(deprecation_msg) - # Return the wrapped function with its original arguments - return deprecated_func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 3dc744f78..0b7ef751c 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -10,11 +10,16 @@ from typing import Iterator, List, Tuple, Optional, Union import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules from volatility3 import framework -from volatility3.framework import constants, exceptions, interfaces, objects +from volatility3.framework import ( + constants, + exceptions, + interfaces, + objects, + Deprecation, +) from volatility3.framework.objects import utility from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux import extensions -from volatility3.framework.configuration import requirements vollog = logging.getLogger(__name__) @@ -455,8 +460,8 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): ## Deprecated APIs ## @classmethod - @requirements.deprecated_method( - replacement="volatility3.framework.symbols.linux.utilities.modules.Modules.mask_mods_list" + @Deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.mask_mods_list ) def mask_mods_list( cls, @@ -472,8 +477,8 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): return linux_utilities_modules.Modules.mask_mods_list(context, layer_name, mods) @classmethod - @requirements.deprecated_method( - replacement="volatility3.framework.symbols.linux.utilities.modules.Modules.lookup_module_address" + @Deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.lookup_module_address ) def lookup_module_address( cls, From d8658f0729f7abbd8410f76571aad6369deabee6 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 17:08:27 +0100 Subject: [PATCH 07/19] put deprecated functions order back --- .../framework/symbols/linux/__init__.py | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 0b7ef751c..bc2492f7a 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -345,6 +345,23 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): yield fd_num, filp, full_path + @classmethod + @Deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.mask_mods_list + ) + def mask_mods_list( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + mods: Iterator[interfaces.objects.ObjectInterface], + ) -> List[Tuple[str, int, int]]: + """ + DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.mask_mods_list" instead. + + A helper function to mask the starting and end address of kernel modules + """ + return linux_utilities_modules.Modules.mask_mods_list(context, layer_name, mods) + @classmethod def generate_kernel_handler_info( cls, @@ -372,6 +389,26 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): context, kernel.layer_name, mods_list ) + @classmethod + @Deprecation.deprecated_method( + replacement=linux_utilities_modules.Modules.lookup_module_address + ) + def lookup_module_address( + cls, + kernel_module: interfaces.context.ModuleInterface, + handlers: List[Tuple[str, int, int]], + target_address: int, + ) -> Tuple[str, str]: + """ + DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.lookup_module_address" instead. + + Searches between the start and end address of the kernel module using target_address. + Returns the module and symbol name of the address provided. + """ + return linux_utilities_modules.Modules.lookup_module_address( + kernel_module.context, kernel_module.name, handlers, target_address + ) + @classmethod def walk_internal_list(cls, vmlinux, struct_name, list_member, list_start): while list_start: @@ -458,44 +495,6 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): [chr((code >> (i * 8)) & 0xFF) for i in range(code_bytes_length)] ) - ## Deprecated APIs ## - @classmethod - @Deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.mask_mods_list - ) - def mask_mods_list( - cls, - context: interfaces.context.ContextInterface, - layer_name: str, - mods: Iterator[interfaces.objects.ObjectInterface], - ) -> List[Tuple[str, int, int]]: - """ - DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.mask_mods_list" instead. - - A helper function to mask the starting and end address of kernel modules - """ - return linux_utilities_modules.Modules.mask_mods_list(context, layer_name, mods) - - @classmethod - @Deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.lookup_module_address - ) - def lookup_module_address( - cls, - kernel_module: interfaces.context.ModuleInterface, - handlers: List[Tuple[str, int, int]], - target_address: int, - ) -> Tuple[str, str]: - """ - DEPRECATED: use "volatility3.framework.symbols.linux.utilities.modules.Modules.lookup_module_address" instead. - - Searches between the start and end address of the kernel module using target_address. - Returns the module and symbol name of the address provided. - """ - return linux_utilities_modules.Modules.lookup_module_address( - kernel_module.context, kernel_module.name, handlers, target_address - ) - class IDStorage(ABC): """Abstraction to support both XArray and RadixTree""" From 825720ed5d8f290b244735c758149e5b9d208c12 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 19:48:31 +0100 Subject: [PATCH 08/19] catch UnsatisfiedException at plugin runtime --- volatility3/cli/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 82a2a4205..d3ce74847 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -500,6 +500,16 @@ class CommandLine: renderer.filter = text_filter.CLIFilter(grid, args.filters) renderer.column_hide_list = args.hide_columns renderer.render(grid) + except exceptions.UnsatisfiedException as excp: + output = sys.stderr + output.write( + "An unsatisfied framework exception was encountered post plugin construction:\n" + ) + self.process_unsatisfied_exceptions(excp) + output.write( + f"Unable to validate the requirements: {[x for x in excp.unsatisfied]}\n", + ) + sys.exit(1) except exceptions.VolatilityException as excp: self.process_exceptions(excp) From 2ed00cc91a6764d05162c01055fc17c18124c6aa Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 19:52:47 +0100 Subject: [PATCH 09/19] add optional version requirement to deprecated_method --- volatility3/framework/__init__.py | 56 ++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/__init__.py b/volatility3/framework/__init__.py index 12254ca77..bf4ec4447 100644 --- a/volatility3/framework/__init__.py +++ b/volatility3/framework/__init__.py @@ -12,9 +12,11 @@ import logging import os import traceback import functools +import warnings from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar -from volatility3.framework import constants, interfaces +from volatility3.framework import constants, exceptions, interfaces +from volatility3.framework.configuration import requirements # ## @@ -57,20 +59,64 @@ class Deprecation: """Deprecation related methods.""" @staticmethod - def deprecated_method(replacement: Callable, additional_information: str = ""): + def deprecated_method( + replacement: Callable, + replacement_base_class_required_version: Tuple[int, int, int] = None, + additional_information: str = "", + ): """A decorator for marking functions as deprecated. Args: replacement: The replacement function overriding the deprecated API, in the form of a Callable (typically a method) + replacement_base_class_required_version: The "replacement" base class version that the deprecated method expects before proxying to it. This implies that "replacement" is a method from a class that inherits from VersionableInterface. additional_information: Information appended at the end of the deprecation message """ def decorator(deprecated_func): @functools.wraps(deprecated_func) def wrapper(*args, **kwargs): - nonlocal replacement, additional_information - deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__name__}\" is deprecated, use \"{replacement.__module__ + '.' + replacement.__name__}\" instead. {additional_information}" - vollog.warning(deprecation_msg) + nonlocal replacement, replacement_base_class_required_version, additional_information + # Prevent version mismatches between deprecated (proxy) methods and the ones they proxy + if replacement_base_class_required_version is not None and callable( + replacement + ): + # example: replacement = volatility3.MyClass.my_dummy_function + # "MyClass.my_dummy_function" -> "MyClass" + replacement_base_class_name = replacement.__qualname__.split(".")[0] + # replacement.__globals__ example: {'MyClass': } + replacement_base_class = replacement.__globals__.get( + replacement_base_class_name + ) + + # Verify that the base class inherits from VersionableInterface + if inspect.isclass(replacement_base_class) and issubclass( + replacement_base_class, + interfaces.configuration.VersionableInterface, + ): + # Construct a requirement + req = requirements.VersionRequirement( + name=replacement_base_class.__name__, + component=replacement_base_class, + version=replacement_base_class_required_version, + ) + # Verify the requirement + if not req.matches_required( + req._version, req._component.version + ): + full_unsat_req_path = ( + deprecated_func.__module__ + + "." + + deprecated_func.__qualname__ + + "." + + req.name + ) + # Catched by the cli and redirected to process_unsatisfied_exceptions + raise exceptions.UnsatisfiedException( + {full_unsat_req_path: req} + ) + + deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__qualname__}\" is deprecated, use \"{replacement.__module__ + '.' + replacement.__qualname__}\" instead. {additional_information}" + warnings.warn(deprecation_msg, FutureWarning) # Return the wrapped function with its original arguments return deprecated_func(*args, **kwargs) From 3657c6fe5e8db92b1a6364a962b778a89e802624 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 19:54:20 +0100 Subject: [PATCH 10/19] require Modules >= 1.0.0 on deprecated methods --- volatility3/framework/symbols/linux/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index bc2492f7a..6a01efc3a 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -347,7 +347,8 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): @classmethod @Deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.mask_mods_list + replacement=linux_utilities_modules.Modules.mask_mods_list, + replacement_base_class_required_version=(1, 0, 0), ) def mask_mods_list( cls, @@ -391,7 +392,8 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): @classmethod @Deprecation.deprecated_method( - replacement=linux_utilities_modules.Modules.lookup_module_address + replacement=linux_utilities_modules.Modules.lookup_module_address, + replacement_base_class_required_version=(1, 0, 0), ) def lookup_module_address( cls, From 4262eff8898b3fbc017abe3c1d1e4f13fa6fb189 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sat, 25 Jan 2025 20:04:51 +0100 Subject: [PATCH 11/19] adhere to AbstractNetfilter requirement checking --- .../framework/plugins/linux/netfilter.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py index 33a8ca7cc..ccb7509aa 100644 --- a/volatility3/framework/plugins/linux/netfilter.py +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -99,6 +99,20 @@ class AbstractNetfilter(ABC): f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}" ) + linux_utilities_modules_required_version = ( + Netfilter._required_linux_utilities_modules_version + ) + linux_utilities_modules_current_version = ( + linux_utilities_modules.Modules._version + ) + if not requirements.VersionRequirement.matches_required( + linux_utilities_modules_required_version, + linux_utilities_modules_current_version, + ): + raise exceptions.PluginRequirementException( + f"linux_utilities_modules.Modules version not suitable: required {linux_utilities_modules_required_version} found {linux_utilities_modules_current_version}" + ) + modules = lsmod.Lsmod.list_modules(context, kernel_module_name) self.handlers = linux.LinuxUtilities.generate_kernel_handler_info( context, kernel_module_name, modules @@ -680,6 +694,7 @@ class Netfilter(interfaces.plugins.PluginInterface): _version = (1, 1, 0) + _required_linux_utilities_modules_version = (1, 0, 0) _required_linuxutils_version = (2, 1, 0) _required_lsmod_version = (2, 0, 0) @@ -694,7 +709,7 @@ class Netfilter(interfaces.plugins.PluginInterface): requirements.VersionRequirement( name="linux_utilities_modules", component=linux_utilities_modules.Modules, - version=(1, 0, 0), + version=cls._required_linux_utilities_modules_version, ), requirements.PluginRequirement( name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version From 6d43dcd3a842d308705f3ecd3c023c94be473846 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 10:31:46 +0100 Subject: [PATCH 12/19] add VersionMismatchException --- volatility3/framework/exceptions.py | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/volatility3/framework/exceptions.py b/volatility3/framework/exceptions.py index 41c67b88d..0409ae5f0 100644 --- a/volatility3/framework/exceptions.py +++ b/volatility3/framework/exceptions.py @@ -8,9 +8,10 @@ space or symbol tables, and by layers when an address is invalid. The :class:`PagedInvalidAddressException` contains information about the size of the invalid page. """ -from typing import Dict, Optional +from typing import Callable, Dict, Optional, Tuple from volatility3.framework import interfaces +from volatility3.framework.interfaces.configuration import VersionableInterface class VolatilityException(Exception): @@ -134,3 +135,31 @@ class RenderException(VolatilityException): class LinuxPageCacheException(VolatilityException): """Thrown if there is an error during Linux Page Cache processing""" + + +class VersionMismatchException(VolatilityException): + """Thrown if a version mismatch has been encountered between two components.""" + + def __init__( + self, + source_component: Callable, + target_component: VersionableInterface, + target_version: Tuple[int, int, int], + failure_reason: str = None, + *args, + ): + """ + Args: + source_component: The component that required the target component + target_component: The component that is required. Must inherit from VersionableInterface + target_version: The version of the target component that was required, and ultimately was not satisfied + failure_reason: A detailed failure reason to enhande debugging and bug tracking + """ + super().__init__(*args) + self.source_component = source_component + self.target_component = target_component + self.target_version = target_version + self.failure_reason = failure_reason + + def __str__(self): + return f"{self.source_component.__module__+ '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__+ '.' + self.target_component.__qualname__} {self.target_component.version} unmet." From 4bd385cc9dadbfe8800fddc2bce9cef1ae4dadd5 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 10:33:02 +0100 Subject: [PATCH 13/19] handle VersionMismatchException --- volatility3/cli/__init__.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index d3ce74847..b57d9a3f3 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -500,16 +500,6 @@ class CommandLine: renderer.filter = text_filter.CLIFilter(grid, args.filters) renderer.column_hide_list = args.hide_columns renderer.render(grid) - except exceptions.UnsatisfiedException as excp: - output = sys.stderr - output.write( - "An unsatisfied framework exception was encountered post plugin construction:\n" - ) - self.process_unsatisfied_exceptions(excp) - output.write( - f"Unable to validate the requirements: {[x for x in excp.unsatisfied]}\n", - ) - sys.exit(1) except exceptions.VolatilityException as excp: self.process_exceptions(excp) @@ -583,6 +573,8 @@ class CommandLine: fulltrace = traceback.TracebackException.from_exception(excp).format(chain=True) vollog.debug("".join(fulltrace)) + file_a_bug_msg = f"Please re-run with -vvv and file a bug with the output at {constants.BUG_URL}" + if isinstance(excp, exceptions.InvalidAddressException): general = "Volatility was unable to read a requested page:" if isinstance(excp, exceptions.SwappedInvalidAddressException): @@ -627,9 +619,7 @@ class CommandLine: elif isinstance(excp, exceptions.LayerException): general = f"Volatility experienced a layer-related issue: {excp.layer_name}" detail = f"{excp}" - caused_by = [ - "A faulty layer implementation (re-run with -vvv and file a bug)" - ] + caused_by = [f"A faulty layer implementation. {file_a_bug_msg}"] elif isinstance(excp, exceptions.MissingModuleException): general = f"Volatility could not import a necessary module: {excp.module}" detail = f"{excp}" @@ -640,13 +630,17 @@ class CommandLine: general = "Volatility experienced an issue when rendering the output:" detail = f"{excp}" caused_by = ["An invalid renderer option, such as no visible columns"] + elif isinstance(excp, exceptions.VersionMismatchException): + general = "A version mismatch was detected between two components:" + detail = f"{excp}" + caused_by = [ + excp.failure_reason or "An outdated API caller, such as a method.", + file_a_bug_msg, + ] else: general = "Volatility encountered an unexpected situation." detail = "" - caused_by = [ - "Please re-run using with -vvv and file a bug with the output", - f"at {constants.BUG_URL}", - ] + caused_by = [file_a_bug_msg] # Code that actually renders the exception output = sys.stderr From ba6b709aba9496f75a61a806f06719189ac97491 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 10:34:18 +0100 Subject: [PATCH 14/19] use VersionMismatchException --- volatility3/framework/__init__.py | 37 ++++++++++--------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/volatility3/framework/__init__.py b/volatility3/framework/__init__.py index bf4ec4447..5d4e0f3c4 100644 --- a/volatility3/framework/__init__.py +++ b/volatility3/framework/__init__.py @@ -61,25 +61,23 @@ class Deprecation: @staticmethod def deprecated_method( replacement: Callable, - replacement_base_class_required_version: Tuple[int, int, int] = None, + replacement_version: Tuple[int, int, int] = None, additional_information: str = "", ): """A decorator for marking functions as deprecated. Args: replacement: The replacement function overriding the deprecated API, in the form of a Callable (typically a method) - replacement_base_class_required_version: The "replacement" base class version that the deprecated method expects before proxying to it. This implies that "replacement" is a method from a class that inherits from VersionableInterface. + replacement_version: The "replacement" base class version that the deprecated method expects before proxying to it. This implies that "replacement" is a method from a class that inherits from VersionableInterface. additional_information: Information appended at the end of the deprecation message """ def decorator(deprecated_func): @functools.wraps(deprecated_func) def wrapper(*args, **kwargs): - nonlocal replacement, replacement_base_class_required_version, additional_information + nonlocal replacement, replacement_version, additional_information # Prevent version mismatches between deprecated (proxy) methods and the ones they proxy - if replacement_base_class_required_version is not None and callable( - replacement - ): + if replacement_version is not None and callable(replacement): # example: replacement = volatility3.MyClass.my_dummy_function # "MyClass.my_dummy_function" -> "MyClass" replacement_base_class_name = replacement.__qualname__.split(".")[0] @@ -93,26 +91,15 @@ class Deprecation: replacement_base_class, interfaces.configuration.VersionableInterface, ): - # Construct a requirement - req = requirements.VersionRequirement( - name=replacement_base_class.__name__, - component=replacement_base_class, - version=replacement_base_class_required_version, - ) - # Verify the requirement - if not req.matches_required( - req._version, req._component.version + # SemVer check + if not requirements.VersionRequirement.matches_required( + replacement_version, replacement_base_class.version ): - full_unsat_req_path = ( - deprecated_func.__module__ - + "." - + deprecated_func.__qualname__ - + "." - + req.name - ) - # Catched by the cli and redirected to process_unsatisfied_exceptions - raise exceptions.UnsatisfiedException( - {full_unsat_req_path: req} + raise exceptions.VersionMismatchException( + deprecated_func, + replacement_base_class, + replacement_version, + "A deprecated method was unable to proxy the call to its replacement", ) deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__qualname__}\" is deprecated, use \"{replacement.__module__ + '.' + replacement.__qualname__}\" instead. {additional_information}" From 28081ed989599bc395b190cf503e8d83ef5b8fed Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 10:35:25 +0100 Subject: [PATCH 15/19] tidy up replacement_base_class_required_version --- volatility3/framework/symbols/linux/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 6a01efc3a..397c36c01 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -348,7 +348,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): @classmethod @Deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.mask_mods_list, - replacement_base_class_required_version=(1, 0, 0), + replacement_version=(1, 0, 0), ) def mask_mods_list( cls, @@ -393,7 +393,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface): @classmethod @Deprecation.deprecated_method( replacement=linux_utilities_modules.Modules.lookup_module_address, - replacement_base_class_required_version=(1, 0, 0), + replacement_version=(1, 0, 0), ) def lookup_module_address( cls, From 459483651b847241cb4189c4d1449fd18c0bdca7 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 10:53:33 +0100 Subject: [PATCH 16/19] typo --- volatility3/framework/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/exceptions.py b/volatility3/framework/exceptions.py index 0409ae5f0..99c5f155e 100644 --- a/volatility3/framework/exceptions.py +++ b/volatility3/framework/exceptions.py @@ -153,7 +153,7 @@ class VersionMismatchException(VolatilityException): source_component: The component that required the target component target_component: The component that is required. Must inherit from VersionableInterface target_version: The version of the target component that was required, and ultimately was not satisfied - failure_reason: A detailed failure reason to enhande debugging and bug tracking + failure_reason: A detailed failure reason to enhance debugging and bug tracking """ super().__init__(*args) self.source_component = source_component From 80eebd2f49bee665f194e4adba92cf12a192a997 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 13:18:30 +0100 Subject: [PATCH 17/19] use classmethod instead of staticmethod --- volatility3/framework/symbols/linux/utilities/modules.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/utilities/modules.py b/volatility3/framework/symbols/linux/utilities/modules.py index ac9b2afaf..82c63fc18 100644 --- a/volatility3/framework/symbols/linux/utilities/modules.py +++ b/volatility3/framework/symbols/linux/utilities/modules.py @@ -13,8 +13,9 @@ class Modules(interfaces.configuration.VersionableInterface): framework.require_interface_version(*_required_framework_version) - @staticmethod + @classmethod def mask_mods_list( + cls, context: interfaces.context.ContextInterface, layer_name: str, mods: Iterator[interfaces.objects.ObjectInterface], @@ -33,8 +34,9 @@ class Modules(interfaces.configuration.VersionableInterface): for mod in mods ] - @staticmethod + @classmethod def lookup_module_address( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str, handlers: List[Tuple[str, int, int]], From 5d58ba63dbd6a60fd36faca6f23a3a1b4a11e724 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 13:19:01 +0100 Subject: [PATCH 18/19] use __name__ instead of overkill __qualname__ --- volatility3/framework/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/exceptions.py b/volatility3/framework/exceptions.py index 99c5f155e..a3d660444 100644 --- a/volatility3/framework/exceptions.py +++ b/volatility3/framework/exceptions.py @@ -162,4 +162,4 @@ class VersionMismatchException(VolatilityException): self.failure_reason = failure_reason def __str__(self): - return f"{self.source_component.__module__+ '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__+ '.' + self.target_component.__qualname__} {self.target_component.version} unmet." + return f"{self.source_component.__module__+ '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__+ '.' + self.target_component.__name__} {self.target_component.version} unmet." From 4cb386858ebe7c62f05b16c9f869fdf762084121 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Sun, 26 Jan 2025 13:20:48 +0100 Subject: [PATCH 19/19] use __self__ and enhance exception msg --- volatility3/framework/__init__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/volatility3/framework/__init__.py b/volatility3/framework/__init__.py index 5d4e0f3c4..aa93340cd 100644 --- a/volatility3/framework/__init__.py +++ b/volatility3/framework/__init__.py @@ -77,14 +77,12 @@ class Deprecation: def wrapper(*args, **kwargs): nonlocal replacement, replacement_version, additional_information # Prevent version mismatches between deprecated (proxy) methods and the ones they proxy - if replacement_version is not None and callable(replacement): - # example: replacement = volatility3.MyClass.my_dummy_function - # "MyClass.my_dummy_function" -> "MyClass" - replacement_base_class_name = replacement.__qualname__.split(".")[0] - # replacement.__globals__ example: {'MyClass': } - replacement_base_class = replacement.__globals__.get( - replacement_base_class_name - ) + if ( + replacement_version is not None + and callable(replacement) + and hasattr(replacement, "__self__") + ): + replacement_base_class = replacement.__self__ # Verify that the base class inherits from VersionableInterface if inspect.isclass(replacement_base_class) and issubclass( @@ -99,7 +97,7 @@ class Deprecation: deprecated_func, replacement_base_class, replacement_version, - "A deprecated method was unable to proxy the call to its replacement", + "This is a bug, the deprecated call needs to be removed and the caller needs to update their code to use the new method.", ) deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__qualname__}\" is deprecated, use \"{replacement.__module__ + '.' + replacement.__qualname__}\" instead. {additional_information}"