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

Improve Linux OS detection #809

Merged
merged 10 commits into from
Sep 3, 2024
8 changes: 3 additions & 5 deletions dissect/target/plugins/os/unix/bsd/freebsd/_os.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

from typing import Optional

from dissect.target.filesystem import Filesystem
from dissect.target.plugin import export
from dissect.target.plugins.os.unix.bsd._os import BsdPlugin
Expand All @@ -14,13 +12,13 @@ def __init__(self, target: Target):
self._os_release = self._parse_os_release("/bin/freebsd-version*")

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
def detect(cls, target: Target) -> Filesystem | None:
for fs in target.filesystems:
if fs.exists("/net") or fs.exists("/.sujournal"):
if fs.exists("/net") and (fs.exists("/.sujournal") or fs.exists("/entropy")):
return fs

return None

@export(property=True)
def version(self) -> Optional[str]:
def version(self) -> str | None:
return self._os_release.get("USERLAND_VERSION")
24 changes: 11 additions & 13 deletions dissect/target/plugins/os/unix/linux/_os.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import logging
from typing import Optional

from dissect.target.filesystem import Filesystem
from dissect.target.helpers.network_managers import (
Expand All @@ -8,6 +9,8 @@
)
from dissect.target.plugin import OperatingSystem, export
from dissect.target.plugins.os.unix._os import UnixPlugin
from dissect.target.plugins.os.unix.bsd.osx._os import MacPlugin
from dissect.target.plugins.os.windows._os import WindowsPlugin
from dissect.target.target import Target

log = logging.getLogger(__name__)
Expand All @@ -20,17 +23,13 @@ def __init__(self, target: Target):
self.network_manager.discover()

@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
def detect(cls, target: Target) -> Filesystem | None:
for fs in target.filesystems:
if (
fs.exists("/var")
and fs.exists("/etc")
and fs.exists("/opt")
or (fs.exists("/sys") or fs.exists("/proc"))
and not fs.exists("/Library")
):
(fs.exists("/var") and fs.exists("/etc") and fs.exists("/opt"))
or (fs.exists("/sys/module") or fs.exists("/proc/sys"))
) and not (MacPlugin.detect(target) or WindowsPlugin.detect(target)):
return fs
return None

@export(property=True)
def ips(self) -> list[str]:
Expand Down Expand Up @@ -68,7 +67,7 @@ def netmask(self) -> list[str]:
return self.network_manager.get_config_value("netmask")

@export(property=True)
def version(self) -> str:
def version(self) -> str | None:
distrib_description = self._os_release.get("DISTRIB_DESCRIPTION", "")
name = self._os_release.get("NAME", "") or self._os_release.get("DISTRIB_ID", "")
version = (
Expand All @@ -78,10 +77,9 @@ def version(self) -> str:
)

if len(f"{name} {version}") > len(distrib_description):
return f"{name} {version}"
distrib_description = f"{name} {version}"

else:
return distrib_description
return distrib_description or None

@export(property=True)
def os(self) -> str:
Expand Down
12 changes: 10 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import annotations

import pathlib
import tempfile
import textwrap
from io import BytesIO
from typing import Callable, Iterator, Optional
from typing import Callable, Iterator

import pytest

Expand Down Expand Up @@ -50,7 +52,7 @@ def make_mock_target(tmp_path: pathlib.Path) -> Iterator[Target]:
def make_os_target(
tmp_path: pathlib.Path,
os_plugin: type[OSPlugin],
root_fs: Optional[Filesystem] = None,
root_fs: Filesystem | None = None,
apply_target: bool = True,
) -> Target:
mock_target = next(make_mock_target(tmp_path))
Expand Down Expand Up @@ -126,6 +128,12 @@ def fs_suse() -> Iterator[VirtualFilesystem]:
yield fs


@pytest.fixture
def fs_linux_sys(fs_linux: VirtualFilesystem) -> Iterator[VirtualFilesystem]:
fs_linux.makedirs("sys")
yield fs_linux


@pytest.fixture
def fs_linux_proc(fs_linux: VirtualFilesystem) -> Iterator[VirtualFilesystem]:
fs = fs_linux
Expand Down
49 changes: 49 additions & 0 deletions tests/plugins/os/unix/linux/test__os.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import pathlib
from typing import Iterator

import pytest

from dissect.target import Target
from dissect.target.filesystem import VirtualFilesystem
from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
from tests.conftest import make_os_target


@pytest.fixture
def target_linux_proc_sys(tmp_path: pathlib.Path) -> Iterator[Target]:
root_fs = VirtualFilesystem()
root_fs.makedirs("/proc/sys")
root_fs.makedirs("/sys/module")
target_linux_proc_sys = make_os_target(tmp_path, LinuxPlugin, root_fs)

yield target_linux_proc_sys


@pytest.fixture
def target_linux_windows_folder(tmp_path: pathlib.Path) -> Iterator[Target]:
root_fs = VirtualFilesystem()
root_fs.makedirs("/windows")
root_fs.makedirs("/var")
root_fs.makedirs("/etc")
root_fs.makedirs("/opt")

target_linux_proc_sys = make_os_target(tmp_path, LinuxPlugin, root_fs)

yield target_linux_proc_sys


def test_linux_os(target_linux: Target) -> None:
target_linux.add_plugin(LinuxPlugin)

assert target_linux.os == "linux"


def test_linux_os_windows_folder(target_linux_windows_folder: Target) -> None:
target_linux_windows_folder.add_plugin(LinuxPlugin)
assert target_linux_windows_folder._os_plugin.detect(target_linux_windows_folder) is not None
assert target_linux_windows_folder.os == "linux"


def test_linux_os_proc_sys(target_linux_proc_sys: Target) -> None:
assert target_linux_proc_sys._os_plugin.detect(target_linux_proc_sys) is not None
assert target_linux_proc_sys.os == "linux"
32 changes: 25 additions & 7 deletions tests/plugins/os/windows/test__os.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from typing import Any, Optional
from __future__ import annotations

from typing import Any, Iterator

import pytest

from dissect.target.filesystem import Filesystem
from dissect.target.helpers.regutil import VirtualKey, VirtualValue
from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
from dissect.target.plugins.os.windows._os import WindowsPlugin
from dissect.target.plugins.os.windows.registry import RegistryPlugin
from dissect.target.target import Target
Expand Down Expand Up @@ -31,7 +35,13 @@ def win_plugin(version_target: Target):
return WindowsPlugin(version_target)


def map_version_value(target: Target, name: Optional[str], value: Any):
@pytest.fixture
def target_win_linux_folders(target_win: Filesystem, fs_linux_sys: Filesystem) -> Iterator[Target]:
target_win.fs.mount("/", fs_linux_sys)
yield target_win


def map_version_value(target: Target, name: str | None, value: Any):
if name is not None:
hive = target.registry._root
hive.map_value(CURRENT_VERSION_KEY, name, VirtualValue(hive, name, value))
Expand All @@ -54,7 +64,7 @@ def assert_value(result: Any, value: Any):
def test_windowsplugin__legacy_curre_ntversion(
version_target: Target,
win_plugin: WindowsPlugin,
name: Optional[str],
name: str | None,
value: Any,
):
map_version_value(version_target, name, value)
Expand All @@ -73,7 +83,7 @@ def test_windowsplugin__legacy_curre_ntversion(
def test_windowsplugin__major_version(
version_target: Target,
win_plugin: WindowsPlugin,
name: Optional[str],
name: str | None,
value: Any,
):
map_version_value(version_target, name, value)
Expand All @@ -92,7 +102,7 @@ def test_windowsplugin__major_version(
def test_windowsplugin__minor_version(
version_target: Target,
win_plugin: WindowsPlugin,
name: Optional[str],
name: str | None,
value: Any,
):
map_version_value(version_target, name, value)
Expand Down Expand Up @@ -129,7 +139,7 @@ def test_windowsplugin__nt_version(
version_target: Target,
win_plugin: WindowsPlugin,
keys: list[tuple[str, Any]],
value: Optional[str],
value: str | None,
):
for key_name, key_value in keys:
map_version_value(version_target, key_name, key_value)
Expand Down Expand Up @@ -228,10 +238,18 @@ def test_windowsplugin_version(
version_target: Target,
win_plugin: WindowsPlugin,
keys: list[tuple[str, Any]],
value: Optional[str],
value: str | None,
):
for key_name, key_value in keys:
map_version_value(version_target, key_name, key_value)
result = win_plugin.version

assert_value(result, value)


def test_windows_os_detection_with_linux_folders(target_win_linux_folders: Target) -> None:
fs_linux = LinuxPlugin.detect(target_win_linux_folders)
fs_windows = WindowsPlugin.detect(target_win_linux_folders)

assert fs_linux is None
assert fs_windows is not None