Merge pull request #942 from gcmoreira/mountinfo_fixes

Mountinfo fixes
This commit is contained in:
ikelos
2023-05-10 11:03:32 +01:00
committed by GitHub
5 changed files with 463 additions and 140 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ class IOMem(interfaces.plugins.PluginInterface):
name = utility.pointer_to_string(resource.name, 128)
except exceptions.InvalidAddressException:
vollog.warning(
"Unable to follow pointer to name for resource object at {resource_offset:#x}, "
f"Unable to follow pointer to name for resource object at {resource_offset:#x}, "
"replaced with UnreadableValue"
)
name = renderers.UnreadableValue()
@@ -9,8 +9,10 @@ from typing import Tuple, List, Iterable, Union
from volatility3.framework import renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
MountInfoData = namedtuple(
@@ -48,6 +50,9 @@ class MountInfo(plugins.PluginInterface):
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
),
requirements.ListRequirement(
name="pids",
description="Filter on specific process IDs.",
@@ -71,37 +76,6 @@ class MountInfo(plugins.PluginInterface):
),
]
@classmethod
def _do_get_path(cls, mnt, fs_root) -> Union[None, str]:
"""It mimics the Linux kernel prepend_path function."""
vfsmnt = mnt.mnt
dentry = vfsmnt.get_mnt_root()
path_reversed = []
while dentry != fs_root.dentry or vfsmnt.vol.offset != fs_root.mnt:
if dentry == vfsmnt.get_mnt_root() or dentry.is_root():
parent = mnt.get_mnt_parent().dereference()
# Escaped?
if dentry != vfsmnt.get_mnt_root():
return None
# Global root?
if mnt.vol.offset != parent.vol.offset:
dentry = mnt.get_mnt_mountpoint()
mnt = parent
vfsmnt = mnt.mnt
continue
return None
parent = dentry.d_parent
dname = dentry.d_name.name_as_str()
path_reversed.append(dname.strip("/"))
dentry = parent
path = "/" + "/".join(reversed(path_reversed))
return path
@classmethod
def get_mountinfo(
cls, mnt, task
@@ -115,8 +89,8 @@ class MountInfo(plugins.PluginInterface):
if not mnt_root:
return None
path_root = cls._do_get_path(mnt, task.fs.root)
if path_root is None:
path_root = linux.LinuxUtilities.get_path_mnt(task, mnt)
if not path_root:
return None
mnt_root_path = mnt_root.path()
@@ -170,9 +144,11 @@ class MountInfo(plugins.PluginInterface):
)
def _get_tasks_mountpoints(
self, tasks: Iterable[interfaces.objects.ObjectInterface], per_namespace: bool
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
filtered_by_pids: bool,
):
seen_namespaces = set()
seen_mountpoints = set()
for task in tasks:
if not (
task
@@ -181,19 +157,27 @@ class MountInfo(plugins.PluginInterface):
and task.nsproxy
and task.nsproxy.mnt_ns
):
# This task doesn't have all the information required
# This task doesn't have all the information required.
# It should be a kernel < 2.6.30
continue
mnt_namespace = task.nsproxy.mnt_ns
mnt_ns_id = mnt_namespace.get_inode()
if per_namespace:
if mnt_ns_id in seen_namespaces:
continue
else:
seen_namespaces.add(mnt_ns_id)
try:
mnt_ns_id = mnt_namespace.get_inode()
except AttributeError:
mnt_ns_id = renderers.NotAvailableValue()
for mount in mnt_namespace.get_mount_points():
# When PIDs are filtered, it makes sense that the user want to
# see each of those processes mount points. So we don't filter
# by mount id in this case.
if not filtered_by_pids:
mnt_id = int(mount.mnt_id)
if mnt_id in seen_mountpoints:
continue
else:
seen_mountpoints.add(mnt_id)
yield task, mount, mnt_ns_id
def _generator(
@@ -201,10 +185,20 @@ class MountInfo(plugins.PluginInterface):
tasks: Iterable[interfaces.objects.ObjectInterface],
mnt_ns_ids: List[int],
mount_format: bool,
per_namespace: bool,
filtered_by_pids: 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:
show_filter_warning = False
for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(
tasks, filtered_by_pids
):
if mnt_ns_ids and isinstance(mnt_ns_id, renderers.NotAvailableValue):
show_filter_warning = True
if (
not isinstance(mnt_ns_id, renderers.NotAvailableValue)
and mnt_ns_ids
and mnt_ns_id not in mnt_ns_ids
):
continue
mnt_info = self.get_mountinfo(mnt, task)
@@ -242,12 +236,17 @@ class MountInfo(plugins.PluginInterface):
]
fields_values = [mnt_ns_id]
if not per_namespace:
if filtered_by_pids:
fields_values.append(task.pid)
fields_values.extend(extra_fields_values)
yield (0, fields_values)
if show_filter_warning:
vollog.warning(
"Could not filter by mount namespace id. This field is not available in this kernel."
)
def run(self):
pids = self.config.get("pids")
mount_ns_ids = self.config.get("mntns")
@@ -263,9 +262,9 @@ class MountInfo(plugins.PluginInterface):
# to displays the mountpoints per namespace.
if pids:
columns.append(("PID", int))
per_namespace = False
filtered_by_pids = True
else:
per_namespace = True
filtered_by_pids = False
if self.config.get("mount-format"):
extra_columns = [
@@ -292,5 +291,6 @@ class MountInfo(plugins.PluginInterface):
columns.extend(extra_columns)
return renderers.TreeGrid(
columns, self._generator(tasks, mount_ns_ids, mount_format, per_namespace)
columns,
self._generator(tasks, mount_ns_ids, mount_format, filtered_by_pids),
)
+42 -10
View File
@@ -28,7 +28,11 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
self._vmlinux = vmlinux
self._task = task
netns_id = task.nsproxy.net_ns.get_inode()
try:
netns_id = task.nsproxy.net_ns.get_inode()
except AttributeError:
netns_id = NotAvailableValue()
self._netdevices = self._build_network_devices_map(netns_id)
self._sock_family_handlers = {
@@ -61,7 +65,10 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
self._vmlinux.symbol_table_name + constants.BANG + "net_device"
)
for net_dev in net.dev_base_head.to_list(net_device_symname, "dev_list"):
if net.get_inode() != netns_id:
if (
isinstance(netns_id, NotAvailableValue)
or net.get_inode() != netns_id
):
continue
dev_name = utility.array_to_string(net_dev.name)
netdevices_map[net_dev.ifindex] = dev_name
@@ -143,19 +150,32 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
return
bpfprog = sock_filter.prog
if bpfprog.type == 0:
# BPF_PROG_TYPE_UNSPEC = 0
BPF_PROG_TYPE_UNSPEC = 0 # cBPF filter
try:
bpfprog_type = bpfprog.get_type()
if bpfprog_type == BPF_PROG_TYPE_UNSPEC:
return # cBPF filter
except AttributeError:
# kernel < 3.18.140, it's a cBPF filter
return
BPF_PROG_TYPE_SOCKET_FILTER = 1 # eBPF filter
if bpfprog_type != BPF_PROG_TYPE_SOCKET_FILTER:
socket_filter["bpf_filter_type"] = f"UNK({bpfprog_type})"
vollog.warning(f"Unexpected BPF type {bpfprog_type} for a socket")
return
socket_filter["bpf_filter_type"] = "eBPF"
if not bpfprog.has_member("aux") or not bpfprog.aux:
return
return # kernel < 3.18.140
bpfprog_aux = bpfprog.aux
if bpfprog_aux.has_member("id"):
# `id` member was added to `bpf_prog_aux` in kernels 4.13
# `id` member was added to `bpf_prog_aux` in kernels 4.13.16
socket_filter["bpf_filter_id"] = str(bpfprog_aux.id)
if bpfprog_aux.has_member("name"):
# `name` was added to `bpf_prog_aux` in kernels 4.15
# `name` was added to `bpf_prog_aux` in kernels 4.15.18
bpfprog_name = utility.array_to_string(bpfprog_aux.name)
if bpfprog_name:
socket_filter["bpf_filter_name"] = bpfprog_name
@@ -227,14 +247,22 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
if netlink_sock.groups:
groups_bitmap = netlink_sock.groups.dereference()
src_addr = f"groups:0x{groups_bitmap:08x}"
src_port = netlink_sock.portid
try:
# Kernel >= 3.7.10
src_port = netlink_sock.get_portid()
except AttributeError:
src_port = NotAvailableValue()
dst_addr = f"group:0x{netlink_sock.dst_group:08x}"
module = netlink_sock.module
if module and module.name:
module_name_str = utility.array_to_string(module.name)
dst_addr = f"{dst_addr},lkm:{module_name_str}"
dst_port = netlink_sock.dst_portid
try:
dst_port = netlink_sock.get_dst_portid()
except AttributeError:
dst_port = NotAvailableValue()
state = netlink_sock.get_state()
@@ -518,7 +546,11 @@ class Sockstat(plugins.PluginInterface):
protocol = child_sock.get_protocol()
net = task.nsproxy.net_ns
netns_id = net.get_inode()
try:
netns_id = net.get_inode()
except AttributeError:
netns_id = NotAvailableValue()
yield task, netns_id, fd_num, family, sock_type, protocol, sock_fields
def _format_fields(self, sock_stat, protocol):
+126 -64
View File
@@ -1,7 +1,7 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Iterator, List, Tuple, Optional
from typing import Iterator, List, Tuple, Optional, Union
from volatility3 import framework
from volatility3.framework import constants, exceptions, interfaces, objects
@@ -30,6 +30,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("kobject", extensions.kobject)
# Might not exist in the current symbols
self.optional_set_type_class("module", extensions.module)
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
# Mount
self.set_type_class("vfsmount", extensions.vfsmount)
@@ -54,88 +55,106 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
class LinuxUtilities(interfaces.configuration.VersionableInterface):
"""Class with multiple useful linux functions."""
_version = (2, 0, 0)
_version = (2, 1, 0)
_required_framework_version = (2, 0, 0)
framework.require_interface_version(*_required_framework_version)
# based on __d_path from the Linux kernel
@classmethod
def _do_get_path(cls, rdentry, rmnt, dentry, vfsmnt) -> str:
ret_path: List[str] = []
def _get_path_file(cls, task, filp) -> str:
"""Returns the file pathname relative to the task's root directory.
while dentry != rdentry or vfsmnt != rmnt:
dname = dentry.path()
if dname == "":
break
Args:
task (task_struct): A reference task
filp (file *): A pointer to an open file
ret_path.insert(0, dname.strip("/"))
if dentry == vfsmnt.get_mnt_root() or dentry == dentry.d_parent:
if vfsmnt.get_mnt_parent() == vfsmnt:
Returns:
str: File pathname relative to the task's root directory.
"""
rdentry = task.fs.get_root_dentry()
rmnt = task.fs.get_root_mnt()
vfsmnt = filp.get_vfsmnt()
dentry = filp.get_dentry()
return cls.do_get_path(rdentry, rmnt, dentry, vfsmnt)
@classmethod
def get_path_mnt(cls, task, mnt) -> str:
"""Returns the mount point pathname relative to the task's root directory.
Args:
task (task_struct): A reference task
mnt (vfsmount or mount): A mounted filesystem or a mount point.
- kernels < 3.3.8 type is 'vfsmount'
- kernels >= 3.3.8 type is 'mount'
Returns:
str: Pathname of the mount point relative to the task's root directory.
"""
rdentry = task.fs.get_root_dentry()
rmnt = task.fs.get_root_mnt()
vfsmnt = mnt.get_vfsmnt_current()
dentry = mnt.get_dentry_current()
return cls.do_get_path(rdentry, rmnt, dentry, vfsmnt)
@classmethod
def do_get_path(cls, rdentry, rmnt, dentry, vfsmnt) -> Union[None, str]:
"""Returns a pathname of the mount point or file
It mimics the Linux kernel prepend_path function.
Args:
rdentry (dentry *): A pointer to the root dentry
rmnt (vfsmount *): A pointer to the root vfsmount
dentry (dentry *): A pointer to the dentry
vfsmnt (vfsmount *): A pointer to the vfsmount
Returns:
str: Pathname of the mount point or file
"""
path_reversed = []
while dentry != rdentry or not vfsmnt.is_equal(rmnt):
if dentry == vfsmnt.get_mnt_root() or dentry.is_root():
# Escaped?
if dentry != vfsmnt.get_mnt_root():
break
dentry = vfsmnt.get_mnt_mountpoint()
vfsmnt = vfsmnt.get_mnt_parent()
# Global root?
if not vfsmnt.has_parent():
break
dentry = vfsmnt.get_dentry_parent()
vfsmnt = vfsmnt.get_vfsmnt_parent()
continue
parent = dentry.d_parent
dname = dentry.d_name.name_as_str()
path_reversed.append(dname.strip("/"))
dentry = parent
# if we did not gather any valid dentrys in the path, then the entire file is
# either 1) smeared out of memory or 2) de-allocated and corresponding structures overwritten
# we return an empty string in this case to avoid confusion with something like a handle to the root
# directory (e.g., "/")
if not ret_path:
return ""
ret_val = "/".join([str(p) for p in ret_path if p != ""])
if ret_val.startswith(("socket:", "pipe:")):
if ret_val.find("]") == -1:
try:
inode = dentry.d_inode
ino = inode.i_ino
except exceptions.InvalidAddressException:
ino = 0
ret_val = ret_val[:-1] + f":[{ino}]"
else:
ret_val = ret_val.replace("/", "")
elif ret_val != "inotify":
ret_val = "/" + ret_val
return ret_val
# method used by 'older' kernels
# TODO: lookup when dentry_operations->d_name was merged into the mainline kernel for exact version
@classmethod
def _get_path_file(cls, task, filp) -> str:
rdentry = task.fs.get_root_dentry()
rmnt = task.fs.get_root_mnt()
dentry = filp.get_dentry()
vfsmnt = filp.get_vfsmnt()
return LinuxUtilities._do_get_path(rdentry, rmnt, dentry, vfsmnt)
path = "/" + "/".join(reversed(path_reversed))
return path
@classmethod
def _get_new_sock_pipe_path(cls, context, task, filp) -> str:
"""Returns the sock pipe pathname relative to the task's root directory.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
task (task_struct): A reference task
filp (file *): A pointer to a sock pipe open file
Returns:
str: Sock pipe pathname relative to the task's root directory.
"""
dentry = filp.get_dentry()
kernel_module = cls.get_module_from_volobj_type(context, dentry)
sym_addr = dentry.d_op.d_dname
symbol_table_arr = sym_addr.vol.type_name.split("!")
symbol_table = None
if len(symbol_table_arr) == 2:
symbol_table = symbol_table_arr[0]
for module_name in context.modules.get_modules_by_symbol_tables(symbol_table):
kernel_module = context.modules[module_name]
break
else:
raise ValueError(f"No module using the symbol table {symbol_table}")
symbs = list(kernel_module.get_symbols_by_absolute_location(sym_addr))
if len(symbs) == 1:
@@ -163,10 +182,21 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
return ret
# a 'file' structure doesn't have enough information to properly restore its full path
# we need the root mount information from task_struct to determine this
@classmethod
def path_for_file(cls, context, task, filp) -> str:
"""Returns a file (or sock pipe) pathname relative to the task's root directory.
A 'file' structure doesn't have enough information to properly restore its
full path we need the root mount information from task_struct to determine this
Args:
context: The context to retrieve required elements (layers, symbol tables) from
task (task_struct): A reference task
filp (file *): A pointer to an open file
Returns:
str: A file (or sock pipe) pathname relative to the task's root directory.
"""
try:
dentry = filp.get_dentry()
except exceptions.InvalidAddressException:
@@ -348,3 +378,35 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
return vmlinux.object(
object_type=type_name, offset=container_addr, absolute=True
)
@classmethod
def get_module_from_volobj_type(
cls,
context: interfaces.context.ContextInterface,
volobj: interfaces.objects.ObjectInterface,
) -> interfaces.context.ModuleInterface:
"""Get the vmlinux from a vol obj
Args:
context: The context to retrieve required elements (layers, symbol tables) from
volobj (vol object): A vol object
Raises:
ValueError: If it cannot obtain any module from the symbol table
Returns:
A kernel object (vmlinux)
"""
symbol_table_arr = volobj.vol.type_name.split("!", 1)
symbol_table = symbol_table_arr[0] if len(symbol_table_arr) == 2 else None
module_names = context.modules.get_modules_by_symbol_tables(symbol_table)
module_names = list(module_names)
if not module_names:
raise ValueError(f"No module using the symbol table '{symbol_table}'")
kernel_module_name = module_names[0]
kernel = context.modules[kernel_module_name]
return kernel
@@ -544,6 +544,7 @@ class struct_file(objects.StructType):
raise AttributeError("Unable to find file -> dentry")
def get_vfsmnt(self) -> interfaces.objects.ObjectInterface:
"""Returns the fs (vfsmount) where this file is mounted"""
if self.has_member("f_vfsmnt"):
return self.f_vfsmnt
elif self.has_member("f_path"):
@@ -675,11 +676,70 @@ class mount(objects.StructType):
raise AttributeError("Unable to find mount -> mount flags")
def get_mnt_parent(self):
"""Gets the fs where we are mounted on
Returns:
A 'mount *'
"""
return self.mnt_parent
def get_mnt_mountpoint(self):
"""Gets the dentry of the mountpoint
Returns:
A 'dentry *'
"""
return self.mnt_mountpoint
def get_parent_mount(self):
return self.mnt.get_parent_mount()
def has_parent(self) -> bool:
"""Checks if this mount has a parent
Returns:
bool: 'True' if this mount has a parent
"""
return self.mnt_parent != self.vol.offset
def get_vfsmnt_current(self):
"""Returns the fs where we are mounted on
Returns:
A 'vfsmount'
"""
return self.mnt
def get_vfsmnt_parent(self):
"""Gets the parent fs (vfsmount) to where it's mounted on
Returns:
A 'vfsmount'
"""
return self.get_mnt_parent().get_vfsmnt_current()
def get_dentry_current(self):
"""Returns the root of the mounted tree
Returns:
A 'dentry *'
"""
vfsmnt = self.get_vfsmnt_current()
dentry = vfsmnt.mnt_root
return dentry
def get_dentry_parent(self):
"""Returns the parent root of the mounted tree
Returns:
A 'dentry *'
"""
return self.get_mnt_parent().get_dentry_current()
def get_flags_access(self) -> str:
return "ro" if self.get_mnt_flags() & self.MNT_READONLY else "rw"
@@ -703,9 +763,6 @@ class mount(objects.StructType):
def get_devname(self) -> str:
return utility.pointer_to_string(self.mnt_devname, count=255)
def has_parent(self) -> bool:
return self.vol.offset != self.mnt_parent
def get_dominating_id(self, root) -> int:
"""Get ID of closest dominating peer group having a representative under the given root."""
mnt_seen = set()
@@ -783,24 +840,121 @@ class vfsmount(objects.StructType):
and self.get_mnt_parent() != 0
)
def _get_real_mnt(self):
table_name = self.vol.type_name.split(constants.BANG)[0]
mount_struct = f"{table_name}{constants.BANG}mount"
offset = self._context.symbol_space.get_type(
mount_struct
).relative_child_offset("mnt")
def _is_kernel_prior_to_struct_mount(self) -> bool:
"""Helper to distinguish between kernels prior to version 3.3.8 that
lacked the 'mount' structure and later versions that have it.
return self._context.object(
mount_struct, self.vol.layer_name, offset=self.vol.offset - offset
The 'mnt_parent' member was moved from struct 'vfsmount' to struct
'mount' when the latter was introduced.
Alternatively, vmlinux.has_type('mount') can be used here but it is faster.
Returns:
bool: 'True' if the kernel
"""
return self.has_member("mnt_parent")
def is_equal(self, vfsmount_ptr) -> bool:
"""Helper to make sure it is comparing two pointers to 'vfsmount'.
Depending on the kernel version, the calling object (self) could be
a 'vfsmount *' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust
in the framework "auto" dereferencing ability to assure that when we
reach this point 'self' will be a 'vfsmount' already and self.vol.offset
a 'vfsmount *' and not a 'vfsmount **'. The argument must be a 'vfsmount *'.
Typically, it's called from do_get_path().
Args:
vfsmount_ptr (vfsmount *): A pointer to a 'vfsmount'
Raises:
exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount *'
Returns:
bool: 'True' if the given argument points to the the same 'vfsmount'
as 'self'.
"""
if type(vfsmount_ptr) == objects.Pointer:
return self.vol.offset == vfsmount_ptr
else:
raise exceptions.VolatilityException(
"Unexpected argument type. It has to be a 'vfsmount *'"
)
def _get_real_mnt(self):
"""Gets the struct 'mount' containing this 'vfsmount'.
It should be only called from kernels >= 3.3.8 when 'struct mount' was introduced.
Returns:
mount: the struct 'mount' containing this 'vfsmount'.
"""
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
return linux.LinuxUtilities.container_of(
self.vol.offset, "mount", "mnt", vmlinux
)
def get_vfsmnt_current(self):
"""Returns the current fs where we are mounted on
Returns:
A 'vfsmount *'
"""
return self.get_mnt_parent()
def get_vfsmnt_parent(self):
"""Gets the parent fs (vfsmount) to where it's mounted on
Returns:
For kernels < 3.3.8: A 'vfsmount *'
For kernels >= 3.3.8: A 'vfsmount'
"""
if self._is_kernel_prior_to_struct_mount():
return self.get_mnt_parent()
else:
return self._get_real_mnt().get_vfsmnt_parent()
def get_dentry_current(self):
"""Returns the root of the mounted tree
Returns:
A 'dentry *'
"""
if self._is_kernel_prior_to_struct_mount():
return self.get_mnt_mountpoint()
else:
return self._get_real_mnt().get_dentry_current()
def get_dentry_parent(self):
"""Returns the parent root of the mounted tree
Returns:
A 'dentry *'
"""
if self._is_kernel_prior_to_struct_mount():
return self.get_mnt_mountpoint()
else:
return self._get_real_mnt().get_mnt_mountpoint()
def get_mnt_parent(self):
if self.has_member("mnt_parent"):
"""Gets the mnt_parent member.
Returns:
For kernels < 3.3.8: A 'vfsmount *'
For kernels >= 3.3.8: A 'mount *'
"""
if self._is_kernel_prior_to_struct_mount():
return self.mnt_parent
else:
return self._get_real_mnt().mnt_parent
return self._get_real_mnt().get_mnt_parent()
def get_mnt_mountpoint(self):
"""Gets the dentry of the mountpoint
Returns:
A 'dentry *'
"""
if self.has_member("mnt_mountpoint"):
return self.mnt_mountpoint
else:
@@ -809,6 +963,41 @@ class vfsmount(objects.StructType):
def get_mnt_root(self):
return self.mnt_root
def has_parent(self) -> bool:
if self._is_kernel_prior_to_struct_mount():
return self.mnt_parent != self.vol.offset
else:
return self._get_real_mnt().has_parent()
def get_mnt_sb(self):
return self.mnt_sb
def get_flags_access(self) -> str:
return "ro" if self.mnt_flags & mount.MNT_READONLY else "rw"
def get_flags_opts(self) -> Iterable[str]:
flags = [
mntflagtxt
for mntflag, mntflagtxt in mount.MNT_FLAGS.items()
if mntflag & self.mnt_flags != 0
]
return flags
def get_mnt_flags(self):
return self.mnt_flags
def is_shared(self) -> bool:
return self.get_mnt_flags() & mount.MNT_SHARED
def is_unbindable(self) -> bool:
return self.get_mnt_flags() & mount.MNT_UNBINDABLE
def is_slave(self) -> bool:
return self.mnt_master and self.mnt_master.vol.offset != 0
def get_devname(self) -> str:
return utility.pointer_to_string(self.mnt_devname, count=255)
class kobject(objects.StructType):
def reference_count(self):
@@ -825,7 +1014,7 @@ class mnt_namespace(objects.StructType):
def get_inode(self):
if self.has_member("proc_inum"):
return self.proc_inum
elif self.ns.has_member("inum"):
elif self.has_member("ns") and self.ns.has_member("inum"):
return self.ns.inum
else:
raise AttributeError("Unable to find mnt_namespace inode")
@@ -844,10 +1033,13 @@ class mnt_namespace(objects.StructType):
class net(objects.StructType):
def get_inode(self):
if self.has_member("proc_inum"):
# 3.8.13 <= kernel < 3.19.8
return self.proc_inum
elif self.ns.has_member("inum"):
elif self.has_member("ns") and self.ns.has_member("inum"):
# kernel >= 3.19.8
return self.ns.inum
else:
# kernel < 3.8.13
raise AttributeError("Unable to find net_namespace inode")
@@ -1055,6 +1247,26 @@ class netlink_sock(objects.StructType):
# Return the generic socket state
return self.sk.sk_socket.get_state()
def get_portid(self):
if self.has_member("pid"):
# kernel < 3.7.10
return self.pid
if self.has_member("portid"):
# kernel >= 3.7.10
return self.portid
else:
raise AttributeError("Unable to find a source port id")
def get_dst_portid(self):
if self.has_member("dst_pid"):
# kernel < 3.7.10
return self.dst_pid
if self.has_member("dst_portid"):
# kernel >= 3.7.10
return self.dst_portid
else:
raise AttributeError("Unable to find a destination port id")
class vsock_sock(objects.StructType):
def get_protocol(self):
@@ -1101,3 +1313,20 @@ class xdp_sock(objects.StructType):
def get_state(self):
# xdp_sock.state is an enum
return self.state.lookup()
class bpf_prog(objects.StructType):
def get_type(self):
# The program type was in `bpf_prog_aux::prog_type` from 3.18.140 to
# 4.1.52 before it was moved to `bpf_prog::type`
if self.has_member("type"):
# kernel >= 4.1.52
return self.type
if self.has_member("aux") and self.aux:
if self.aux.has_member("prog_type"):
# 3.18.140 <= kernel < 4.1.52
return self.aux.prog_type
# kernel < 3.18.140
raise AttributeError("Unable to find the BPF type")