Skip to content

Commit eb757b4

Browse files
authored
Merge pull request #1230 from gcmoreira/linux_inode_timespec_timespec64_obj_extensions
Linux: Add inode, timespec, and timespec64 object extensions
2 parents af5a743 + ed20834 commit eb757b4

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

volatility3/framework/symbols/linux/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,18 @@ def __init__(self, *args, **kwargs) -> None:
2929
self.set_type_class("files_struct", extensions.files_struct)
3030
self.set_type_class("kobject", extensions.kobject)
3131
self.set_type_class("cred", extensions.cred)
32+
self.set_type_class("inode", extensions.inode)
3233
# Might not exist in the current symbols
3334
self.optional_set_type_class("module", extensions.module)
3435
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
3536
self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
3637
self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t)
3738

39+
# kernels >= 4.18
40+
self.optional_set_type_class("timespec64", extensions.timespec64)
41+
# kernels < 4.18. Reuses timespec64 obj extension, since both has the same members
42+
self.optional_set_type_class("timespec", extensions.timespec64)
43+
3844
# Mount
3945
self.set_type_class("vfsmount", extensions.vfsmount)
4046
# Might not exist in older kernels or the current symbols

volatility3/framework/symbols/linux/extensions/__init__.py

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
import collections.abc
66
import logging
7+
import stat
8+
from datetime import datetime
79
import socket as socket_module
8-
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
10+
from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union
911

1012
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
13+
from volatility3.framework.renderers import conversion
1114
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
1215
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
1316
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
@@ -1761,3 +1764,136 @@ def get_capabilities(self) -> int:
17611764
)
17621765

17631766
return cap_value & self.get_kernel_cap_full()
1767+
1768+
1769+
class timespec64(objects.StructType):
1770+
def to_datetime(self) -> datetime:
1771+
"""Returns the respective aware datetime"""
1772+
1773+
dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9)
1774+
return dt
1775+
1776+
1777+
class inode(objects.StructType):
1778+
def is_valid(self) -> bool:
1779+
# i_count is a 'signed' counter (atomic_t). Smear, or essentially a wrong inode
1780+
# pointer, will easily cause an integer overflow here.
1781+
return self.i_ino > 0 and self.i_count.counter >= 0
1782+
1783+
@property
1784+
def is_dir(self) -> bool:
1785+
"""Returns True if the inode is a directory"""
1786+
return stat.S_ISDIR(self.i_mode) != 0
1787+
1788+
@property
1789+
def is_reg(self) -> bool:
1790+
"""Returns True if the inode is a regular file"""
1791+
return stat.S_ISREG(self.i_mode) != 0
1792+
1793+
@property
1794+
def is_link(self) -> bool:
1795+
"""Returns True if the inode is a symlink"""
1796+
return stat.S_ISLNK(self.i_mode) != 0
1797+
1798+
@property
1799+
def is_fifo(self) -> bool:
1800+
"""Returns True if the inode is a FIFO"""
1801+
return stat.S_ISFIFO(self.i_mode) != 0
1802+
1803+
@property
1804+
def is_sock(self) -> bool:
1805+
"""Returns True if the inode is a socket"""
1806+
return stat.S_ISSOCK(self.i_mode) != 0
1807+
1808+
@property
1809+
def is_block(self) -> bool:
1810+
"""Returns True if the inode is a block device"""
1811+
return stat.S_ISBLK(self.i_mode) != 0
1812+
1813+
@property
1814+
def is_char(self) -> bool:
1815+
"""Returns True if the inode is a char device"""
1816+
return stat.S_ISCHR(self.i_mode) != 0
1817+
1818+
@property
1819+
def is_sticky(self) -> bool:
1820+
"""Returns True if the sticky bit is set"""
1821+
return (self.i_mode & stat.S_ISVTX) != 0
1822+
1823+
def get_inode_type(self) -> Union[str, None]:
1824+
"""Returns inode type name
1825+
1826+
Returns:
1827+
The inode type name
1828+
"""
1829+
if self.is_dir:
1830+
return "DIR"
1831+
elif self.is_reg:
1832+
return "REG"
1833+
elif self.is_link:
1834+
return "LNK"
1835+
elif self.is_fifo:
1836+
return "FIFO"
1837+
elif self.is_sock:
1838+
return "SOCK"
1839+
elif self.is_char:
1840+
return "CHR"
1841+
elif self.is_block:
1842+
return "BLK"
1843+
else:
1844+
return None
1845+
1846+
def _time_member_to_datetime(self, member) -> datetime:
1847+
if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"):
1848+
# kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32
1849+
# Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853
1850+
return conversion.unixtime_to_datetime(
1851+
self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9
1852+
)
1853+
elif self.has_member(f"__{member}"):
1854+
# 6.6 <= kernels < 6.11 it's a timespec64
1855+
# Ref Linux commit 13bc24457850583a2e7203ded05b7209ab4bc5ef / 12cd44023651666bd44baa36a5c999698890debb
1856+
return self.member(f"__{member}").to_datetime()
1857+
elif self.has_member(member):
1858+
# In kernels < 6.6 it's a timespec64 or timespec
1859+
return self.member(member).to_datetime()
1860+
else:
1861+
raise exceptions.VolatilityException(
1862+
"Unsupported kernel inode type implementation"
1863+
)
1864+
1865+
def get_access_time(self) -> datetime:
1866+
"""Returns the inode's last access time
1867+
This is updated when inode contents are read
1868+
1869+
Returns:
1870+
A datetime with the inode's last access time
1871+
"""
1872+
return self._time_member_to_datetime("i_atime")
1873+
1874+
def get_modification_time(self) -> datetime:
1875+
"""Returns the inode's last modification time
1876+
This is updated when the inode contents change
1877+
1878+
Returns:
1879+
A datetime with the inode's last data modification time
1880+
"""
1881+
1882+
return self._time_member_to_datetime("i_mtime")
1883+
1884+
def get_change_time(self) -> datetime:
1885+
"""Returns the inode's last change time
1886+
This is updated when the inode metadata changes
1887+
1888+
Returns:
1889+
A datetime with the inode's last change time
1890+
"""
1891+
return self._time_member_to_datetime("i_ctime")
1892+
1893+
def get_file_mode(self) -> str:
1894+
"""Returns the inode's file mode as string of the form '-rwxrwxrwx'.
1895+
1896+
Returns:
1897+
The inode's file mode string
1898+
"""
1899+
return stat.filemode(self.i_mode)

0 commit comments

Comments
 (0)