diff --git a/volatility/framework/constants/__init__.py b/volatility/framework/constants/__init__.py index 5b2f27c2d..d08dfd8f2 100644 --- a/volatility/framework/constants/__init__.py +++ b/volatility/framework/constants/__init__.py @@ -38,7 +38,6 @@ SYMBOL_BASEPATHS = [ ] BANG = "!" PACKAGE_VERSION = "3.0.0_alpha1" -DISABLE_MULTITHREADED_SCANNING = False AUTOMAGIC_CONFIG_PATH = 'automagic' LOGLEVEL_V = 9 @@ -56,3 +55,6 @@ LINUX_BANNERS_PATH = os.path.join(CACHE_PATH, "linux_banners.cache") MAC_BANNERS_PATH = os.path.join(CACHE_PATH, "mac_banners.cache") ProgressCallback = Optional[Callable[[float, str], None]] + +# Options are 'multiprocessing', 'threading' or None +PARALLELISM = 'multiprocessing' diff --git a/volatility/framework/interfaces/layers.py b/volatility/framework/interfaces/layers.py index 38277948e..ea7a398cc 100644 --- a/volatility/framework/interfaces/layers.py +++ b/volatility/framework/interfaces/layers.py @@ -212,7 +212,7 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla progress = DummyProgress() # type: ProgressValue scan_iterator = functools.partial(self._scan_iterator, scanner, sections) scan_metric = self._scan_metric(scanner, sections) - if scanner.thread_safe and not constants.DISABLE_MULTITHREADED_SCANNING: + if scanner.thread_safe and not constants.PARALLELISM: progress = multiprocessing.Manager().Value("Q", 0) scan_chunk = functools.partial(self._scan_chunk, scanner, progress) with multiprocessing.Pool() as pool: diff --git a/volatility/framework/layers/physical.py b/volatility/framework/layers/physical.py index c0e42afcb..9b0ad6562 100644 --- a/volatility/framework/layers/physical.py +++ b/volatility/framework/layers/physical.py @@ -17,10 +17,11 @@ # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the # specific language governing rights and limitations under the License. # - +import threading +from contextlib import contextmanager from typing import Any, Dict, IO, List, Optional -from volatility.framework import exceptions, interfaces +from volatility.framework import exceptions, interfaces, constants from volatility.framework.configuration import requirements from volatility.framework.layers import resources @@ -77,6 +78,15 @@ class BufferDataLayer(interfaces.layers.DataLayerInterface): ] +class DummyLock: + + def __enter__(self): + pass + + def __exit__(self, type, value, traceback): + pass + + class FileLayer(interfaces.layers.DataLayerInterface): """a DataLayer backed by a file on the filesystem""" @@ -93,6 +103,12 @@ class FileLayer(interfaces.layers.DataLayerInterface): self._accessor = resources.ResourceAccessor() self._file_ = None # type: Optional[IO[Any]] self._size = None # type: Optional[int] + # Construct the lock now (shared if made before threading) in case we ever need it + if constants.PARALLELISM == 'threading': + self._lock = threading.Lock() + else: + # We don't need a lock for multiprocessing because child threads can't inherit file descriptors by default + self._lock = DummyLock() # Instantiate the file to throw exceptions if the file doesn't open _ = self._file @@ -115,10 +131,11 @@ class FileLayer(interfaces.layers.DataLayerInterface): # Zero based, so we return the size of the file minus 1 if self._size: return self._size - orig = self._file.tell() - self._file.seek(0, 2) - self._size = self._file.tell() - self._file.seek(orig) + with self._lock: + orig = self._file.tell() + self._file.seek(0, 2) + self._size = self._file.tell() + self._file.seek(orig) return self._size @property @@ -141,8 +158,12 @@ class FileLayer(interfaces.layers.DataLayerInterface): invalid_address = self.maximum_address + 1 raise exceptions.InvalidAddressException(self.name, invalid_address, "Offset outside of the buffer boundaries") - self._file.seek(offset) - data = self._file.read(length) + + # TODO: implement locking for multi-threading + with self._lock: + self._file.seek(offset) + data = self._file.read(length) + if len(data) < length: if pad: data += (b"\x00" * (length - len(data))) @@ -162,8 +183,9 @@ class FileLayer(interfaces.layers.DataLayerInterface): invalid_address = self.maximum_address + 1 raise exceptions.InvalidAddressException(self.name, invalid_address, "Data segment outside of the " + self.name + " file boundaries") - self._file.seek(offset) - self._file.write(data) + with self._lock: + self._file.seek(offset) + self._file.write(data) def __getstate__(self) -> Dict[str, Any]: """Do not store the open _file_ attribute, our property will ensure the file is open when needed diff --git a/volatility/framework/layers/scanners/__init__.py b/volatility/framework/layers/scanners/__init__.py index 0528f8960..97e7fecb9 100644 --- a/volatility/framework/layers/scanners/__init__.py +++ b/volatility/framework/layers/scanners/__init__.py @@ -43,8 +43,7 @@ class BytesScanner(layers.ScannerInterface): class RegExScanner(layers.ScannerInterface): - # TODO: Document why this isn't thread safe? - thread_safe = False + thread_safe = True def __init__(self, pattern: bytes, flags: int = 0) -> None: super().__init__()