Linux: Add inode, timespec, and timespec64 object extensions to support different kernel versions, ensuring we will get aware datetimes when using them.

This commit is contained in:
Gustavo Moreira
2024-08-01 12:23:09 +10:00
parent 16cde44400
commit 3017a7d00c
2 changed files with 138 additions and 0 deletions
@@ -29,12 +29,18 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("files_struct", extensions.files_struct)
self.set_type_class("kobject", extensions.kobject)
self.set_type_class("cred", extensions.cred)
self.set_type_class("inode", extensions.inode)
# Might not exist in the current symbols
self.optional_set_type_class("module", extensions.module)
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t)
# kernels >= 4.18
self.optional_set_type_class("timespec64", extensions.timespec64)
# kernels < 4.18. Reuses timespec64 obj extension, since both has the same members
self.optional_set_type_class("timespec", extensions.timespec64)
# Mount
self.set_type_class("vfsmount", extensions.vfsmount)
# Might not exist in older kernels or the current symbols
@@ -4,10 +4,13 @@
import collections.abc
import logging
import stat
from datetime import datetime
import socket as socket_module
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
from volatility3.framework import renderers
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
@@ -1761,3 +1764,132 @@ class kernel_cap_t(kernel_cap_struct):
)
return cap_value & self.get_kernel_cap_full()
class timespec64(objects.StructType):
def to_datetime(self) -> datetime:
"""Returns the respective aware datetime"""
dt = renderers.conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9)
return dt
class inode(objects.StructType):
def is_valid(self) -> bool:
# i_count is a 'signed' counter (atomic_t). Smear, or essentially a wrong inode
# pointer, will easily cause an integer overflow here.
return self.i_ino > 0 and self.i_count.counter >= 0
def is_dir(self) -> bool:
"""Returns True if the inode is a directory"""
return stat.S_ISDIR(self.i_mode) != 0
def is_reg(self) -> bool:
"""Returns True if the inode is a regular file"""
return stat.S_ISREG(self.i_mode) != 0
def is_link(self) -> bool:
"""Returns True if the inode is a symlink"""
return stat.S_ISLNK(self.i_mode) != 0
def is_fifo(self) -> bool:
"""Returns True if the inode is a FIFO"""
return stat.S_ISFIFO(self.i_mode) != 0
def is_sock(self) -> bool:
"""Returns True if the inode is a socket"""
return stat.S_ISSOCK(self.i_mode) != 0
def is_block(self) -> bool:
"""Returns True if the inode is a block device"""
return stat.S_ISBLK(self.i_mode) != 0
def is_char(self) -> bool:
"""Returns True if the inode is a char device"""
return stat.S_ISCHR(self.i_mode) != 0
def is_sticky(self) -> bool:
"""Returns True if the sticky bit is set"""
return (self.i_mode & stat.S_ISVTX) != 0
def get_inode_type(self) -> str:
"""Returns inode type name
Returns:
The inode type name
"""
if self.is_dir():
return "DIR"
elif self.is_reg():
return "REG"
elif self.is_link():
return "LNK"
elif self.is_fifo():
return "FIFO"
elif self.is_sock():
return "SOCK"
elif self.is_char():
return "CHR"
elif self.is_block():
return "BLK"
else:
return renderers.UnparsableValue()
def get_inode_number(self) -> int:
"""Returns the inode number"""
return int(self.i_ino)
def ___time_member_to_datetime(self, member) -> datetime:
if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"):
# kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32
# Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853
return renderers.conversion.unixtime_to_datetime(
self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9
)
elif self.has_member(f"__{member}"):
# 6.6 <= kernels < 6.11 it's a timespec64
# Ref Linux commit 13bc24457850583a2e7203ded05b7209ab4bc5ef / 12cd44023651666bd44baa36a5c999698890debb
return self.member(f"__{member}").to_datetime()
elif self.has_member(member):
# In kernels < 6.6 it's a timespec64 or timespec
return self.member(member).to_datetime()
else:
raise exceptions.VolatilityException(
"Unsupported kernel inode type implementation"
)
def get_access_time(self) -> datetime:
"""Returns the inode's last access time
This is updated when inode contents are read
Returns:
A datetime with the inode's last access time
"""
return self.___time_member_to_datetime("i_atime")
def get_modification_time(self) -> datetime:
"""Returns the inode's last modification time
This is updated when the inode contents change
Returns:
A datetime with the inode's last data modification time
"""
return self.___time_member_to_datetime("i_mtime")
def get_change_time(self) -> datetime:
"""Returns the inode's last change time
This is updated when the inode metadata changes
Returns:
A datetime with the inode's last change time
"""
return self.___time_member_to_datetime("i_ctime")
def get_file_mode(self) -> str:
"""Returns the inode's file mode as string of the form '-rwxrwxrwx'.
Returns:
The inode's file mode string
"""
return stat.filemode(self.i_mode)