Skip to content

Commit c0aea41

Browse files
authored
Merge pull request #1750 from volatilityfoundation/import_checker
Testing: Check + fix 'import from' statements
2 parents 61e7430 + 1412004 commit c0aea41

File tree

32 files changed

+254
-186
lines changed

32 files changed

+254
-186
lines changed
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Check Volatility3 Version Requirements
1+
name: Volatility3 Code Analysis
22
on: [push, pull_request]
33
jobs:
44

@@ -21,5 +21,4 @@ jobs:
2121
2222
- name: Testing...
2323
run: |
24-
# Verify completeness of ConfigurableInterface requirements
25-
python ./test/check_configurable_requirements.py
24+
python ./test/volatility3_code_analysis.py
Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import logging
2323
import pkgutil
2424
import sys
25+
import traceback
2526
import types
2627
from typing import Any, Iterator, List, Optional, Tuple, Type, Union
2728

@@ -105,6 +106,36 @@ def __str__(self) -> str:
105106
)
106107

107108

109+
class DirectVolatilityImportUsage(CodeViolation):
110+
111+
def __init__(
112+
self,
113+
module: types.ModuleType,
114+
node: ast.AST,
115+
importing_module: str,
116+
imported_item: object,
117+
imported_name: str,
118+
) -> None:
119+
self.imported_item = imported_item
120+
self.imported_name = imported_name
121+
self.importing_module = importing_module
122+
super().__init__(module, node)
123+
124+
def __str__(self) -> str:
125+
components = self.importing_module.split(".")
126+
return (
127+
super().__str__()
128+
+ ": "
129+
+ (
130+
f"Direct import of {self.imported_name} "
131+
f"({type(self.imported_item)}) "
132+
f"from module {self.importing_module} - "
133+
"change to "
134+
f"'from {'.'.join(components[:-1])} import {components[-1]} and using {components[-1]}.{self.imported_name}"
135+
)
136+
)
137+
138+
108139
def is_versionable(var):
109140
try:
110141
return (
@@ -134,6 +165,46 @@ def __init__(self, module: types.ModuleType) -> None:
134165
def violations(self):
135166
return self._violations
136167

168+
def _check_vol3_import_from(self, node: ast.ImportFrom):
169+
"""
170+
Ensure that the only thing imported from a volatility3 module (apart
171+
from the root volatility3 module) are functions and modules. This
172+
prevents re-exporting of classes and variables from modules that use
173+
them.
174+
"""
175+
if (
176+
node.module
177+
and node.module.startswith("volatility3.") # Give a pass to volatility3 module
178+
and node.module != "volatility3.framework.constants._version" # make an exception for this
179+
):
180+
for name in node.names:
181+
try:
182+
item = vars(self._module)[
183+
name.asname if name.asname is not None else name.name
184+
]
185+
except KeyError:
186+
logger.debug(
187+
"Couldn't find imported name %s in module %s",
188+
name.asname or name.name,
189+
self._module.__name__,
190+
)
191+
continue
192+
193+
if not (isinstance(item, types.ModuleType) or inspect.isfunction(item)):
194+
self._violations.append(
195+
DirectVolatilityImportUsage(
196+
self._module,
197+
node,
198+
node.module,
199+
item,
200+
name.asname or name.name,
201+
)
202+
)
203+
204+
def enter_ImportFrom(self, node: ast.ImportFrom):
205+
self._check_vol3_import_from(node)
206+
207+
137208
def enter_ClassDef(self, node: ast.ClassDef) -> Any:
138209
logger.debug("Entering class %s", node.name)
139210
clazz = None
@@ -304,6 +375,7 @@ def report_missing_requirements() -> Iterator[Tuple[str, UnrequiredVersionableUs
304375
modname,
305376
str(exc),
306377
)
378+
traceback.print_exc()
307379
continue
308380

309381
logger.info("Checking module %s", plugin_module.__name__)
@@ -344,9 +416,7 @@ def perform_review():
344416
print(str(usage))
345417

346418
if found:
347-
print(
348-
f"Found {found} issues"
349-
)
419+
print(f"Found {found} issues")
350420
sys.exit(1)
351421

352422
print("All configurable classes passed validation!")

volatility3/framework/exceptions.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from typing import Callable, Dict, Optional, Tuple
1212

1313
from volatility3.framework import interfaces
14-
from volatility3.framework.interfaces.configuration import VersionableInterface
1514

1615

1716
class VolatilityException(Exception):
@@ -143,15 +142,15 @@ class VersionMismatchException(VolatilityException):
143142
def __init__(
144143
self,
145144
source_component: Callable,
146-
target_component: VersionableInterface,
145+
target_component: interfaces.configuration.VersionableInterface,
147146
target_version: Tuple[int, int, int],
148147
failure_reason: str = None,
149148
*args,
150149
):
151150
"""
152151
Args:
153152
source_component: The component that required the target component
154-
target_component: The component that is required. Must inherit from VersionableInterface
153+
target_component: The component that is required. Must inherit from interfaces.configuration.VersionableInterface
155154
target_version: The version of the target component that was required, and ultimately was not satisfied
156155
failure_reason: A detailed failure reason to enhance debugging and bug tracking
157156
"""

volatility3/framework/interfaces/symbols.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from volatility3.framework import constants, exceptions, interfaces
1111
from volatility3.framework.configuration import requirements
1212
from volatility3.framework.interfaces import configuration, objects
13-
from volatility3.framework.interfaces.configuration import RequirementInterface
1413

1514

1615
class SymbolInterface:
@@ -347,7 +346,7 @@ def build_configuration(self) -> "configuration.HierarchicalDict":
347346
return config
348347

349348
@classmethod
350-
def get_requirements(cls) -> List[RequirementInterface]:
349+
def get_requirements(cls) -> List[configuration.RequirementInterface]:
351350
return super().get_requirements() + [
352351
requirements.IntRequirement(
353352
name="symbol_mask",

volatility3/framework/layers/elf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Optional
77

88
from volatility3.framework import exceptions, interfaces, constants
9-
from volatility3.framework.constants.linux import ELF_CLASS
9+
from volatility3.framework.constants import linux as linux_constants
1010
from volatility3.framework.layers import segmented
1111
from volatility3.framework.symbols import intermed
1212

@@ -23,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer):
2323

2424
_header_struct = struct.Struct("<IBBB")
2525
MAGIC = 0x464C457F # "\x7fELF"
26-
ELF_CLASS = ELF_CLASS.ELFCLASS64
26+
ELF_CLASS = linux_constants.ELF_CLASS.ELFCLASS64
2727

2828
def __init__(
2929
self, context: interfaces.context.ContextInterface, config_path: str, name: str

volatility3/framework/layers/registry.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77

88
from volatility3.framework import constants, exceptions, interfaces, objects
99
from volatility3.framework.configuration import requirements
10-
from volatility3.framework.configuration.requirements import (
11-
IntRequirement,
12-
TranslationLayerRequirement,
13-
)
14-
from volatility3.framework.exceptions import InvalidAddressException
1510
from volatility3.framework.layers import linear
1611
from volatility3.framework.symbols import intermed
1712
from volatility3.framework.symbols.windows import extensions
@@ -154,7 +149,7 @@ def address_mask(self) -> int:
154149
@property
155150
def root_cell_offset(self) -> int:
156151
"""Returns the offset for the root cell in this hive."""
157-
with contextlib.suppress(InvalidAddressException):
152+
with contextlib.suppress(exceptions.InvalidAddressException):
158153
if (
159154
self._base_block.Signature.cast(
160155
"string", max_length=4, encoding="latin-1"
@@ -271,7 +266,7 @@ def _mask(value: int, high_bit: int, low_bit: int) -> int:
271266
@classmethod
272267
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
273268
return [
274-
IntRequirement(
269+
requirements.IntRequirement(
275270
name="hive_offset",
276271
description="Offset within the base layer at which the hive lives",
277272
default=0,
@@ -280,7 +275,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
280275
requirements.SymbolTableRequirement(
281276
name="nt_symbols", description="Windows kernel symbols"
282277
),
283-
TranslationLayerRequirement(
278+
requirements.TranslationLayerRequirement(
284279
name="base_layer",
285280
description="Layer in which the registry hive lives",
286281
optional=False,

volatility3/framework/layers/xen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from volatility3.framework import constants, interfaces, exceptions
66
from volatility3.framework.layers import elf
77
from volatility3.framework.symbols import intermed
8-
from volatility3.framework.constants.linux import ELF_CLASS
8+
from volatility3.framework.constants import linux as linux_constants
99

1010
vollog = logging.getLogger(__name__)
1111

@@ -15,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
1515

1616
_header_struct = struct.Struct("<IBBB")
1717
MAGIC = 0x464C457F # "\x7fELF"
18-
ELF_CLASS = ELF_CLASS.ELFCLASS64
18+
ELF_CLASS = linux_constants.ELF_CLASS.ELFCLASS64
1919

2020
def __init__(
2121
self, context: interfaces.context.ContextInterface, config_path: str, name: str

volatility3/framework/plugins/linux/bash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from volatility3.framework.interfaces import plugins
1414
from volatility3.framework.layers import scanners
1515
from volatility3.framework.objects import utility
16-
from volatility3.framework.symbols.linux.bash import BashIntermedSymbols
16+
from volatility3.framework.symbols.linux import bash
1717
from volatility3.plugins import timeliner
1818
from volatility3.plugins.linux import pslist
1919

@@ -70,7 +70,7 @@ def _generator(self, tasks):
7070
pack_format = "Q"
7171
bash_json_file = "bash64"
7272

73-
bash_table_name = BashIntermedSymbols.create(
73+
bash_table_name = bash.BashIntermedSymbols.create(
7474
self.context, self.config_path, "linux", bash_json_file
7575
)
7676

volatility3/framework/plugins/linux/elfs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from volatility3.framework.renderers import format_hints
1515
from volatility3.framework.symbols import intermed
1616
from volatility3.framework.symbols.linux.extensions import elf
17-
from volatility3.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE
17+
from volatility3.framework.constants import linux as linux_constants
1818
from volatility3.plugins.linux import pslist
1919

2020

@@ -116,7 +116,7 @@ def elf_dump(
116116
real_size = end - start
117117

118118
# Check if ELF has a legitimate size
119-
if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE:
119+
if real_size < 0 or real_size > linux_constants.ELF_MAX_EXTRACTION_SIZE:
120120
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
121121

122122
sections[start] = real_size

volatility3/framework/plugins/linux/modxview.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
88

9-
from volatility3.framework import interfaces, deprecation
9+
from volatility3.framework import interfaces, deprecation, renderers
1010
from volatility3.framework.configuration import requirements
11-
from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue
11+
from volatility3.framework.renderers import format_hints
1212
from volatility3.framework.symbols.linux import extensions
1313
from volatility3.framework.constants import architectures
1414
from volatility3.framework.symbols.linux.utilities import tainting
@@ -156,12 +156,12 @@ def _generator(self):
156156
yield (
157157
0,
158158
(
159-
module.get_name() or NotAvailableValue(),
159+
module.get_name() or renderers.NotAvailableValue(),
160160
format_hints.Hex(module_offset),
161161
linux_utilities_modules.ModuleGathererLsmod.name in gatherers,
162162
linux_utilities_modules.ModuleGathererSysFs.name in gatherers,
163163
linux_utilities_modules.ModuleGathererScanner.name in gatherers,
164-
taints or NotAvailableValue(),
164+
taints or renderers.NotAvailableValue(),
165165
),
166166
)
167167

@@ -175,7 +175,7 @@ def run(self):
175175
("Taints", str),
176176
]
177177

178-
return TreeGrid(
178+
return renderers.TreeGrid(
179179
columns,
180180
self._generator(),
181181
)

0 commit comments

Comments
 (0)