Skip to content

Commit

Permalink
add superblock or device numbers path prepending
Browse files Browse the repository at this point in the history
  • Loading branch information
Abyss-W4tcher committed Feb 11, 2025
1 parent f82fd53 commit 999f3d0
Showing 1 changed file with 39 additions and 16 deletions.
55 changes: 39 additions & 16 deletions volatility3/framework/plugins/linux/pagecache.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,11 +633,8 @@ def run(self):
class RecoverFs(plugins.PluginInterface):
"""Recovers the cached filesystem (directories, files, symlinks) into a compressed tarball.
Metadata aren't replicated to extracted objects and timestamps are set to the plugin run time. To prevent extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount.
To mount:
"ratarmount recovered_fs.tar.gz ./recovered_fs_mounted/".
To unmount:
"umount ./recovered_fs_mounted/".
Details: level 0 directories are named after the UUID of the parent superblock; metadata aren't replicated to extracted objects; objects modification time is set to the plugin run time.
Troubleshooting: to fix extraction errors related to long paths, please consider using https://github.com/mxmlnkn/ratarmount.
"""

_version = (1, 0, 0)
Expand Down Expand Up @@ -673,6 +670,7 @@ def _tar_add_reg_inode(
layer_name: str,
tar: tarfile.TarFile,
reg_inode_in: InodeInternal,
path_prefix: str = "",
mtime: float = None,
) -> int:
"""Extracts a REG inode content and writes it to a TarFile object.
Expand All @@ -682,6 +680,7 @@ def _tar_add_reg_inode(
layer_name: The name of the layer on which to operate
tar: The TarFile object to write to
reg_inode_in: The inode to extract content from
path_prefix: A custom path prefix to prepend the inode path with
mtime: The modification time to set the TarInfo object to
Returns:
Expand All @@ -694,7 +693,7 @@ def _tar_add_reg_inode(
inode_content_buffer.seek(0)
handle_buffer_size = inode_content_buffer.getbuffer().nbytes

tar_info = tarfile.TarInfo(reg_inode_in.path)
tar_info = tarfile.TarInfo(path_prefix + reg_inode_in.path)
# The tarfile module only has read support for sparse files:
# https://docs.python.org/3.12/library/tarfile.html#tarfile.LNKTYPE:~:text=and%20longlink%20extensions%2C-,read%2Donly%20support,-for%20all%20variants
tar_info.type = tarfile.REGTYPE
Expand All @@ -707,20 +706,20 @@ def _tar_add_reg_inode(
return handle_buffer_size

@classmethod
def _tar_add_dir_inode(
def _tar_add_dir(
cls,
tar: tarfile.TarFile,
reg_dir_in: InodeInternal,
directory_path: str,
mtime: float = None,
) -> None:
"""Adds a directory path to a TarFile object, based on a DIR inode.
Args:
tar: The TarFile object to write to
reg_dir_in: The inode to base the new directory on
directory_path: The directory path to create
mtime: The modification time to set the TarInfo object to
"""
tar_info = tarfile.TarInfo(reg_dir_in.path)
tar_info = tarfile.TarInfo(directory_path)
tar_info.type = tarfile.DIRTYPE
tar_info.mode = 0o755
if mtime is not None:
Expand Down Expand Up @@ -774,22 +773,46 @@ def _generator(self):
vmlinux_module_name=vmlinux_module_name,
follow_symlinks=False,
)
visited_paths = set()

# Prefix paths by the super_block uuid's to prevent overlaps.
# Switch to device major and device minor for older kernels (< 2.6.39-rc1).
uuid_as_prefix = vmlinux.get_type("super_block").has_member("s_uuid")
if not uuid_as_prefix:
vollog.warning(
"super_block struct does not support s_uuid attribute. Consequently, level 0 directories won't refer to the superblock uuid's, but to its device_major:device_minor numbers."
)

visited_paths = seen_prefixes = set()
for inode_in in inodes_iter:
if inode_in.path in visited_paths:
if uuid_as_prefix:
prefix = f"/{inode_in.superblock.uuid}"
else:
prefix = f"/{inode_in.superblock.major}:{inode_in.superblock.minor}"
prefixed_path = prefix + inode_in.path

if prefixed_path in visited_paths:
continue
visited_paths.add(inode_in.path)
elif prefix not in seen_prefixes:
self._tar_add_dir(tar, prefix, mtime)
seen_prefixes.add(prefix)

visited_paths.add(prefixed_path)
extracted_file_size = renderers.NotApplicableValue()

# Inodes parent directory is yielded first, which
# ensures that a file parent path will exist beforehand.
# tarfile will take care of creating it anyway.
if inode_in.inode.is_reg:
extracted_file_size = self._tar_add_reg_inode(
self.context, vmlinux_layer.name, tar, inode_in, mtime
self.context,
vmlinux_layer.name,
tar,
inode_in,
prefix,
mtime,
)
elif inode_in.inode.is_dir:
self._tar_add_dir_inode(tar, inode_in, mtime)
self._tar_add_dir(tar, prefixed_path, mtime)
elif (
inode_in.inode.is_link
and inode_in.inode.has_member("i_link")
Expand All @@ -799,7 +822,7 @@ def _generator(self):
symlink_dest = inode_in.inode.i_link.dereference().cast(
"string", max_length=255, encoding="utf-8", errors="replace"
)
self._tar_add_lnk(tar, inode_in.path, symlink_dest, mtime)
self._tar_add_lnk(tar, prefixed_path, symlink_dest, mtime)
# Set path to a user friendly representation before yielding
inode_in.path = InodeUser.format_symlink(inode_in.path, symlink_dest)
else:
Expand Down

0 comments on commit 999f3d0

Please sign in to comment.