|
2 | 2 | # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 |
3 | 3 | # |
4 | 4 |
|
| 5 | +import logging |
| 6 | + |
5 | 7 | from volatility3.framework import interfaces, renderers |
6 | 8 | from volatility3.framework.configuration import requirements |
7 | 9 | from volatility3.framework.renderers import format_hints |
8 | 10 | from volatility3.plugins.linux import pslist |
9 | 11 |
|
| 12 | +vollog = logging.getLogger(__name__) |
| 13 | + |
10 | 14 |
|
11 | 15 | class PsTree(interfaces.plugins.PluginInterface): |
12 | 16 | """Plugin for listing processes in a tree based on their parent process ID.""" |
@@ -52,19 +56,41 @@ def find_level(self, pid: int) -> None: |
52 | 56 | Args: |
53 | 57 | pid: PID to find the level in the hierarchy |
54 | 58 | """ |
55 | | - seen = set([pid]) |
| 59 | + seen_ppids = set() |
| 60 | + seen_offsets = set() |
| 61 | + |
56 | 62 | level = 0 |
57 | 63 | proc = self._tasks.get(pid) |
58 | | - while proc and proc.get_parent_pid() not in seen: |
| 64 | + |
| 65 | + while proc: |
| 66 | + # we don't want swapper in the tree |
| 67 | + if proc.pid == 0: |
| 68 | + break |
| 69 | + |
59 | 70 | if proc.is_thread_group_leader: |
60 | 71 | parent_pid = proc.get_parent_pid() |
61 | 72 | else: |
62 | 73 | parent_pid = proc.tgid |
63 | 74 |
|
| 75 | + if parent_pid in seen_ppids or proc.vol.offset in seen_offsets: |
| 76 | + break |
| 77 | + |
| 78 | + # only pid 1 (init/systemd) or 2 (kthreadd) should have swapper as a parent |
| 79 | + # any other process with a ppid of 0 is smeared or terminated |
| 80 | + if parent_pid == 0 and proc.pid > 2: |
| 81 | + vollog.debug( |
| 82 | + "Smeared process with parent PID of 0 and PID greater than 2 ({proc.pid}) is being skipped." |
| 83 | + ) |
| 84 | + break |
| 85 | + |
| 86 | + seen_ppids.add(parent_pid) |
| 87 | + seen_offsets.add(proc.vol.offset) |
| 88 | + |
64 | 89 | child_list = self._children.setdefault(parent_pid, set()) |
65 | 90 | child_list.add(proc.pid) |
66 | 91 |
|
67 | 92 | proc = self._tasks.get(parent_pid) |
| 93 | + |
68 | 94 | level += 1 |
69 | 95 |
|
70 | 96 | self._levels[pid] = level |
@@ -110,12 +136,26 @@ def yield_processes(pid): |
110 | 136 | ) |
111 | 137 | yield (self._levels[task_fields.user_tid] - 1, fields) |
112 | 138 |
|
| 139 | + seen_children = set() |
| 140 | + |
113 | 141 | for child_pid in sorted(self._children.get(task_fields.user_tid, [])): |
| 142 | + if child_pid in seen_children: |
| 143 | + break |
| 144 | + seen_children.add(child_pid) |
| 145 | + |
114 | 146 | yield from yield_processes(child_pid) |
115 | 147 |
|
| 148 | + seen_processes = set() |
| 149 | + |
116 | 150 | for pid, level in self._levels.items(): |
117 | 151 | if level == 1: |
118 | | - yield from yield_processes(pid) |
| 152 | + for fields in yield_processes(pid): |
| 153 | + pid = fields[1] |
| 154 | + if pid in seen_processes: |
| 155 | + break |
| 156 | + seen_processes.add(pid) |
| 157 | + |
| 158 | + yield fields |
119 | 159 |
|
120 | 160 | def run(self): |
121 | 161 | filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) |
|
0 commit comments