Package rekall :: Package plugins :: Package overlays :: Package linux :: Module vfs
[frames] | no frames]

Source Code for Module rekall.plugins.overlays.linux.vfs

  1  # This program is free software; you can redistribute it and/or modify 
  2  # it under the terms of the GNU General Public License as published by 
  3  # the Free Software Foundation; either version 2 of the License, or (at 
  4  # your option) any later version. 
  5  # 
  6  # This program is distributed in the hope that it will be useful, but 
  7  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  8  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
  9  # General Public License for more details. 
 10  # 
 11  # You should have received a copy of the GNU General Public License 
 12  # along with this program; if not, write to the Free Software 
 13  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 14   
 15  """ 
 16  @author:       Michael Cohen 
 17  @license:      GNU General Public License 2.0 or later 
 18  @contact:      scudette@gmail.com 
 19  @organization: Google Inc. 
 20   
 21  This file encapsulates various virtual file system operations for supported 
 22  linux versions. The code is basically copied from the kernel sources of the 
 23  relevant versions. 
 24  """ 
 25  import posixpath 
 26  import re 
 27  import math 
 28   
 29  from rekall import obj 
 30  from rekall.plugins.overlays import basic 
 31  from rekall_lib import utils 
32 33 34 -class File(object):
35 """Represents a Linux file.""" 36
37 - def __init__(self, filename=None, mountpoint=None, dentry=None, 38 is_root=False, session=None):
39 if isinstance(filename, (basestring, basic.String)): 40 self.filename = utils.SmartUnicode(filename).split("/") 41 elif isinstance(filename, list): 42 self.filename = filename 43 elif not filename: 44 self.filename = [] 45 else: 46 raise TypeError("Invalid filename.") 47 self.mountpoint = mountpoint or MountPoint() 48 self.dentry = dentry 49 self.is_root = is_root 50 self.session = session
51 52 @utils.safe_property
53 - def fullpath(self):
54 if self.is_root: 55 return self.mountpoint.name 56 else: 57 return '/'.join([self.mountpoint.name.rstrip("/"), 58 '/'.join(self.filename)])
59 60 @utils.safe_property
61 - def name(self):
62 try: 63 return self.filename[-1] or obj.NoneObject() 64 except IndexError: 65 return obj.NoneObject()
66 67 @utils.safe_property
68 - def size(self):
69 return self.dentry.d_inode.i_size
70 71 @utils.safe_property
72 - def extents(self):
73 """Returns a list of ranges for which we have data in memory.""" 74 page_size = self.session.kernel_address_space.PAGE_SIZE 75 range_start = None 76 77 index = 0 78 while index <= (self.size / page_size): 79 page = self._radix_tree_lookup(index) 80 if page: 81 # Start a new range if previously there was no data. 82 if range_start == None: 83 range_start = index * page_size 84 else: 85 # Finish the current range if previously we had data. 86 if range_start != None: 87 range_end = (index * page_size) -1 88 range_end = min(self.size, range_end) 89 yield (range_start, range_end) 90 range_start = None 91 index += 1 92 93 # Close the last range, as if there was data in the tail of the file, we 94 # wouldn't have outputted it. 95 # Since we cannot guarantee that there won't be data in the last index+1 96 # of the radix tree we do an explicit check, instead of walking 97 # total_indexes+1 in the algorithm above. 98 if range_start != None: 99 range_end = (index * page_size) - 1 100 range_end = min(self.size, range_end) 101 yield (range_start, range_end)
102
103 - def _radix_tree_is_indirect_ptr(self, ptr):
104 """See include/linux/radix-tree.h -> is_indirect_ptr().""" 105 return ptr & 1
106
107 - def _radix_tree_indirect_to_ptr(self, node_p):
108 """See lib/radix-tree.c -> indirect_to_ptr().""" 109 110 return self.dentry.obj_profile.Object( 111 "Pointer", target="radix_tree_node", 112 value=node_p.v() & ~1, 113 profile=node_p.obj_profile, 114 vm=node_p.obj_vm)
115
116 - def _radix_tree_lookup_element(self, index, is_slot):
117 """See lib/radix-tree.c -> radix_tree_lookup_element(). 118 119 Searches either for the slot or the node of the index of a radix_tree. 120 """ 121 122 root = self.dentry.d_inode.i_mapping.page_tree 123 node = root.rnode 124 if not node: 125 return obj.NoneObject() 126 127 # The RADIX_TREE_MAP_SIZE is the length of the node.slots array. 128 self.RADIX_TREE_MAP_SIZE = len(node.slots) 129 self.RADIX_TREE_MAP_SHIFT = int( 130 math.log(self.RADIX_TREE_MAP_SIZE) / math.log(2)) 131 self.RADIX_TREE_MAP_MASK = self.RADIX_TREE_MAP_SIZE - 1 132 133 if not self._radix_tree_is_indirect_ptr(node): 134 if index > 0: 135 return obj.NoneObject() 136 if is_slot: 137 return node.reference() 138 else: 139 return node 140 141 node = self._radix_tree_indirect_to_ptr(node) 142 height = node.height 143 shift = (height - 1) * self.RADIX_TREE_MAP_SHIFT 144 slot = -1 145 146 while 1: 147 idx = (index >> shift) & self.RADIX_TREE_MAP_MASK 148 node = node.slots[idx] 149 node = node.cast("Pointer", target="radix_tree_node") 150 slot = node.reference() 151 shift -= self.RADIX_TREE_MAP_SHIFT 152 height -= 1 153 if height <= 0: 154 break 155 156 if slot == -1: 157 return obj.NoneObject() 158 159 if is_slot: 160 return slot 161 else: 162 return self._radix_tree_indirect_to_ptr(node)
163
164 - def _radix_tree_lookup_slot(self, index):
165 """See lib/radix-tree.c .""" 166 return self._radix_tree_lookup_element(index, 1)
167
168 - def _radix_tree_lookup(self, index):
169 """See lib/radix-tree.c .""" 170 return self._radix_tree_lookup_element(index, 0)
171
172 - def GetPage(self, page_index):
173 page_size = self.session.kernel_address_space.PAGE_SIZE 174 page = self._radix_tree_lookup(page_index) 175 if page: 176 page = page.dereference_as("page") 177 return page.read(0, page_size)
178
179 - def walk(self, recursive=False, unallocated=False):
180 if not self.is_directory(): 181 return 182 183 results = [] 184 185 for dentry in self.dentry.d_subdirs.list_of_type_fast("dentry", "d_u"): 186 filename = dentry.d_name.name.deref() 187 inode = dentry.d_inode 188 189 # If we are the root pseudofile, we have no name. 190 if self.is_root: 191 child_filename = unicode(filename) 192 else: 193 child_filename = self.filename + [unicode(filename)] 194 195 new_file = File(filename=child_filename, 196 mountpoint=self.mountpoint, 197 dentry=dentry, 198 session=self.session) 199 200 if unallocated or (filename != None and inode): 201 results.append(new_file) 202 203 if recursive and inode and inode.type.S_IFDIR: 204 if recursive and inode.type.S_IFDIR: 205 for sub_file in new_file.walk(recursive=recursive, 206 unallocated=unallocated): 207 results.append(sub_file) 208 209 for file_ in sorted(results, key=lambda x: x.fullpath): 210 yield file_
211
212 - def __eq__(self, other):
213 if isinstance(other, File): 214 return (self.fullpath == other.fullpath and 215 self.dentry == other.dentry and 216 self.mountpoint == other.mountpoint) 217 return False
218
219 - def is_directory(self):
220 return self.dentry and self.dentry.d_inode.type.S_IFDIR
221
222 223 -class MountPoint(object):
224 """Represents a Linux mount point.""" 225
226 - def __init__(self, device="(undefined device)", 227 mount_path="(undefined mount path)", 228 superblock=None, flags=None, session=None):
229 self.device = device 230 self.name = unicode(mount_path) 231 self.sb = superblock 232 self.flags = flags 233 self.session = session
234
235 - def walk(self, recursive=False, unallocated=False):
236 """Yields Files for each file in this mountpoint.""" 237 238 if self.sb and self.sb.s_root.d_inode.type.S_IFDIR: 239 # Create a dummy file for the root of the filesystem to walk it. 240 root_file = File(mountpoint=self, 241 dentry=self.sb.s_root, 242 is_root=True, 243 session=self.session) 244 for sub_file in root_file.walk(recursive=recursive, 245 unallocated=unallocated): 246 yield sub_file
247 248 @utils.safe_property
249 - def fstype(self):
250 return self.sb.s_type.name.deref()
251
252 - def __eq__(self, other):
253 if isinstance(other, MountPoint): 254 return (self.sb == other.sb 255 and self.device == other.device 256 and self.name == other.name) 257 return False
258
259 260 -class FileName(object):
261 """An object to represent a filename.""" 262 MAX_DEPTH = 15 263
264 - def __init__(self, components=None, start_dentry=None):
265 self.start_dentry = start_dentry 266 self.mount_point = "/" 267 self.deleted = False 268 if components is None: 269 components = [] 270 self.path_components = components
271
272 - def PrependName(self, component):
273 self.path_components.insert(0, utils.SmartUnicode(component))
274
275 - def FormatName(self, root_dentry):
276 # For sockets we need more info. 277 if len(self.path_components) >= self.MAX_DEPTH: 278 return obj.NoneObject( 279 u"Depth exceeded at %s" % "/".join(self.path_components)) 280 281 if self.mount_point == "socket:": 282 return "{0}/{1}[{2}]".format( 283 self.mount_point, root_dentry.d_name.name.deref(), 284 self.start_dentry.d_inode.i_ino) 285 286 elif self.mount_point == "pipe:": 287 return "{0}[{1}]".format( 288 self.mount_point, self.start_dentry.d_inode.i_ino) 289 290 elif self.mount_point == "anon_inode:": 291 return u"anon_inode:%s" % self.start_dentry.d_name.name.deref() 292 293 # This is the normal condition for files. 294 else: 295 return re.sub("/+", "/", self.__unicode__())
296
297 - def __unicode__(self):
298 if self.deleted: 299 deleted = " (deleted) " 300 else: 301 deleted = "" 302 303 return u"%s%s%s" % (self.mount_point, 304 "/".join(self.path_components), deleted)
305
306 307 -class Linux3VFS(object):
308 """This is the implementation specific VFS operations. 309 310 Most of the code here is a direct copy of the methods in linux/fs/dcache.c 311 312 http://lxr.free-electrons.com/source/fs/mount.h?v=3.7#L53 313 http://lxr.free-electrons.com/source/fs/dcache.c?v=3.7#L2576 314 """ 315
316 - def __init__(self, profile=None):
317 # Autodetect kernel version 318 self.profile = profile 319 if self.profile.get_constant("set_mphash_entries"): 320 self.prepend_path = self._prepend_path314 321 elif self.profile.has_type("mount"): 322 self.prepend_path = self._prepend_path303 323 else: 324 self.prepend_path = self._prepend_path300
325
326 - def get_path(self, task, filp):
327 """Resolve the dentry, vfsmount relative to this task's chroot. 328 329 Returns: 330 An absolute path to the global filesystem mount. (I.e. we do not 331 truncate the path at the chroot point as the kernel does). 332 """ 333 return posixpath.normpath(self.prepend_path(filp.f_path, task.fs.root))
334
335 - def _real_mount(self, vfsmnt):
336 """Return the mount container of the vfsmnt object.""" 337 return basic.container_of(vfsmnt, "mount", "mnt").reference()
338
339 - def _mnt_has_parent(self, mnt):
340 return mnt != mnt.mnt_parent
341
342 - def _prepend_path300(self, path, root):
343 """Return the path of a dentry for 3.0-3.2 kernels. 344 345 http://lxr.free-electrons.com/source/fs/dcache.c?v=3.2#L2576 346 """ 347 # Ensure we can not get into an infinite loop here by limiting the 348 # depth. 349 depth = 0 350 dentry = path.dentry 351 vfsmnt = path.mnt 352 353 result = FileName(start_dentry=dentry) 354 355 # Check for deleted dentry. 356 if dentry.d_flags.DCACHE_UNHASHED and not dentry.is_root: 357 result.deleted = True 358 359 while dentry != root.dentry or vfsmnt != root.mnt: 360 # Control the depth. 361 depth += 1 362 if depth >= result.MAX_DEPTH: 363 break 364 365 if dentry == vfsmnt.mnt_root or dentry.is_root: 366 # Global root? 367 if vfsmnt.mnt_parent == vfsmnt: 368 result.PrependName("") 369 break 370 dentry = vfsmnt.mnt_mountpoint 371 vfsmnt = vfsmnt.mnt_parent 372 continue 373 374 parent = dentry.d_parent 375 if dentry.d_name.name: 376 result.PrependName(dentry.d_name.name.deref()) 377 dentry = parent 378 379 # When we get here dentry is a root dentry and mnt is the mount point it 380 # is mounted on. There are some special mount points we want to 381 # highlight. 382 result.mount_point = vfsmnt.mnt_mountpoint.d_name.name.deref() 383 384 return result.FormatName(dentry)
385
386 - def _prepend_path303(self, path, root):
387 """Return the path of a dentry for 3.3-3.13 kernels. 388 389 Linxu 3.3 introduced the struct mount, and moved some fields between 390 struct mount and struct vfsmount. 391 392 http://lxr.free-electrons.com/source/fs/dcache.c?v=3.7#L2576 393 """ 394 # Ensure we can not get into an infinite loop here by limiting the 395 # depth. 396 depth = 0 397 dentry = path.dentry 398 vfsmnt = path.mnt 399 mnt = self._real_mount(vfsmnt) 400 slash = False 401 402 result = FileName(start_dentry=dentry) 403 404 # Check for deleted dentry. 405 if dentry.d_flags.DCACHE_UNHASHED and not dentry.is_root: 406 result.deleted = True 407 408 while dentry != root.dentry or vfsmnt != root.mnt: 409 # Control the depth. 410 depth += 1 411 if depth >= result.MAX_DEPTH: 412 break 413 414 if dentry == vfsmnt.mnt_root or dentry.is_root: 415 # Global root? 416 if not self._mnt_has_parent(mnt): 417 if not slash: 418 result.PrependName("") 419 break 420 dentry = mnt.mnt_mountpoint 421 mnt = mnt.mnt_parent 422 vfsmnt = mnt.mnt 423 continue 424 425 parent = dentry.d_parent 426 if dentry.d_name.name: 427 result.PrependName(dentry.d_name.name.deref()) 428 slash = True 429 dentry = parent 430 431 # When we get here dentry is a root dentry and mnt is the mount point it 432 # is mounted on. There are some special mount points we want to 433 # highlight. 434 result.mount_point = mnt.mnt_mountpoint.d_name.name.deref() 435 436 return result.FormatName(dentry)
437
438 - def _prepend_path314(self, path, root):
439 """Return the path of a dentry for 3.14 kernels. 440 441 http://lxr.free-electrons.com/source/fs/dcache.c?v=3.14#L2867 442 """ 443 # Ensure we can not get into an infinite loop here by limiting the 444 # depth. 445 depth = 0 446 dentry = path.dentry 447 vfsmnt = path.mnt 448 mnt = self._real_mount(vfsmnt) 449 result = FileName(start_dentry=dentry) 450 451 # Check for deleted dentry. 452 if dentry.d_flags.DCACHE_UNHASHED and not dentry.is_root: 453 result.deleted = True 454 455 while dentry != root.dentry or vfsmnt != root.mnt: 456 # Control the depth. 457 depth += 1 458 if depth >= result.MAX_DEPTH: 459 break 460 461 if dentry == vfsmnt.mnt_root or dentry.is_root: 462 parent = mnt.mnt_parent 463 # Global root? 464 if mnt != parent: 465 dentry = mnt.mnt_mountpoint 466 mnt = mnt.mnt_parent 467 vfsmnt = mnt.mnt 468 continue 469 470 parent = dentry.d_parent 471 if dentry.d_name.name: 472 result.PrependName(dentry.d_name.name.deref()) 473 dentry = parent 474 475 # When we get here dentry is a root dentry and mnt is the mount point it 476 # is mounted on. There are some special mount points we want to 477 # highlight. 478 result.mount_point = mnt.mnt_mountpoint.d_name.name.deref() 479 480 return result.FormatName(dentry)
481
482 483 -class Linux26VFS(Linux3VFS):
484 """This is the implementation specific VFS operations. 485 486 Most of the code here is a direct copy of the methods in linux/fs/dcache.c 487 488 http://lxr.free-electrons.com/source/fs/dcache.c?v=2.6.26#L1782 489 """ 490
491 - def get_path(self, task, filp):
492 """Resolve the dentry, vfsmount relative to this task's chroot. 493 494 Returns: 495 An absolute path to the global filesystem mount. (I.e. we do not 496 truncate the path at the chroot point as the kernel does). 497 """ 498 root = task.fs.root.deref() 499 # For very old kernels (<=2.6.24) the root member is a dentry not a 500 # struct path, so we need to synthesize one. 501 if root.obj_type == "dentry": 502 root = task.obj_profile.path() 503 root.dentry = task.fs.root 504 root.mnt = task.fs.rootmnt 505 506 return self.__d_path(filp.f_path, root)
507
508 - def d_unhashed(self, dentry):
509 return dentry.d_flags.DCACHE_UNHASHED
510
511 - def __d_path(self, path, root):
512 """A literal copy of the __d_path function from kernel 2.6.26.""" 513 depth = 0 514 dentry = path.dentry 515 if dentry == None: 516 return "" 517 518 vfsmnt = path.mnt 519 520 result = FileName(start_dentry=dentry) 521 522 if not dentry.is_root and self.d_unhashed(dentry): 523 result.deleted = True 524 525 # Limit the recursion here to avoid getting stuck. 526 while depth < result.MAX_DEPTH: 527 if dentry == root.dentry and vfsmnt == root.mnt: 528 break 529 530 if dentry == vfsmnt.mnt_root or dentry.is_root: 531 if vfsmnt.mnt_parent == vfsmnt: 532 break 533 534 dentry = vfsmnt.mnt_mountpoint 535 vfsmnt = vfsmnt.mnt_parent 536 continue 537 538 parent = dentry.d_parent 539 result.PrependName(dentry.d_name.name.deref()) 540 541 dentry = parent 542 543 # When we get here dentry is a root dentry and mnt is the mount point it 544 # is mounted on. There are some special mount points we want to 545 # highlight. 546 result.mount_point = utils.SmartUnicode( 547 vfsmnt.mnt_mountpoint.d_name.name.deref()) 548 549 return result.FormatName(dentry)
550