diff --git a/volatility3/framework/plugins/linux/mountinfo.py b/volatility3/framework/plugins/linux/mountinfo.py index 10869a45b..a3006d47e 100644 --- a/volatility3/framework/plugins/linux/mountinfo.py +++ b/volatility3/framework/plugins/linux/mountinfo.py @@ -7,7 +7,7 @@ import logging from collections import namedtuple from typing import Tuple, List, Iterable, Union -from volatility3.framework import renderers, interfaces, constants +from volatility3.framework import renderers, interfaces from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins from volatility3.plugins.linux import pslist @@ -18,11 +18,11 @@ MountInfoData = namedtuple("MountInfoData", ("mnt_id", "parent_id", "st_dev", "m "mnt_opts", "fields", "mnt_type", "devname", "sb_opts")) class MountInfo(plugins.PluginInterface): - """Lists mount points in processes mount namespaces""" + """Lists mount points on processes mount namespaces""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -35,25 +35,19 @@ class MountInfo(plugins.PluginInterface): description="Filter on specific process IDs.", element_type=int, optional=True), - requirements.BooleanRequirement(name="all-processes", - description="Shows information about mount points for each process mount " - "namespace. It could take a while depending on the number of processes " - "running. Note that if this argument is not specified it uses the root " - "mount namespace based on pid 1.", - optional=True, - default=False), + requirements.ListRequirement(name="mntns", + description="Filter results by mount namespace. " + "Otherwise, all of them are shown.", + element_type=int, + optional=True), requirements.BooleanRequirement(name="mount-format", - description="Shows a brief summary of a process mount points information " + description="Shows a brief summary of the mount points information " "with similar output format to the older /proc/[pid]/mounts or the " "user-land command 'mount -l'.", optional=True, default=False), ] - def _get_symbol_fullname(self, symbol_basename: str) -> str: - """Given a short symbol or type name, it returns its full name""" - return self._vmlinux.symbol_table_name + constants.BANG + symbol_basename - @classmethod def _do_get_path(cls, mnt, fs_root) -> Union[None, str]: """It mimics the Linux kernel prepend_path function.""" @@ -139,21 +133,7 @@ class MountInfo(plugins.PluginInterface): return MountInfoData(mnt_id, parent_id, st_dev, mnt_root_path, path_root, mnt_opts, fields, mnt_type, devname, sb_opts) - def _get_mnt_namespace_mountpoints(self, mnt_namespace): - mnt_type = self._get_symbol_fullname("mount") - if not self.context.symbol_space.has_type(mnt_type): - # Old kernels ~ 2.6 - mnt_type = self._get_symbol_fullname("vfsmount") - - for mount in mnt_namespace.list.to_list(mnt_type, "mnt_list"): - yield mount - - def _get_tasks_mountpoints(self, pids: Iterable[int]): - self._vmlinux = self.context.modules[self.config['kernel']] - - pid_filter = pslist.PsList.create_pid_filter(pids) - tasks = pslist.PsList.list_tasks(self.context, self.config['kernel'], filter_func=pid_filter) - + def _get_tasks_mountpoints(self, tasks: Iterable[interfaces.objects.ObjectInterface], per_namespace: bool): seen_namespaces = set() for task in tasks: if not (task and task.fs and task.fs.root and task.nsproxy and task.nsproxy.mnt_ns): @@ -161,26 +141,33 @@ class MountInfo(plugins.PluginInterface): continue mnt_namespace = task.nsproxy.mnt_ns - mount_ns_id = mnt_namespace.get_inode() + mnt_ns_id = mnt_namespace.get_inode() - if self._show_mountpoints_per_namespace: - if mount_ns_id in seen_namespaces: + if per_namespace: + if mnt_ns_id in seen_namespaces: continue else: - seen_namespaces.add(mount_ns_id) + seen_namespaces.add(mnt_ns_id) - for mount in self._get_mnt_namespace_mountpoints(mnt_namespace): - yield task, mount, mount_ns_id + for mount in mnt_namespace.get_mount_points(): + yield task, mount, mnt_ns_id - def _generator(self): - pids = self.config.get('pids') + def _generator( + self, + tasks: Iterable[interfaces.objects.ObjectInterface], + mnt_ns_ids: List[int], + mount_format: bool, + per_namespace: bool) -> Iterable[Tuple[int, Tuple]]: + + for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(tasks, per_namespace): + if mnt_ns_ids and mnt_ns_id not in mnt_ns_ids: + continue - for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(pids=pids): mnt_info = self.get_mountinfo(mnt, task) if mnt_info is None: continue - if self.config.get('mount-format'): + if mount_format: all_opts = set() all_opts.update(mnt_info.mnt_opts) all_opts.update(mnt_info.sb_opts) @@ -197,22 +184,28 @@ class MountInfo(plugins.PluginInterface): mnt_info.devname, sb_opts_str] fields_values = [mnt_ns_id] - if not self._show_mountpoints_per_namespace: + if not per_namespace: fields_values.append(task.pid) fields_values.extend(extra_fields_values) yield (0, fields_values) def run(self): - if self.config.get('all-processes') and self.config.get('pids'): - raise ValueError("Unable to use --all-processes and specified a pid") + pids = self.config.get('pids') + mount_ns_ids = self.config.get('mntns') + mount_format = self.config.get('mount-format') - # When no arguments are specified, it displays the mountpoints per namespace - self._show_mountpoints_per_namespace = not any([self.config.get('pids'), self.config.get('all-processes')]) + pid_filter = pslist.PsList.create_pid_filter(pids) + tasks = pslist.PsList.list_tasks(self.context, self.config['kernel'], filter_func=pid_filter) columns = [("MNT_NS_ID", int)] - if not self._show_mountpoints_per_namespace: + # The PID column does not make sense when a PID filter is not specified. In that case, the default behavior is + # to displays the mountpoints per namespace. + if pids: columns.append(("PID", int)) + per_namespace = False + else: + per_namespace = True if self.config.get('mount-format'): extra_columns = [("DEVNAME", str), ("PATH", str), ("FSTYPE", str), ("MNT_OPTS", str)] @@ -224,4 +217,4 @@ class MountInfo(plugins.PluginInterface): columns.extend(extra_columns) - return renderers.TreeGrid(columns, self._generator()) + return renderers.TreeGrid(columns, self._generator(tasks, mount_ns_ids, mount_format, per_namespace)) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 3a237d280..23967dd25 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -555,12 +555,12 @@ class mount(objects.StructType): MNT_UNBINDABLE = 0x2000 MNT_FLAGS = { - MNT_NOSUID: "nosuid", - MNT_NODEV: "nodev", - MNT_NOEXEC: "noexec", - MNT_NOATIME: "noatime", + MNT_NOSUID: "nosuid", + MNT_NODEV: "nodev", + MNT_NOEXEC: "noexec", + MNT_NOATIME: "noatime", MNT_NODIRATIME: "nodiratime", - MNT_RELATIME: "relatime", + MNT_RELATIME: "relatime", } def get_mnt_sb(self): @@ -703,3 +703,13 @@ class mnt_namespace(objects.StructType): return self.ns.inum else: raise AttributeError("Unable to find mnt_namespace inode") + + def get_mount_points(self): + table_name = self.vol.type_name.split(constants.BANG)[0] + mnt_type = table_name + constants.BANG + "mount" + if not self._context.symbol_space.has_type(mnt_type): + # Old kernels ~ 2.6 + mnt_type = table_name + constants.BANG + "vfsmount" + + for mount in self.list.to_list(mnt_type, "mnt_list"): + yield mount