Linux: Add support for PROT_NONE, Intel Side Channel Vulnerability L1TF changes and fix _maxphyaddr in x86-64

This commit is contained in:
Gustavo Moreira
2024-11-07 20:31:20 +11:00
parent 9ae7c2bb10
commit 73d4f2fc88
3 changed files with 105 additions and 9 deletions
+3 -3
View File
@@ -80,14 +80,14 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
context, table_name, layer_name, progress_callback=progress_callback context, table_name, layer_name, progress_callback=progress_callback
) )
layer_class: Type = intel.Intel
if "init_top_pgt" in table.symbols: if "init_top_pgt" in table.symbols:
layer_class = intel.Intel32e layer_class = intel.LinuxIntel32e
dtb_symbol_name = "init_top_pgt" dtb_symbol_name = "init_top_pgt"
elif "init_level4_pgt" in table.symbols: elif "init_level4_pgt" in table.symbols:
layer_class = intel.Intel32e layer_class = intel.LinuxIntel32e
dtb_symbol_name = "init_level4_pgt" dtb_symbol_name = "init_level4_pgt"
else: else:
layer_class = intel.LinuxIntel
dtb_symbol_name = "swapper_pg_dir" dtb_symbol_name = "swapper_pg_dir"
dtb = cls.virtual_to_physical_address( dtb = cls.virtual_to_physical_address(
@@ -11,6 +11,14 @@ KERNEL_NAME = "__kernel__"
"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)""" """The value hard coded from the Linux Kernel (hence not extracted from the layer itself)"""
# Translation Layer constants
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_PROTNONE = 1 << PAGE_BIT_PROTNONE
# include/linux/sched.h # include/linux/sched.h
PF_KTHREAD = 0x00200000 # I'm a kernel thread PF_KTHREAD = 0x00200000 # I'm a kernel thread
+94 -6
View File
@@ -13,6 +13,7 @@ from volatility3 import classproperty
from volatility3.framework import exceptions, interfaces, constants from volatility3.framework import exceptions, interfaces, constants
from volatility3.framework.configuration import requirements from volatility3.framework.configuration import requirements
from volatility3.framework.layers import linear from volatility3.framework.layers import linear
from volatility3.framework.constants import linux as linux_constants
vollog = logging.getLogger(__name__) vollog = logging.getLogger(__name__)
@@ -163,12 +164,17 @@ class Intel(linear.LinearlyMappedLayer):
entry, entry,
f"Page Fault at entry {hex(entry)} in page 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 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]: def _translate_entry(self, offset: int) -> Tuple[int, int]:
"""Translates a specific offset based on paging tables. """Translates a specific offset based on paging tables.
@@ -203,10 +209,10 @@ class Intel(linear.LinearlyMappedLayer):
"Page Fault at entry " + hex(entry) + " in table " + name, "Page Fault at entry " + hex(entry) + " in table " + name,
) )
# Check if we're a large page # Check if we're a large page
if large_page and (entry & (1 << 7)): if large_page and (entry & (1 << linux_constants.PAGE_BIT_PSE)):
# Mask off the PAT bit # Mask off the PAT bit
if entry & (1 << 12): if entry & (1 << linux_constants.PAGE_BIT_PAT_LARGE):
entry -= 1 << 12 entry -= 1 << linux_constants.PAGE_BIT_PAT_LARGE
# We're a large page, the rest is finished below # We're a large page, the rest is finished below
# If we want to implement PSE-36, it would need to be done here # If we want to implement PSE-36, it would need to be done here
break break
@@ -501,3 +507,85 @@ class WindowsIntel32e(WindowsMixin, Intel32e):
def _translate(self, offset: int) -> Tuple[int, int, str]: def _translate(self, offset: int) -> Tuple[int, int, str]:
return self._translate_swap(self, offset, self._bits_per_register // 2) 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)
& (linux_constants.PAGE_PRESENT | linux_constants.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/PAGE_GLOBAL) are inverted
return not (entry & linux_constants.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 ~0 & 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