diff --git a/dissect/target/plugins/os/unix/linux/modules.py b/dissect/target/plugins/os/unix/linux/modules.py new file mode 100644 index 000000000..740500ede --- /dev/null +++ b/dissect/target/plugins/os/unix/linux/modules.py @@ -0,0 +1,71 @@ +from dataclasses import dataclass +from typing import Iterator + +from dissect.target.exceptions import UnsupportedPluginError +from dissect.target.helpers.record import TargetRecordDescriptor +from dissect.target.plugin import Plugin, export +from dissect.target.target import Target + +ModuleRecord = TargetRecordDescriptor( + "linux/module", + [ + ("string", "name"), + ("varint", "size"), + ("varint", "refcount"), + ("string[]", "used_by"), + ("path", "source"), + ], +) + + +@dataclass +class Module: + path: str + name: str + size: int + refcnt: int + used_by: list[str] + + +class ModulePlugin(Plugin): + def __init__(self, target: Target): + super().__init__(target) + self._module_base_path = self.target.fs.path("/sys/module") + + def check_compatible(self) -> bool: + if not self._module_base_path.is_dir() or not next(self._module_base_path.iterdir(), None): + raise UnsupportedPluginError("No module paths found.") + + def _iterate_modules(self) -> Iterator[Module]: + for module_path in self._module_base_path.iterdir(): + if module_path.joinpath("initstate").exists(): + holders = [] + if (holders_path := module_path.joinpath("holders")).exists(): + holders = [item.name for item in holders_path.iterdir()] + yield Module( + module_path, + module_path.name, + int(module_path.joinpath("coresize").read_text()), + int(module_path.joinpath("refcnt").read_text()), + holders, + ) + + @export(record=ModuleRecord) + def sysmodules(self) -> Iterator[ModuleRecord]: + """Return information about active kernel modules.""" + for module in self._iterate_modules(): + yield ModuleRecord( + name=module.name, + size=module.size, + refcount=module.refcnt, + used_by=module.used_by, + source=module.path, + _target=self.target, + ) + + @export(output="yield") + def lsmod(self) -> Iterator[str]: + """Return information about active kernel modules in lsmod format""" + yield f"{'Module ':<28} {'Size':<7} Used by" + for module in self._iterate_modules(): + yield f"{module.name:<28} {module.size:<7} {module.refcnt} {','.join(module.used_by)}" diff --git a/tests/data/plugins/os/unix/linux/modules/module/modulea/coresize b/tests/data/plugins/os/unix/linux/modules/module/modulea/coresize new file mode 100644 index 000000000..56a6051ca --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/modulea/coresize @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/modulea/holders/holdera b/tests/data/plugins/os/unix/linux/modules/module/modulea/holders/holdera new file mode 100644 index 000000000..945c9b46d --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/modulea/holders/holdera @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/modulea/initstate b/tests/data/plugins/os/unix/linux/modules/module/modulea/initstate new file mode 100644 index 000000000..5fce625a9 --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/modulea/initstate @@ -0,0 +1 @@ +live \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/modulea/refcnt b/tests/data/plugins/os/unix/linux/modules/module/modulea/refcnt new file mode 100644 index 000000000..e440e5c84 --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/modulea/refcnt @@ -0,0 +1 @@ +3 \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/moduleb/coresize b/tests/data/plugins/os/unix/linux/modules/module/moduleb/coresize new file mode 100644 index 000000000..d8263ee98 --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/moduleb/coresize @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holdera b/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holdera new file mode 100644 index 000000000..945c9b46d --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holdera @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holderb b/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holderb new file mode 100644 index 000000000..945c9b46d --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/moduleb/holders/holderb @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/moduleb/initstate b/tests/data/plugins/os/unix/linux/modules/module/moduleb/initstate new file mode 100644 index 000000000..5fce625a9 --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/moduleb/initstate @@ -0,0 +1 @@ +live \ No newline at end of file diff --git a/tests/data/plugins/os/unix/linux/modules/module/moduleb/refcnt b/tests/data/plugins/os/unix/linux/modules/module/moduleb/refcnt new file mode 100644 index 000000000..bf0d87ab1 --- /dev/null +++ b/tests/data/plugins/os/unix/linux/modules/module/moduleb/refcnt @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/tests/test_plugins_os_unix_linux_modules.py b/tests/test_plugins_os_unix_linux_modules.py new file mode 100644 index 000000000..1f20e197d --- /dev/null +++ b/tests/test_plugins_os_unix_linux_modules.py @@ -0,0 +1,22 @@ +from dissect.target.filesystem import VirtualFilesystem +from dissect.target.plugins.os.unix.linux.modules import ModulePlugin +from dissect.target.target import Target + +from ._utils import absolute_path + + +def test_modules_plugin(target_unix: Target, fs_unix: VirtualFilesystem) -> None: + test_folder = absolute_path("data/plugins/os/unix/linux/modules/module") + fs_unix.map_dir("/sys/module", test_folder) + + target_unix.add_plugin(ModulePlugin) + results = sorted(list(target_unix.sysmodules()), key=lambda x: x.name) + assert len(results) == 2 + assert results[0].name == "modulea" + assert results[0].size == 1 + assert results[0].refcount == 3 + assert results[0].used_by == ["holdera"] + assert results[1].name == "moduleb" + assert results[1].size == 2 + assert results[1].refcount == 4 + assert results[1].used_by == ["holdera", "holderb"]