Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge two Linux APIs for mapping kernel modules to pointers. Fix bugs… #1673

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
62 changes: 2 additions & 60 deletions volatility3/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@
import logging
import os
import traceback
import functools
import warnings
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, TypeVar

from volatility3.framework import constants, exceptions, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework import constants, interfaces

if (
sys.version_info.major != constants.REQUIRED_PYTHON_VERSION[0]
Expand Down Expand Up @@ -66,61 +63,6 @@ def require_interface_version(*args) -> None:
)


class Deprecation:
"""Deprecation related methods."""

@staticmethod
def deprecated_method(
replacement: Callable,
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_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_version, additional_information
# Prevent version mismatches between deprecated (proxy) methods and the ones they proxy
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(
replacement_base_class,
interfaces.configuration.VersionableInterface,
):
# SemVer check
if not requirements.VersionRequirement.matches_required(
replacement_version, replacement_base_class.version
):
raise exceptions.VersionMismatchException(
deprecated_func,
replacement_base_class,
replacement_version,
"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}"
warnings.warn(deprecation_msg, FutureWarning)
# 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
Expand Down
81 changes: 81 additions & 0 deletions volatility3/framework/deprecation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#

# This file contains the Deprecation class used to deprecate methods in an orderly manner

import warnings
import functools
import inspect

from typing import Callable, Tuple

from volatility3.framework import interfaces, exceptions
from volatility3.framework.configuration import requirements


def method_being_removed(message: str, removal_date: str):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance of a docstring for this please?

def decorator(deprecated_func):
@functools.wraps(deprecated_func)
def wrapper(*args, **kwargs):
warnings.warn(
f"This API ({deprecated_func.__module__}.{deprecated_func.__qualname__}) will be removed in the first release after {removal_date}. {message}",
FutureWarning,
)
return deprecated_func(*args, **kwargs)

return wrapper

return decorator


def deprecated_method(
replacement: Callable,
removal_date: str,
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_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_version, additional_information
# Prevent version mismatches between deprecated (proxy) methods and the ones they proxy
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(
replacement_base_class,
interfaces.configuration.VersionableInterface,
):
# SemVer check
if not requirements.VersionRequirement.matches_required(
replacement_version, replacement_base_class.version
):
raise exceptions.VersionMismatchException(
deprecated_func,
replacement_base_class,
replacement_version,
"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 and will be removed in the first release after {removal_date}, 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)

return wrapper

return decorator
2 changes: 1 addition & 1 deletion volatility3/framework/plugins/linux/check_idt.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(1, 0, 0),
version=(2, 0, 0),
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
Expand Down
67 changes: 22 additions & 45 deletions volatility3/framework/plugins/linux/check_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@
#

import logging
from typing import List
from typing import List, Dict

from volatility3.framework import interfaces, renderers, exceptions, constants
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, renderers, deprecation
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.linux import lsmod
from volatility3.framework.symbols.linux import extensions

vollog = logging.getLogger(__name__)


class Check_modules(plugins.PluginInterface):
"""Compares module list to sysfs info, if available"""

_version = (1, 0, 0)
_version = (2, 0, 0)
_required_framework_version = (2, 0, 0)

@classmethod
Expand All @@ -29,58 +30,34 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(2, 0, 0),
),
]

@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_kset_modules,
removal_date="2025-09-25",
replacement_version=(2, 0, 0),
)
def get_kset_modules(
cls, context: interfaces.context.ContextInterface, vmlinux_name: str
):
vmlinux = context.modules[vmlinux_name]

try:
module_kset = vmlinux.object_from_symbol("module_kset")
except exceptions.SymbolError:
module_kset = None

if not module_kset:
raise TypeError(
"This plugin requires the module_kset structure. This structure is not present in the supplied symbol table. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt."
)

ret = {}

kobj_off = vmlinux.get_type("module_kobject").relative_child_offset("kobj")

for kobj in module_kset.list.to_list(
vmlinux.symbol_table_name + constants.BANG + "kobject", "entry"
):
mod_kobj = vmlinux.object(
object_type="module_kobject",
offset=kobj.vol.offset - kobj_off,
absolute=True,
)

mod = mod_kobj.mod

try:
name = utility.pointer_to_string(kobj.name, 32)
except exceptions.InvalidAddressException:
continue

if kobj.name and kobj.reference_count() > 2:
ret[name] = mod

return ret
) -> Dict[str, extensions.module]:
return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name)

def _generator(self):
kset_modules = self.get_kset_modules(self.context, self.config["kernel"])
kset_modules = linux_utilities_modules.Modules.get_kset_modules(
self.context, self.config["kernel"]
)

lsmod_modules = set(
str(utility.array_to_string(modules.name))
for modules in lsmod.Lsmod.list_modules(self.context, self.config["kernel"])
for modules in linux_utilities_modules.Modules.list_modules(
self.context, self.config["kernel"]
)
)

for mod_name in set(kset_modules.keys()).difference(lsmod_modules):
Expand Down
Loading
Loading