Fix bugs in kallsyms and the related pscallstack found in testing and switch calls to deprecated functions

This commit is contained in:
Andrew Case
2025-03-17 17:08:27 -05:00
parent ed3bcf1f5f
commit b8a427c130
4 changed files with 137 additions and 56 deletions
@@ -106,6 +106,8 @@ class Kallsyms(plugins.PluginInterface):
for symbols_generator in symbol_generators:
for kassymbol in symbols_generator:
if not kassymbol:
continue
# Symbol sizes are calculated using the address of the next non-aliased
# symbol or the end of the kernel text area _end/_etext. However, some kernel
# symbols are located beyond that area, which causes this method to fail for
@@ -118,9 +118,15 @@ class PsCallStack(plugins.PluginInterface):
current_sp = rsp_start
idx = 0
while current_sp < task_top_of_stack:
stack_value_bytes = task_layer.read(current_sp, pointer_size)
try:
stack_value_bytes = task_layer.read(current_sp, pointer_size)
except exceptions.InvalidAddressException:
break
stack_value = int.from_bytes(stack_value_bytes, byteorder=byte_order)
if not stack_value:
idx += 1
current_sp += pointer_size
continue
kassymbol = kas.lookup_address(stack_value)
sp_address = current_sp & vmlinux_layer.address_mask
stack_value &= vmlinux_layer.address_mask
@@ -1994,7 +1994,10 @@ class bpf_prog(objects.StructType):
# 'prog_aux' was added in kernels 3.18
return None
return self.aux.get_name()
try:
return self.aux.get_name()
except exceptions.InvalidAddressException:
return None
def bpf_jit_binary_hdr_address(self) -> int:
"""Return the jitted BPF program start address
@@ -2056,11 +2059,13 @@ class bpf_prog_aux(objects.StructType):
# 'name' was added in kernels 4.15
return None
if not self.name:
try:
if not self.name:
return None
return utility.array_to_string(self.name)
except exceptions.InvalidAddressException:
return None
return utility.array_to_string(self.name)
class cred(objects.StructType):
# struct cred was added in kernels 2.6.29
@@ -2996,7 +3001,9 @@ class latch_tree_root(objects.StructType):
rb_node = rb_node_ptr.dereference()
lt_node = self._get_lt_node_from_rb_node(rb_node, idx)
c = comp_function(key, lt_node)
if c < 0:
if c is None:
return None
elif c < 0:
rb_node_ptr = rb_node.rb_left
elif c > 0:
rb_node_ptr = rb_node.rb_right
+115 -49
View File
@@ -6,12 +6,12 @@ import functools
import logging
from typing import Iterator, List, Optional, Tuple
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import constants, exceptions, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.constants import linux as linux_constants
from volatility3.framework.objects import utility
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import lsmod
vollog = logging.getLogger(__name__)
@@ -304,28 +304,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
@classmethod
def _assert_versions(cls) -> None:
"""Verify versions of shared dependencies"""
lsmod_version_required = (2, 0, 0)
linux_utilities_modules_version_required = (3, 0, 0)
if not requirements.VersionRequirement.matches_required(
lsmod_version_required, lsmod.Lsmod.version
linux_utilities_modules_version_required,
linux_utilities_modules.Modules.version,
):
raise exceptions.VolatilityException(
"Lsmod version not suitable: "
f"required {lsmod_version_required} found {lsmod.Lsmod.version}",
"linux_utilities_modules.Modules version not suitable: "
f"required {linux_utilities_modules_version_required} found {linux_utilities_modules.Modules.version}",
)
return None
def _read_bytes(self, address: int, size: int) -> bytes:
def _read_bytes(self, address: int, size: int) -> Optional[bytes]:
layer = self._context.layers[self._layer_name]
return layer.read(address, size).decode()
try:
return layer.read(address, size).decode()
except exceptions.InvalidAddressException:
return None
def _read_int(self, address: int, size: int, signed: bool = False) -> int:
def _read_int(self, address: int, size: int, signed: bool = False) -> Optional[int]:
layer = self._context.layers[self._layer_name]
return int.from_bytes(
layer.read(address, size),
byteorder=self._endian,
signed=signed,
)
try:
return int.from_bytes(
layer.read(address, size),
byteorder=self._endian,
signed=signed,
)
except exceptions.InvalidAddressException:
return None
def _bootstrap(self) -> None:
layer = self._context.layers[self._layer_name]
@@ -402,7 +409,20 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
"""
current_offset = 0
for sym_idx in range(self._kallsyms_num_syms):
kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx)
try:
kassymbol, compressed_length = self._get_symbol(current_offset, sym_idx)
except exceptions.InvalidAddressException:
vollog.debug(
f"Unable to reconstruct core symbol at offset {current_offset:#x} and index {sym_idx}"
)
continue
if compressed_length is None:
vollog.debug(
f"Unable to reconstruct compressed_length at offset {current_offset:#x} and index {sym_idx}"
)
break
if kassymbol:
yield kassymbol
@@ -485,7 +505,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
)
return kassymbolbasic, compressed_length
def _get_symbol_address_by_index(self, index: int) -> int:
def _get_symbol_address_by_index(self, index: int) -> Optional[int]:
"""Return symbol address based on the symbol index in the kallsyms arrays.
Based on kallsyms_sym_address()
@@ -502,6 +522,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
signed_int_size = 4
sym_offset_ptr = self._kallsyms_offsets_address + (index * signed_int_size)
sym_addr = self._read_int(sym_offset_ptr, signed_int_size, signed=True)
if sym_addr is None:
return None
if sym_addr < 0:
# Negative offsets are relative to kallsyms_relative_base - 1
@@ -517,35 +539,56 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
self._long_size,
signed=False,
)
if kallsyms_address is None:
return None
return kallsyms_address & layer.address_mask
else:
raise exceptions.VolatilityException("Unsupported kernel")
@functools.lru_cache
def _get_symbol_pos(self, address: int) -> Tuple[int, int]:
def _get_symbol_pos(self, address: int) -> Optional[Tuple[int, int]]:
"""Returns the symbol position in the kallsyms arrays and its size."""
low = 0
high = self._kallsyms_num_syms
while high - low > 1:
mid = low + (high - low) // 2
if self._get_symbol_address_by_index(mid) <= address:
symbol_index = self._get_symbol_address_by_index(mid)
if symbol_index is None:
return None, None
elif symbol_index <= address:
low = mid
else:
high = mid
# prevent accidental bleed through
symbol_index = None
# Search for the first aliased symbol. *Aliased symbols* are symbols with the same address.
while low and self._get_symbol_address_by_index(
low - 1
) == self._get_symbol_address_by_index(low):
low -= 1
while low:
symbol_index = self._get_symbol_address_by_index(low - 1)
if symbol_index is None:
return None, None
if symbol_index == self._get_symbol_address_by_index(low):
low -= 1
else:
break
symbol_start = self._get_symbol_address_by_index(low)
if symbol_start is None:
return None, None
symbol_end = 0
# Search for next non-aliased symbol.
for idx in range(low + 1, self._kallsyms_num_syms):
if self._get_symbol_address_by_index(idx) > symbol_start:
symbol_index = self._get_symbol_address_by_index(idx)
if symbol_index is None:
return None, None
if symbol_index > symbol_start:
symbol_end = self._get_symbol_address_by_index(idx)
break
@@ -664,6 +707,8 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
return None
pos, sym_size = self._get_symbol_pos(address)
if pos is None:
return None
offset = self._get_symbol_offset(pos)
sym_address = self._get_symbol_address_by_index(pos)
kassymbolbasic, _compressed_length = self._expand_symbol(offset)
@@ -855,7 +900,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
self,
) -> List[Tuple[interfaces.objects.ObjectInterface, int, int]]:
modules_region = []
for module in lsmod.Lsmod.list_modules(self._context, self._module_name):
for module in linux_utilities_modules.Modules.list_modules(
self._context, self._module_name
):
minimum_address, maximum_address = module.get_module_address_boundaries()
module_region = module, minimum_address, maximum_address
modules_region.append(module_region)
@@ -923,21 +970,35 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
return self._search_module_by_address(address)
@functools.lru_cache
def _get_type_cache(self, name: str):
def _get_type_cache(self, name: str) -> Optional[interfaces.objects.Template]:
vmlinux = self._context.modules[self._module_name]
return vmlinux.get_type(name)
try:
return vmlinux.get_type(name)
except exceptions.SymbolError:
return None
def _mod_tree_comp(
self, address: int, latch_tree_node: interfaces.objects.ObjectInterface
) -> int:
) -> Optional[int]:
vmlinux = self._context.modules[self._module_name]
module_memory_mtn_offset = self._get_type_cache(
"module_memory"
).relative_child_offset("mtn")
mod_tree_node_mod_offset = self._get_type_cache(
"mod_tree_node"
).relative_child_offset("mod")
module_memory_mtn = self._get_type_cache("module_memory")
if not module_memory_mtn:
vollog.debug(
"`module_memory` symbol not present in the symbol table. Cannot proceed."
)
return None
module_memory_mtn_offset = module_memory_mtn.relative_child_offset("mtn")
mod_tree_node_mod = self._get_type_cache("mod_tree_node")
if not mod_tree_node_mod:
vollog.debug(
"`mod_tree_node` symbol not present in the symbol table. Cannot proceed."
)
return None
mod_tree_node_mod_offset = mod_tree_node_mod.relative_child_offset("mod")
module_memory_offset = (
latch_tree_node.vol.offset
@@ -1087,7 +1148,9 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
KASSymbol objects
"""
layer = self._context.layers[self._layer_name]
for module in lsmod.Lsmod.list_modules(self._context, self._module_name):
for module in linux_utilities_modules.Modules.list_modules(
self._context, self._module_name
):
module_name = utility.array_to_string(module.name)
for elf_sym_idx, elf_sym_obj in enumerate(module.get_symbols()):
sym_name = elf_sym_obj.get_name()
@@ -1254,21 +1317,24 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
# this function will still be able to gather the symbols.
bpf_kallsyms_list = vmlinux.object_from_symbol("bpf_kallsyms")
for elem in bpf_kallsyms_list.to_list(list_type_symname, list_head_member):
# See kernel's bpf_get_kallsym()
if list_type == "bpf_ksym":
# kernels >= 5.8
bpf_ksym = elem
sym_name = utility.array_to_string(bpf_ksym.name)
sym_addr = bpf_ksym.start
sym_size = bpf_ksym.end - bpf_ksym.start
else:
# list_type == "bpf_prog_aux" 3.18 <= kernels < 5.8
bpf_prog_aux = elem
bpf_prog = bpf_prog_aux.prog
sym_name = bpf_prog.get_name()
sym_addr = bpf_prog.bpf_func
sym_start, sym_end = bpf_prog.get_address_region()
sym_size = sym_end - sym_start
try:
# See kernel's bpf_get_kallsym()
if list_type == "bpf_ksym":
# kernels >= 5.8
bpf_ksym = elem
sym_name = utility.array_to_string(bpf_ksym.name)
sym_addr = bpf_ksym.start
sym_size = bpf_ksym.end - bpf_ksym.start
else:
# list_type == "bpf_prog_aux" 3.18 <= kernels < 5.8
bpf_prog_aux = elem
bpf_prog = bpf_prog_aux.prog
sym_name = bpf_prog.get_name()
sym_addr = bpf_prog.bpf_func
sym_start, sym_end = bpf_prog.get_address_region()
sym_size = sym_end - sym_start
except exceptions.InvalidAddressException:
continue
# The following are also hardcoded in the Linux kernel
# see kernel's get_ksymbol_bpf(), bpf_get_kallsym() and BPF_SYM_ELF_TYPE
@@ -1322,7 +1388,7 @@ class Kallsyms(interfaces.configuration.VersionableInterface):
sym_size = symbol_end - symbol_start
elif vmlinux.has_type("latch_tree_root") and vmlinux.get_type(
"bpf_prog_aux"
).child_template("ksym_tnode"):
).has_member("ksym_tnode"):
# For 4.11 <= kernels < 5.7
# latch_tree_root was added in kernels 4.2 ade3f510f93a5613b672febe88eff8ea7f1c63b7
# BPF kallsyms support was added in kernels 4.11 74451e66d516c55e309e8d89a4a1e7596e46aacd