From e18b37abf5c64dc541cd41a36b7f660c122a1c77 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 1 Dec 2025 15:54:58 +0100 Subject: [PATCH 1/4] remove PTEs "large_page" attribute and re-order large_page check --- volatility3/framework/layers/intel.py | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 848580004..f750f3f2f 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -38,7 +38,7 @@ class Intel(linear.LinearlyMappedLayer): # NOTE: _maxphyaddr is MAXPHYADDR as defined in the Intel specs *NOT* the maximum physical address _maxphyaddr = 32 _maxvirtaddr = _maxphyaddr - _structure = [("page directory", 10, False), ("page table", 10, True)] + _structure = [("page directory", 10, True), ("page table", 10, False)] _direct_metadata = collections.ChainMap( {"architecture": "Intel32"}, {"mapped": True}, @@ -221,18 +221,6 @@ class Intel(linear.LinearlyMappedLayer): entry, "Page Fault at entry " + hex(entry) + " in table " + name, ) - # Check if we're a large page - if large_page and (entry & self._PAGE_PSE): - # Mask off the PAT bit - 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 - # Figure out how much of the offset we should be using - start = position - position -= size - index = self._mask(page_address, start, position + 1) >> (position + 1) # Grab the base address of the table we'll be getting the next entry from base_address = self._mask( @@ -249,6 +237,11 @@ class Intel(linear.LinearlyMappedLayer): "Page Fault at entry " + hex(entry) + " in table " + name, ) + # Figure out how much of the offset we should be using + start = position + position -= size + index = self._mask(page_address, start, position + 1) >> (position + 1) + # Read the data for the next entry entry_data_start = index << self._index_shift entry_data = table[entry_data_start : entry_data_start + self._entry_size] @@ -262,6 +255,15 @@ class Intel(linear.LinearlyMappedLayer): # Read out the new entry from memory (entry,) = struct.unpack(self._entry_format, entry_data) + # Check if we're a large page + if large_page and (entry & self._PAGE_PSE): + # Mask off the PAT bit + 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 + return entry, position @functools.lru_cache(maxsize=1025) @@ -429,7 +431,7 @@ class IntelPAE(Intel): _structure = [ ("page directory pointer", 2, False), ("page directory", 9, True), - ("page table", 9, True), + ("page table", 9, False), ] _direct_metadata = collections.ChainMap({"pae": True}, Intel._direct_metadata) @@ -449,7 +451,7 @@ class Intel32e(Intel): ("page map layer 4", 9, False), ("page directory pointer", 9, True), ("page directory", 9, True), - ("page table", 9, True), + ("page table", 9, False), ] From ae0f0ca440f878d7e7e56c163b6cb61eb58f19e5 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Mon, 1 Dec 2025 15:56:56 +0100 Subject: [PATCH 2/4] skip invalid blocks more efficiently --- volatility3/framework/layers/intel.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index f750f3f2f..4108d7231 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -269,9 +269,12 @@ class Intel(linear.LinearlyMappedLayer): @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( - self._base_layer, base_address, self.page_size - ) + try: + table = self._context.layers.read( + self._base_layer, base_address, self.page_size + ) + except exceptions.InvalidAddressException: + return None # If the table is entirely duplicates, then mark the whole table as bad if table == table[: self._entry_size] * self._entry_number: @@ -375,12 +378,19 @@ class Intel(linear.LinearlyMappedLayer): while length > 0: try: chunk_offset, page_size, layer_name = self._translate(offset) - chunk_size = min(page_size - (chunk_offset % page_size), length) + # Page align the chunk size value + chunk_size = min(page_size - (offset % page_size), length) if not self._context.layers[layer_name].is_valid( chunk_offset, chunk_size ): - raise exceptions.InvalidAddressException( - layer_name=layer_name, invalid_address=chunk_offset + # Virtual -> physical is contiguous in the chunk_size range. + # If we fail, we can jump directly to the end as we know all bytes in between + # aren't mapped (virtually and) physically anyway. + raise exceptions.PagedInvalidAddressException( + layer_name=layer_name, + invalid_address=chunk_offset, + entry=0, + invalid_bits=int(math.log2(chunk_size)), ) except ( exceptions.PagedInvalidAddressException, From f4e2f1391e25da8d0aa8e69da3bd1257bf2db2e1 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 3 Dec 2025 11:22:27 +0100 Subject: [PATCH 3/4] refactor use of "invalid_bits" inside _mapping into a dedicated variable --- volatility3/framework/layers/intel.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 4108d7231..e6a3244cb 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -376,6 +376,7 @@ class Intel(linear.LinearlyMappedLayer): yield offset, length, mapped_offset, length, layer_name return None while length > 0: + skip_mask = None try: chunk_offset, page_size, layer_name = self._translate(offset) # Page align the chunk size value @@ -386,11 +387,9 @@ class Intel(linear.LinearlyMappedLayer): # Virtual -> physical is contiguous in the chunk_size range. # If we fail, we can jump directly to the end as we know all bytes in between # aren't mapped (virtually and) physically anyway. - raise exceptions.PagedInvalidAddressException( - layer_name=layer_name, - invalid_address=chunk_offset, - entry=0, - invalid_bits=int(math.log2(chunk_size)), + skip_mask = chunk_size - 1 + raise exceptions.InvalidAddressException( + layer_name=layer_name, invalid_address=chunk_offset ) except ( exceptions.PagedInvalidAddressException, @@ -398,12 +397,13 @@ class Intel(linear.LinearlyMappedLayer): ) as excp: if not ignore_errors: raise - # We can jump more if we know where the page fault failed - if isinstance(excp, exceptions.PagedInvalidAddressException): - mask = (1 << excp.invalid_bits) - 1 - else: - mask = (1 << self._page_size_in_bits) - 1 - length_diff = mask + 1 - (offset & mask) + if skip_mask is None: + # We can jump more if we know where the page fault occured + if isinstance(excp, exceptions.PagedInvalidAddressException): + skip_mask = (1 << excp.invalid_bits) - 1 + else: + skip_mask = (1 << self._page_size_in_bits) - 1 + length_diff = skip_mask + 1 - (offset & skip_mask) length -= length_diff offset += length_diff else: From 93d6282817045e4f0a0d8667a7cb2d4f52ae0c11 Mon Sep 17 00:00:00 2001 From: Abyss Watcher Date: Wed, 3 Dec 2025 11:25:56 +0100 Subject: [PATCH 4/4] version bump: 2.27.0 -> 2.27.1 --- volatility3/framework/constants/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 7f71c277e..73eb5452e 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -1,7 +1,7 @@ # We use the SemVer 2.0.0 versioning scheme VERSION_MAJOR = 2 # Number of releases of the library with a breaking change VERSION_MINOR = 27 # Number of changes that only add to the interface -VERSION_PATCH = 0 # Number of changes that do not change the interface +VERSION_PATCH = 1 # Number of changes that do not change the interface VERSION_SUFFIX = "" PACKAGE_VERSION = (