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:
ikelos
2024-11-27 10:31:18 +00:00
committed by GitHub
4 changed files with 109 additions and 12 deletions
+3 -3
View File
@@ -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 -1
View File
@@ -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 = ""
+1 -1
View File
@@ -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."""
+104 -7
View File
@@ -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