Skip to content

Commit d735680

Browse files
committed
Merge branch 'develop' into linux_fix_mnt_namespace_issue_1187
2 parents 4513806 + 966d23e commit d735680

File tree

11 files changed

+1718
-40
lines changed

11 files changed

+1718
-40
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
4747
- name: Clean up post-test
4848
run: |
49-
rm -rf *.lime
49+
rm -rf *.bin
5050
rm -rf *.img
5151
cd volatility3/symbols
5252
rm -rf linux

volatility3/framework/constants/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# We use the SemVer 2.0.0 versioning scheme
22
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
33
VERSION_MINOR = 8 # Number of changes that only add to the interface
4-
VERSION_PATCH = 0 # Number of changes that do not change the interface
4+
VERSION_PATCH = 1 # Number of changes that do not change the interface
55
VERSION_SUFFIX = ""
66

77
PACKAGE_VERSION = (
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
2+
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
3+
#
4+
import logging
5+
from typing import List
6+
7+
from volatility3.framework import renderers, interfaces, exceptions
8+
from volatility3.framework.renderers import format_hints
9+
from volatility3.framework.interfaces import plugins
10+
from volatility3.framework.configuration import requirements
11+
12+
vollog = logging.getLogger(__name__)
13+
14+
15+
class EBPF(plugins.PluginInterface):
16+
"""Enumerate eBPF programs"""
17+
18+
_required_framework_version = (2, 0, 0)
19+
20+
_version = (1, 0, 0)
21+
22+
@classmethod
23+
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
24+
return [
25+
requirements.ModuleRequirement(
26+
name="kernel",
27+
description="Linux kernel",
28+
architectures=["Intel32", "Intel64"],
29+
),
30+
]
31+
32+
def get_ebpf_programs(
33+
self,
34+
context: interfaces.context.ContextInterface,
35+
vmlinux_module_name: str,
36+
) -> interfaces.objects.ObjectInterface:
37+
"""Enumerate eBPF programs walking its IDR.
38+
39+
Args:
40+
context: The context to retrieve required elements (layers, symbol tables) from
41+
vmlinux_module_name: The name of the kernel module on which to operate
42+
Yields:
43+
eBPF program objects
44+
"""
45+
vmlinux = context.modules[vmlinux_module_name]
46+
47+
if not vmlinux.has_symbol("prog_idr"):
48+
raise exceptions.VolatilityException(
49+
"Cannot find the eBPF prog idr. Unsupported kernel"
50+
)
51+
52+
prog_idr = vmlinux.object_from_symbol("prog_idr")
53+
for page_addr in prog_idr.get_entries():
54+
bpf_prog = vmlinux.object("bpf_prog", offset=page_addr, absolute=True)
55+
yield bpf_prog
56+
57+
def _generator(self):
58+
for prog in self.get_ebpf_programs(self.context, self.config["kernel"]):
59+
prog_addr = prog.vol.offset
60+
prog_type = prog.get_type() or renderers.NotAvailableValue()
61+
prog_tag = prog.get_tag() or renderers.NotAvailableValue()
62+
prog_name = prog.get_name() or renderers.NotAvailableValue()
63+
fields = (format_hints.Hex(prog_addr), prog_name, prog_tag, prog_type)
64+
yield (0, fields)
65+
66+
def run(self):
67+
headers = [
68+
("Address", format_hints.Hex),
69+
("Name", str),
70+
("Tag", str),
71+
("Type", str),
72+
]
73+
return renderers.TreeGrid(headers, self._generator())

volatility3/framework/plugins/linux/lsof.py

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
1+
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
22
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
33
#
44
"""A module containing a collection of plugins that produce data typically
55
found in Linux's /proc file system."""
6-
import logging
6+
import logging, datetime
77
from typing import List, Callable
88

9-
from volatility3.framework import renderers, interfaces, constants
9+
from volatility3.framework import renderers, interfaces, constants, exceptions
1010
from volatility3.framework.configuration import requirements
1111
from volatility3.framework.interfaces import plugins
1212
from volatility3.framework.objects import utility
1313
from volatility3.framework.symbols import linux
1414
from volatility3.plugins.linux import pslist
15+
from volatility3.plugins import timeliner
1516

1617
vollog = logging.getLogger(__name__)
1718

1819

19-
class Lsof(plugins.PluginInterface):
20-
"""Lists all memory maps for all processes."""
20+
class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
21+
"""Lists open files for each processes."""
2122

2223
_required_framework_version = (2, 0, 0)
23-
24-
_version = (1, 1, 0)
24+
_version = (1, 2, 0)
2525

2626
@classmethod
2727
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -45,14 +45,37 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4545
),
4646
]
4747

48+
@classmethod
49+
def get_inode_metadata(cls, filp: interfaces.objects.ObjectInterface):
50+
try:
51+
dentry = filp.get_dentry()
52+
if dentry:
53+
inode_object = dentry.d_inode
54+
if inode_object and inode_object.is_valid():
55+
itype = (
56+
inode_object.get_inode_type() or renderers.NotAvailableValue()
57+
)
58+
return (
59+
inode_object.i_ino,
60+
itype,
61+
inode_object.i_size,
62+
inode_object.get_file_mode(),
63+
inode_object.get_change_time(),
64+
inode_object.get_modification_time(),
65+
inode_object.get_access_time(),
66+
)
67+
except (exceptions.InvalidAddressException, AttributeError) as e:
68+
vollog.warning(f"Can't get inode metadata: {e}")
69+
return None
70+
4871
@classmethod
4972
def list_fds(
5073
cls,
5174
context: interfaces.context.ContextInterface,
5275
symbol_table: str,
5376
filter_func: Callable[[int], bool] = lambda _: False,
5477
):
55-
linuxutils_symbol_table = None # type: ignore
78+
linuxutils_symbol_table = None
5679
for task in pslist.PsList.list_tasks(context, symbol_table, filter_func):
5780
if linuxutils_symbol_table is None:
5881
if constants.BANG not in task.vol.type_name:
@@ -69,21 +92,79 @@ def list_fds(
6992
for fd_fields in fd_generator:
7093
yield pid, task_comm, task, fd_fields
7194

95+
@classmethod
96+
def list_fds_and_inodes(
97+
cls,
98+
context: interfaces.context.ContextInterface,
99+
symbol_table: str,
100+
filter_func: Callable[[int], bool] = lambda _: False,
101+
):
102+
for pid, task_comm, task, (fd_num, filp, full_path) in cls.list_fds(
103+
context, symbol_table, filter_func
104+
):
105+
inode_metadata = cls.get_inode_metadata(filp)
106+
if inode_metadata is None:
107+
inode_metadata = tuple(
108+
interfaces.renderers.BaseAbsentValue() for _ in range(7)
109+
)
110+
yield pid, task_comm, task, fd_num, filp, full_path, inode_metadata
111+
72112
def _generator(self, pids, symbol_table):
73113
filter_func = pslist.PsList.create_pid_filter(pids)
74-
fds_generator = self.list_fds(
114+
fds_generator = self.list_fds_and_inodes(
75115
self.context, symbol_table, filter_func=filter_func
76116
)
77117

78-
for pid, task_comm, _task, fd_fields in fds_generator:
79-
fd_num, _filp, full_path = fd_fields
80-
81-
fields = (pid, task_comm, fd_num, full_path)
118+
for (
119+
pid,
120+
task_comm,
121+
task,
122+
fd_num,
123+
filp,
124+
full_path,
125+
inode_metadata,
126+
) in fds_generator:
127+
inode_num, itype, file_size, imode, ctime, mtime, atime = inode_metadata
128+
fields = (
129+
pid,
130+
task_comm,
131+
fd_num,
132+
full_path,
133+
inode_num,
134+
itype,
135+
imode,
136+
ctime,
137+
mtime,
138+
atime,
139+
file_size,
140+
)
82141
yield (0, fields)
83142

84143
def run(self):
85144
pids = self.config.get("pid", None)
86145
symbol_table = self.config["kernel"]
87146

88-
tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)]
147+
tree_grid_args = [
148+
("PID", int),
149+
("Process", str),
150+
("FD", int),
151+
("Path", str),
152+
("Inode", int),
153+
("Type", str),
154+
("Mode", str),
155+
("Changed", datetime.datetime),
156+
("Modified", datetime.datetime),
157+
("Accessed", datetime.datetime),
158+
("Size", int),
159+
]
89160
return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table))
161+
162+
def generate_timeline(self):
163+
pids = self.config.get("pid", None)
164+
symbol_table = self.config["kernel"]
165+
for row in self._generator(pids, symbol_table):
166+
_depth, row_data = row
167+
description = f'Process {row_data[1]} ({row_data[0]}) Open "{row_data[3]}"'
168+
yield description, timeliner.TimeLinerType.CHANGED, row_data[7]
169+
yield description, timeliner.TimeLinerType.MODIFIED, row_data[8]
170+
yield description, timeliner.TimeLinerType.ACCESSED, row_data[9]

volatility3/framework/plugins/linux/mountinfo.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class MountInfo(plugins.PluginInterface):
3737

3838
_required_framework_version = (2, 2, 0)
3939

40-
_version = (1, 0, 0)
40+
_version = (1, 2, 0)
4141

4242
@classmethod
4343
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -143,10 +143,10 @@ def get_mountinfo(
143143
sb_opts,
144144
)
145145

146+
@staticmethod
146147
def _get_tasks_mountpoints(
147-
self,
148148
tasks: Iterable[interfaces.objects.ObjectInterface],
149-
filtered_by_pids: bool,
149+
filtered_by_pids: bool = False,
150150
):
151151
seen_mountpoints = set()
152152
for task in tasks:
@@ -184,8 +184,8 @@ def _generator(
184184
self,
185185
tasks: Iterable[interfaces.objects.ObjectInterface],
186186
mnt_ns_ids: List[int],
187-
mount_format: bool,
188-
filtered_by_pids: bool,
187+
mount_format: bool = False,
188+
filtered_by_pids: bool = False,
189189
) -> Iterable[Tuple[int, Tuple]]:
190190
show_filter_warning = False
191191
for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(
@@ -247,6 +247,37 @@ def _generator(
247247
"Could not filter by mount namespace id. This field is not available in this kernel."
248248
)
249249

250+
@classmethod
251+
def get_superblocks(
252+
cls,
253+
context: interfaces.context.ContextInterface,
254+
vmlinux_module_name: str,
255+
) -> Iterable[interfaces.objects.ObjectInterface]:
256+
"""Yield file system superblocks based on the task's mounted filesystems.
257+
258+
Args:
259+
context: The context to retrieve required elements (layers, symbol tables) from
260+
vmlinux_module_name: The name of the kernel module on which to operate
261+
262+
Yields:
263+
super_block: Kernel's struct super_block object
264+
"""
265+
# No filter so that we get all the mount namespaces from all tasks
266+
tasks = pslist.PsList.list_tasks(context, vmlinux_module_name)
267+
268+
seen_sb_ptr = set()
269+
for task, mnt, _mnt_ns_id in cls._get_tasks_mountpoints(tasks):
270+
path_root = linux.LinuxUtilities.get_path_mnt(task, mnt)
271+
if not path_root:
272+
continue
273+
274+
sb_ptr = mnt.get_mnt_sb()
275+
if not sb_ptr or sb_ptr in seen_sb_ptr:
276+
continue
277+
seen_sb_ptr.add(sb_ptr)
278+
279+
yield sb_ptr.dereference(), path_root
280+
250281
def run(self):
251282
pids = self.config.get("pids")
252283
mount_ns_ids = self.config.get("mntns")

0 commit comments

Comments
 (0)