Skip to content

Commit fc3c9b4

Browse files
SchamperJSCU-CNI
authored andcommitted
Reset file position in container, volume and filesystem (fox-it#437)
1 parent aeca5ff commit fc3c9b4

18 files changed

+163
-56
lines changed

dissect/target/container.py

+37-3
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,40 @@ def detect(cls, item: Union[list, BinaryIO, Path]) -> bool:
6565

6666
return False
6767

68-
@staticmethod
69-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
68+
@classmethod
69+
def detect_fh(cls, fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
7070
"""Detect if this ``Container`` can be used to open the file-like object ``fh``.
7171
72-
The function checks wether the raw data contains any magic information that corresponds to this
72+
The function checks whether the raw data contains any magic information that corresponds to this
7373
specific container.
7474
75+
Args:
76+
fh: A file-like object that we want to open a ``Container`` on.
77+
original: The original argument passed to ``detect()``.
78+
79+
Returns:
80+
``True`` if this ``Container`` can be used for this file-like object, ``False`` otherwise.
81+
"""
82+
offset = fh.tell()
83+
try:
84+
fh.seek(0)
85+
return cls._detect_fh(fh, original)
86+
except NotImplementedError:
87+
raise
88+
except Exception as e:
89+
log.warning("Failed to detect %s container", cls.__name__)
90+
log.debug("", exc_info=e)
91+
finally:
92+
fh.seek(offset)
93+
94+
return False
95+
96+
@staticmethod
97+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
98+
"""Detect if this ``Container`` can be used to open the file-like object ``fh``.
99+
100+
This method should be implemented by subclasses. The position of ``fh`` is guaranteed to be ``0``.
101+
75102
Args:
76103
fh: A file-like object that we want to open a ``Container`` on.
77104
original: The original argument passed to ``detect()``.
@@ -177,6 +204,7 @@ def open(item: Union[list, str, BinaryIO, Path], *args, **kwargs):
177204
first = item[0] if isinstance(item, list) else item
178205
first_fh = None
179206
first_fh_opened = False
207+
first_fh_offset = None
180208
first_path = None
181209

182210
if hasattr(first, "read"):
@@ -187,6 +215,10 @@ def open(item: Union[list, str, BinaryIO, Path], *args, **kwargs):
187215
first_fh = first.open("rb")
188216
first_fh_opened = True
189217

218+
if first_fh:
219+
first_fh_offset = first_fh.tell()
220+
first_fh.seek(0)
221+
190222
try:
191223
for container in CONTAINERS + [RawContainer]:
192224
try:
@@ -203,6 +235,8 @@ def open(item: Union[list, str, BinaryIO, Path], *args, **kwargs):
203235
finally:
204236
if first_fh_opened:
205237
first_fh.close()
238+
elif first_fh:
239+
first_fh.seek(first_fh_offset)
206240

207241
raise ContainerError(f"Failed to detect container for {item}")
208242

dissect/target/containers/asdf.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,8 @@ def __init__(self, fh: BinaryIO, *args, **kwargs):
2626
super().__init__(fh, self.asdf.size, *args, **kwargs)
2727

2828
@staticmethod
29-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
30-
magic = fh.read(4)
31-
fh.seek(-4, io.SEEK_CUR)
32-
33-
return magic == FILE_MAGIC
29+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
30+
return fh.read(4) == FILE_MAGIC
3431

3532
@staticmethod
3633
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/ewf.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@ def __init__(self, fh: Union[list, BinaryIO, Path], *args, **kwargs):
2323
super().__init__(fh, self.ewf.size, *args, **kwargs)
2424

2525
@staticmethod
26-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
26+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
2727
"""Detect file header"""
28-
magic = fh.read(3)
29-
fh.seek(-3, io.SEEK_CUR)
30-
31-
return magic == b"EVF" or magic == b"LVF" or magic == b"LEF"
28+
return fh.read(3) in (b"EVF", b"LVF", b"LEF")
3229

3330
@staticmethod
3431
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/hdd.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def __init__(self, fh: Path, *args, **kwargs):
1717
super().__init__(fh, self.stream.size, *args, **kwargs)
1818

1919
@staticmethod
20-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
20+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
2121
return False
2222

2323
@staticmethod

dissect/target/containers/hds.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,8 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
1818
super().__init__(fh, self.hds.size, *args, **kwargs)
1919

2020
@staticmethod
21-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
22-
sig = fh.read(16)
23-
fh.seek(-16, io.SEEK_CUR)
24-
25-
return sig in (c_hdd.SIGNATURE_STRUCTURED_DISK_V1, c_hdd.SIGNATURE_STRUCTURED_DISK_V2)
21+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
22+
return fh.read(16) in (c_hdd.SIGNATURE_STRUCTURED_DISK_V1, c_hdd.SIGNATURE_STRUCTURED_DISK_V2)
2623

2724
@staticmethod
2825
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/qcow2.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@ def __init__(self, fh: Union[BinaryIO, Path], data_file=None, backing_file=None,
1717
super().__init__(fh, self.qcow2.size, *args, **kwargs)
1818

1919
@staticmethod
20-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
21-
magic = fh.read(4)
22-
fh.seek(-4, io.SEEK_CUR)
23-
24-
return magic == c_qcow2.QCOW2_MAGIC_BYTES
20+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
21+
return fh.read(4) == c_qcow2.QCOW2_MAGIC_BYTES
2522

2623
@staticmethod
2724
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/raw.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
3232
super().__init__(fh, size, *args, **kwargs)
3333

3434
@staticmethod
35-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
35+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
3636
return True
3737

3838
@staticmethod

dissect/target/containers/split.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, fh: Union[list, BinaryIO, Path], *args, **kwargs):
3232
super().__init__(fh, offset, *args, **kwargs)
3333

3434
@staticmethod
35-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
35+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
3636
return isinstance(original, list) and len(original) > 1
3737

3838
@staticmethod

dissect/target/containers/vdi.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
1919
super().__init__(fh, self.vdi.size, *args, **kwargs)
2020

2121
@staticmethod
22-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
23-
magic = fh.read(68)
24-
fh.seek(-68, io.SEEK_CUR)
25-
return magic[-4:] == b"\x7f\x10\xda\xbe"
22+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
23+
return fh.read(68)[-4:] == b"\x7f\x10\xda\xbe"
2624

2725
@staticmethod
2826
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/vhd.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,9 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
1717
super().__init__(fh, self.vhd.size, *args, **kwargs)
1818

1919
@staticmethod
20-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
21-
offset = fh.tell()
20+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
2221
fh.seek(-512, io.SEEK_END)
23-
magic = fh.read(9)
24-
fh.seek(offset)
25-
26-
return b"conectix" in magic
22+
return b"conectix" in fh.read(9)
2723

2824
@staticmethod
2925
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/vhdx.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,8 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
1313
super().__init__(fh, self.vhdx.size, *args, **kwargs)
1414

1515
@staticmethod
16-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
17-
magic = fh.read(8)
18-
fh.seek(-8, io.SEEK_CUR)
19-
20-
return magic == b"vhdxfile"
16+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
17+
return fh.read(8) == b"vhdxfile"
2118

2219
@staticmethod
2320
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/containers/vmdk.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,8 @@ def __init__(self, fh: Union[BinaryIO, Path], *args, **kwargs):
1515
super().__init__(fh, self.vmdk.size, *args, **kwargs)
1616

1717
@staticmethod
18-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
19-
magic = fh.read(4)
20-
fh.seek(-4, io.SEEK_CUR)
21-
22-
return magic in (b"KDMV", b"COWD", b"# Di")
18+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
19+
return fh.read(4) in (b"KDMV", b"COWD", b"# Di")
2320

2421
@staticmethod
2522
def detect_path(path: Path, original: Union[list, BinaryIO]) -> bool:

dissect/target/filesystem.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -1449,15 +1449,22 @@ def register(module: str, class_name: str, internal: bool = True) -> None:
14491449

14501450

14511451
def open(fh: BinaryIO, *args, **kwargs) -> Filesystem:
1452-
for filesystem in FILESYSTEMS:
1453-
try:
1454-
if filesystem.detect(fh):
1455-
instance = filesystem(fh, *args, **kwargs)
1456-
instance.volume = fh
1457-
return instance
1458-
except ImportError as e:
1459-
log.info("Failed to import %s", filesystem)
1460-
log.debug("", exc_info=e)
1452+
offset = fh.tell()
1453+
fh.seek(0)
1454+
1455+
try:
1456+
for filesystem in FILESYSTEMS:
1457+
try:
1458+
if filesystem.detect(fh):
1459+
instance = filesystem(fh, *args, **kwargs)
1460+
instance.volume = fh
1461+
1462+
return instance
1463+
except ImportError as e:
1464+
log.info("Failed to import %s", filesystem)
1465+
log.debug("", exc_info=e)
1466+
finally:
1467+
fh.seek(offset)
14611468

14621469
raise FilesystemError(f"Failed to open filesystem for {fh}")
14631470

dissect/target/volume.py

+5
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,15 @@ def open(fh: BinaryIO, *args, **kwargs) -> DissectVolumeSystem:
327327
Returns:
328328
An opened :class:`~dissect.target.volumes.disk.DissectVolumeSystem`.
329329
"""
330+
offset = fh.tell()
331+
fh.seek(0)
332+
330333
try:
331334
return disk.DissectVolumeSystem(fh)
332335
except Exception as e:
333336
raise VolumeSystemError(f"Failed to load volume system for {fh}", cause=e)
337+
finally:
338+
fh.seek(offset)
334339

335340

336341
def is_lvm_volume(volume: BinaryIO) -> bool:

tests/data/plugin_register/container.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __repr__(self):
1313
return f"<{self.__class__.__name__} size={self.size} vs={self.vs}>"
1414

1515
@staticmethod
16-
def detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
16+
def _detect_fh(fh: BinaryIO, original: Union[list, BinaryIO]) -> bool:
1717
return False
1818

1919
@staticmethod

tests/test_container_open.py

+26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import struct
2+
from io import BytesIO
23
from pathlib import Path
34
from unittest.mock import Mock, patch
45

@@ -69,3 +70,28 @@ def test_open_fallback_fh(tmp_path):
6970
tmp_dummy.write_bytes(b"\x00" * 1024)
7071
assert isinstance(container.open(tmp_dummy), raw.RawContainer)
7172
assert not vhd.VhdContainer.detect(tmp_dummy)
73+
74+
75+
def test_reset_file_position() -> None:
76+
fh = BytesIO(b"\x00" * 8192)
77+
fh.seek(512)
78+
79+
class MockContainer(container.Container):
80+
def __init__(self, fh):
81+
assert fh.tell() == 0
82+
fh.seek(1024)
83+
self.success = True
84+
85+
@staticmethod
86+
def _detect_fh(fh, *args, **kwargs):
87+
assert fh.tell() == 0
88+
fh.seek(256)
89+
return True
90+
91+
mock_container = Mock()
92+
mock_container.MockContainer = MockContainer
93+
with patch.object(container, "CONTAINERS", [mock_container.MockContainer]):
94+
opened_container = container.open(fh)
95+
assert isinstance(opened_container, mock_container.MockContainer)
96+
assert opened_container.success
97+
assert fh.tell() == 512

tests/test_filesystem.py

+28
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
22
import stat
3+
from io import BytesIO
34
from tempfile import NamedTemporaryFile, TemporaryDirectory
45
from typing import Union
56
from unittest.mock import Mock, patch
67

78
import pytest
89
from _pytest.fixtures import FixtureRequest
910

11+
from dissect.target import filesystem
1012
from dissect.target.exceptions import (
1113
FileNotFoundError,
1214
NotADirectoryError,
@@ -1092,3 +1094,29 @@ def test_mapped_file_lattr(mapped_file: MappedFile) -> None:
10921094
with patch("dissect.target.helpers.fsutil.fs_attrs", autospec=True) as fs_attrs:
10931095
mapped_file.lattr()
10941096
fs_attrs.assert_called_with(mapped_file.entry, follow_symlinks=False)
1097+
1098+
1099+
def test_reset_file_position() -> None:
1100+
fh = BytesIO(b"\x00" * 8192)
1101+
fh.seek(512)
1102+
1103+
class MockFilesystem(filesystem.Filesystem):
1104+
def __init__(self, fh):
1105+
assert fh.tell() == 0
1106+
fh.seek(1024)
1107+
self.success = True
1108+
1109+
@staticmethod
1110+
def _detect(fh):
1111+
assert fh.tell() == 0
1112+
fh.seek(256)
1113+
return True
1114+
1115+
mock_fs = Mock()
1116+
mock_fs.MockFilesystem = MockFilesystem
1117+
1118+
with patch.object(filesystem, "FILESYSTEMS", [mock_fs.MockFilesystem]):
1119+
opened_fs = filesystem.open(fh)
1120+
assert isinstance(opened_fs, mock_fs.MockFilesystem)
1121+
assert opened_fs.success
1122+
assert fh.tell() == 512

tests/test_volume.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from io import BytesIO
2+
from unittest.mock import patch
3+
4+
from dissect.target import volume
5+
from dissect.target.volumes import disk
6+
7+
8+
def test_reset_file_position() -> None:
9+
fh = BytesIO(b"\x00" * 8192)
10+
fh.seek(512)
11+
12+
class MockVolumeSystem(volume.VolumeSystem):
13+
def __init__(self, fh):
14+
assert fh.tell() == 0
15+
fh.seek(1024)
16+
self.success = True
17+
18+
@staticmethod
19+
def _detect(fh):
20+
assert fh.tell() == 0
21+
fh.seek(256)
22+
return True
23+
24+
with patch.object(disk, "DissectVolumeSystem", MockVolumeSystem):
25+
assert MockVolumeSystem.detect(fh)
26+
assert fh.tell() == 512
27+
28+
opened_vs = volume.open(fh)
29+
assert isinstance(opened_vs, MockVolumeSystem)
30+
assert opened_vs.success
31+
assert fh.tell() == 512

0 commit comments

Comments
 (0)