99from volatility3 .framework import renderers , interfaces
1010from volatility3 .framework .configuration import requirements
1111from volatility3 .framework .interfaces import plugins
12+ from volatility3 .framework .symbols import linux
1213from volatility3 .plugins .linux import pslist
1314
15+
1416vollog = logging .getLogger (__name__ )
1517
1618MountInfoData = namedtuple (
@@ -48,6 +50,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
4850 requirements .PluginRequirement (
4951 name = "pslist" , plugin = pslist .PsList , version = (2 , 0 , 0 )
5052 ),
53+ requirements .VersionRequirement (
54+ name = "linuxutils" , component = linux .LinuxUtilities , version = (2 , 1 , 0 )
55+ ),
5156 requirements .ListRequirement (
5257 name = "pids" ,
5358 description = "Filter on specific process IDs." ,
@@ -71,37 +76,6 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
7176 ),
7277 ]
7378
74- @classmethod
75- def _do_get_path (cls , mnt , fs_root ) -> Union [None , str ]:
76- """It mimics the Linux kernel prepend_path function."""
77- vfsmnt = mnt .mnt
78- dentry = vfsmnt .get_mnt_root ()
79-
80- path_reversed = []
81- while dentry != fs_root .dentry or vfsmnt .vol .offset != fs_root .mnt :
82- if dentry == vfsmnt .get_mnt_root () or dentry .is_root ():
83- parent = mnt .get_mnt_parent ().dereference ()
84- # Escaped?
85- if dentry != vfsmnt .get_mnt_root ():
86- return None
87-
88- # Global root?
89- if mnt .vol .offset != parent .vol .offset :
90- dentry = mnt .get_mnt_mountpoint ()
91- mnt = parent
92- vfsmnt = mnt .mnt
93- continue
94-
95- return None
96-
97- parent = dentry .d_parent
98- dname = dentry .d_name .name_as_str ()
99- path_reversed .append (dname .strip ("/" ))
100- dentry = parent
101-
102- path = "/" + "/" .join (reversed (path_reversed ))
103- return path
104-
10579 @classmethod
10680 def get_mountinfo (
10781 cls , mnt , task
@@ -115,8 +89,8 @@ def get_mountinfo(
11589 if not mnt_root :
11690 return None
11791
118- path_root = cls . _do_get_path ( mnt , task . fs . root )
119- if path_root is None :
92+ path_root = linux . LinuxUtilities . get_path_mnt ( task , mnt )
93+ if not path_root :
12094 return None
12195
12296 mnt_root_path = mnt_root .path ()
@@ -170,9 +144,11 @@ def get_mountinfo(
170144 )
171145
172146 def _get_tasks_mountpoints (
173- self , tasks : Iterable [interfaces .objects .ObjectInterface ], per_namespace : bool
147+ self ,
148+ tasks : Iterable [interfaces .objects .ObjectInterface ],
149+ filtered_by_pids : bool ,
174150 ):
175- seen_namespaces = set ()
151+ seen_mountpoints = set ()
176152 for task in tasks :
177153 if not (
178154 task
@@ -181,30 +157,48 @@ def _get_tasks_mountpoints(
181157 and task .nsproxy
182158 and task .nsproxy .mnt_ns
183159 ):
184- # This task doesn't have all the information required
160+ # This task doesn't have all the information required.
161+ # It should be a kernel < 2.6.30
185162 continue
186163
187164 mnt_namespace = task .nsproxy .mnt_ns
188- mnt_ns_id = mnt_namespace .get_inode ()
189-
190- if per_namespace :
191- if mnt_ns_id in seen_namespaces :
192- continue
193- else :
194- seen_namespaces .add (mnt_ns_id )
165+ try :
166+ mnt_ns_id = mnt_namespace .get_inode ()
167+ except AttributeError :
168+ mnt_ns_id = renderers .NotAvailableValue ()
195169
196170 for mount in mnt_namespace .get_mount_points ():
171+ # When PIDs are filtered, it makes sense that the user want to
172+ # see each of those processes mount points. So we don't filter
173+ # by mount id in this case.
174+ if not filtered_by_pids :
175+ mnt_id = int (mount .mnt_id )
176+ if mnt_id in seen_mountpoints :
177+ continue
178+ else :
179+ seen_mountpoints .add (mnt_id )
180+
197181 yield task , mount , mnt_ns_id
198182
199183 def _generator (
200184 self ,
201185 tasks : Iterable [interfaces .objects .ObjectInterface ],
202186 mnt_ns_ids : List [int ],
203187 mount_format : bool ,
204- per_namespace : bool ,
188+ filtered_by_pids : bool ,
205189 ) -> Iterable [Tuple [int , Tuple ]]:
206- for task , mnt , mnt_ns_id in self ._get_tasks_mountpoints (tasks , per_namespace ):
207- if mnt_ns_ids and mnt_ns_id not in mnt_ns_ids :
190+ show_filter_warning = False
191+ for task , mnt , mnt_ns_id in self ._get_tasks_mountpoints (
192+ tasks , filtered_by_pids
193+ ):
194+ if mnt_ns_ids and isinstance (mnt_ns_id , renderers .NotAvailableValue ):
195+ show_filter_warning = True
196+
197+ if (
198+ not isinstance (mnt_ns_id , renderers .NotAvailableValue )
199+ and mnt_ns_ids
200+ and mnt_ns_id not in mnt_ns_ids
201+ ):
208202 continue
209203
210204 mnt_info = self .get_mountinfo (mnt , task )
@@ -242,12 +236,17 @@ def _generator(
242236 ]
243237
244238 fields_values = [mnt_ns_id ]
245- if not per_namespace :
239+ if filtered_by_pids :
246240 fields_values .append (task .pid )
247241 fields_values .extend (extra_fields_values )
248242
249243 yield (0 , fields_values )
250244
245+ if show_filter_warning :
246+ vollog .warning (
247+ "Could not filter by mount namespace id. This field is not available in this kernel."
248+ )
249+
251250 def run (self ):
252251 pids = self .config .get ("pids" )
253252 mount_ns_ids = self .config .get ("mntns" )
@@ -263,9 +262,9 @@ def run(self):
263262 # to displays the mountpoints per namespace.
264263 if pids :
265264 columns .append (("PID" , int ))
266- per_namespace = False
265+ filtered_by_pids = True
267266 else :
268- per_namespace = True
267+ filtered_by_pids = False
269268
270269 if self .config .get ("mount-format" ):
271270 extra_columns = [
@@ -292,5 +291,6 @@ def run(self):
292291 columns .extend (extra_columns )
293292
294293 return renderers .TreeGrid (
295- columns , self ._generator (tasks , mount_ns_ids , mount_format , per_namespace )
294+ columns ,
295+ self ._generator (tasks , mount_ns_ids , mount_format , filtered_by_pids ),
296296 )
0 commit comments