mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-13 21:27:39 +02:00
Merge pull request #1335 from gcmoreira/linux_prot_none_L1TF_and_layer_addr_translation_fixes
Linux - PROT_NONE, L1TF mitigation and layer address translation fix
This commit is contained in:
@@ -67,14 +67,14 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
context, table_name, layer_name, progress_callback=progress_callback
|
||||
)
|
||||
|
||||
layer_class: Type = intel.Intel
|
||||
if "init_top_pgt" in table.symbols:
|
||||
layer_class = intel.Intel32e
|
||||
layer_class = intel.LinuxIntel32e
|
||||
dtb_symbol_name = "init_top_pgt"
|
||||
elif "init_level4_pgt" in table.symbols:
|
||||
layer_class = intel.Intel32e
|
||||
layer_class = intel.LinuxIntel32e
|
||||
dtb_symbol_name = "init_level4_pgt"
|
||||
else:
|
||||
layer_class = intel.LinuxIntel
|
||||
dtb_symbol_name = "swapper_pg_dir"
|
||||
|
||||
dtb = cls.virtual_to_physical_address(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
VERSION_MINOR = 11 # Number of changes that only add to the interface
|
||||
VERSION_MINOR = 12 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 0 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ class DataLayerInterface(
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the minimum valid address of the space."""
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def address_mask(self) -> int:
|
||||
"""Returns a mask which encapsulates all the active bits of an address
|
||||
for this layer."""
|
||||
|
||||
@@ -22,6 +22,16 @@ INTEL_TRANSLATION_DEBUGGING = False
|
||||
class Intel(linear.LinearlyMappedLayer):
|
||||
"""Translation Layer for the Intel IA32 memory mapping."""
|
||||
|
||||
_PAGE_BIT_PRESENT = 0
|
||||
_PAGE_BIT_PSE = 7 # Page Size Extension: 4 MB (or 2MB) page
|
||||
_PAGE_BIT_PROTNONE = 8
|
||||
_PAGE_BIT_PAT_LARGE = 12 # 2MB or 1GB pages
|
||||
|
||||
_PAGE_PRESENT = 1 << _PAGE_BIT_PRESENT
|
||||
_PAGE_PSE = 1 << _PAGE_BIT_PSE
|
||||
_PAGE_PROTNONE = 1 << _PAGE_BIT_PROTNONE
|
||||
_PAGE_PAT_LARGE = 1 << _PAGE_BIT_PAT_LARGE
|
||||
|
||||
_entry_format = "<I"
|
||||
_page_size_in_bits = 12
|
||||
_bits_per_register = 32
|
||||
@@ -163,12 +173,17 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
entry,
|
||||
f"Page Fault at entry {hex(entry)} in page entry",
|
||||
)
|
||||
page = self._mask(entry, self._maxphyaddr - 1, position + 1) | self._mask(
|
||||
offset, position, 0
|
||||
)
|
||||
|
||||
pfn = self._pte_pfn(entry)
|
||||
page_offset = self._mask(offset, position, 0)
|
||||
page = pfn << self.page_shift | page_offset
|
||||
|
||||
return page, 1 << (position + 1), self._base_layer
|
||||
|
||||
def _pte_pfn(self, entry: int) -> int:
|
||||
"""Extracts the page frame number (PFN) from the page table entry (PTE) entry"""
|
||||
return entry >> self.page_shift
|
||||
|
||||
def _translate_entry(self, offset: int) -> Tuple[int, int]:
|
||||
"""Translates a specific offset based on paging tables.
|
||||
|
||||
@@ -203,10 +218,10 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
"Page Fault at entry " + hex(entry) + " in table " + name,
|
||||
)
|
||||
# Check if we're a large page
|
||||
if large_page and (entry & (1 << 7)):
|
||||
if large_page and (entry & self._PAGE_PSE):
|
||||
# Mask off the PAT bit
|
||||
if entry & (1 << 12):
|
||||
entry -= 1 << 12
|
||||
if entry & self._PAGE_PAT_LARGE:
|
||||
entry -= self._PAGE_PAT_LARGE
|
||||
# We're a large page, the rest is finished below
|
||||
# If we want to implement PSE-36, it would need to be done here
|
||||
break
|
||||
@@ -252,7 +267,7 @@ class Intel(linear.LinearlyMappedLayer):
|
||||
|
||||
return entry, position
|
||||
|
||||
@functools.lru_cache(1025)
|
||||
@functools.lru_cache(maxsize=1025)
|
||||
def _get_valid_table(self, base_address: int) -> Optional[bytes]:
|
||||
"""Extracts the table, validates it and returns it if it's valid."""
|
||||
table = self._context.layers.read(
|
||||
@@ -501,3 +516,85 @@ class WindowsIntel32e(WindowsMixin, Intel32e):
|
||||
|
||||
def _translate(self, offset: int) -> Tuple[int, int, str]:
|
||||
return self._translate_swap(self, offset, self._bits_per_register // 2)
|
||||
|
||||
|
||||
class LinuxMixin(Intel):
|
||||
@functools.cached_property
|
||||
def _register_mask(self) -> int:
|
||||
return (1 << self._bits_per_register) - 1
|
||||
|
||||
@functools.cached_property
|
||||
def _physical_mask(self) -> int:
|
||||
# From kernels 4.18 the physical mask is dynamic: See AMD SME, Intel Multi-Key Total
|
||||
# Memory Encryption and CONFIG_DYNAMIC_PHYSICAL_MASK: 94d49eb30e854c84d1319095b5dd0405a7da9362
|
||||
physical_mask = (1 << self._maxphyaddr) - 1
|
||||
# TODO: Come back once SME support is available in the framework
|
||||
return physical_mask
|
||||
|
||||
@functools.cached_property
|
||||
def page_mask(self) -> int:
|
||||
# Note that within the Intel class it's a class method. However, since it uses
|
||||
# complement operations and we are working in Python, it would be more careful to
|
||||
# limit it to the architecture's pointer size.
|
||||
return ~(self.page_size - 1) & self._register_mask
|
||||
|
||||
@functools.cached_property
|
||||
def _physical_page_mask(self) -> int:
|
||||
return self.page_mask & self._physical_mask
|
||||
|
||||
@functools.cached_property
|
||||
def _pte_pfn_mask(self) -> int:
|
||||
return self._physical_page_mask
|
||||
|
||||
@functools.cached_property
|
||||
def _pte_flags_mask(self) -> int:
|
||||
return ~self._pte_pfn_mask & self._register_mask
|
||||
|
||||
def _pte_flags(self, pte) -> int:
|
||||
return pte & self._pte_flags_mask
|
||||
|
||||
def _is_pte_present(self, entry: int) -> bool:
|
||||
return (
|
||||
self._pte_flags(entry) & (self._PAGE_PRESENT | self._PAGE_PROTNONE)
|
||||
) != 0
|
||||
|
||||
def _page_is_valid(self, entry: int) -> bool:
|
||||
# Overrides the Intel static method with the Linux-specific implementation
|
||||
return self._is_pte_present(entry)
|
||||
|
||||
def _pte_needs_invert(self, entry) -> bool:
|
||||
# Entries that were set to PROT_NONE (PAGE_PRESENT) are inverted
|
||||
# A clear PTE shouldn't be inverted. See f19f5c4
|
||||
return entry and not (entry & self._PAGE_PRESENT)
|
||||
|
||||
def _protnone_mask(self, entry: int) -> int:
|
||||
"""Gets a mask to XOR with the page table entry to get the correct PFN"""
|
||||
return self._register_mask if self._pte_needs_invert(entry) else 0
|
||||
|
||||
def _pte_pfn(self, entry: int) -> int:
|
||||
"""Extracts the page frame number from the page table entry"""
|
||||
pfn = entry ^ self._protnone_mask(entry)
|
||||
return (pfn & self._pte_pfn_mask) >> self.page_shift
|
||||
|
||||
|
||||
class LinuxIntel(LinuxMixin, Intel):
|
||||
pass
|
||||
|
||||
|
||||
class LinuxIntelPAE(LinuxMixin, IntelPAE):
|
||||
pass
|
||||
|
||||
|
||||
class LinuxIntel32e(LinuxMixin, Intel32e):
|
||||
# In the Linux kernel, the __PHYSICAL_MASK_SHIFT is a mask used to extract the
|
||||
# physical address from a PTE. In Volatility3, this is referred to as _maxphyaddr.
|
||||
#
|
||||
# Until kernel version 4.17, Linux x86-64 used a 46-bit mask. With commit
|
||||
# b83ce5ee91471d19c403ff91227204fb37c95fb2, this was extended to 52 bits,
|
||||
# applying to both 4 and 5-level page tables.
|
||||
#
|
||||
# We initially used 52 bits for all Intel 64-bit systems, but this produced incorrect
|
||||
# results for PROT_NONE pages. Since the mask value is defined by a preprocessor macro,
|
||||
# it's difficult to detect the exact bit shift used in the current kernel.
|
||||
# Using 46 bits has proven reliable for our use case, as seen in tools like crashtool.
|
||||
_maxphyaddr = 46
|
||||
|
||||
Reference in New Issue
Block a user