From 68da517872e8b452603b7b2d33102cde9f4be759 Mon Sep 17 00:00:00 2001 From: Andreia Ocanoaia Date: Thu, 11 Sep 2025 14:33:46 +0300 Subject: [PATCH 1/3] Fix qcow2 support for absolute backing_file paths _open_backing_file assumed that the backing_file path was always a filename relative to the snapshot path. Using .with_name() to concat the whole path broke the use case for absolute path for the backing_file causing ValueError("Invalid name ..."). This change updates the logic to detect whether auto_backing_file is an absolute path. If so, it is used directly; otherwise we fall back to constructing the path relative to the snapshot location. This will support the following use-cases: backing file: /home/user/workdir/ubuntu-22.04.qcow2 backing file: ubuntu-22.04.qcow2 backing file: ./ubuntu-22.04-packer.qcow2 backing file: ../../ubuntu-22.04-packer.qcow2 Fixes: https://github.com/fox-it/dissect.hypervisor/issues/64 Signed-off-by: Andreia Ocanoaia --- dissect/hypervisor/disk/qcow2.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index e869f71..fe24274 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -188,7 +188,13 @@ def _open_backing_file(self, backing_file: BinaryIO | None, allow_no_backing_fil backing_file_path = None if backing_file is None: if self.path: - if (backing_file_path := self.path.with_name(self.auto_backing_file)).exists(): + auto_backing_path = Path(self.auto_backing_file) + backing_file_path = ( + auto_backing_path + if auto_backing_path.is_absolute() + else self.path.with_name(auto_backing_path.name) + ) + if backing_file_path.exists(): backing_file = backing_file_path.open("rb") elif not allow_no_backing_file: raise Error( From 44e972a2257fcf463636081eda6b41ff809b12cf Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:48:58 +0200 Subject: [PATCH 2/3] Small change and unit test --- dissect/hypervisor/disk/qcow2.py | 10 ++-------- tests/disk/test_qcow2.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/dissect/hypervisor/disk/qcow2.py b/dissect/hypervisor/disk/qcow2.py index fe24274..70cd647 100644 --- a/dissect/hypervisor/disk/qcow2.py +++ b/dissect/hypervisor/disk/qcow2.py @@ -174,7 +174,7 @@ def _open_data_file(self, data_file: BinaryIO | None, allow_no_data_file: bool = return data_file if self.path: - if (data_file_path := self.path.with_name(self.image_data_file)).exists(): + if (data_file_path := self.path.parent.joinpath(self.image_data_file)).exists(): return data_file_path.open("rb") if not allow_no_data_file: @@ -188,13 +188,7 @@ def _open_backing_file(self, backing_file: BinaryIO | None, allow_no_backing_fil backing_file_path = None if backing_file is None: if self.path: - auto_backing_path = Path(self.auto_backing_file) - backing_file_path = ( - auto_backing_path - if auto_backing_path.is_absolute() - else self.path.with_name(auto_backing_path.name) - ) - if backing_file_path.exists(): + if (backing_file_path := self.path.parent.joinpath(self.auto_backing_file)).exists(): backing_file = backing_file_path.open("rb") elif not allow_no_backing_file: raise Error( diff --git a/tests/disk/test_qcow2.py b/tests/disk/test_qcow2.py index e7d6e42..a8a8792 100644 --- a/tests/disk/test_qcow2.py +++ b/tests/disk/test_qcow2.py @@ -61,6 +61,15 @@ def test_data_file(data_file_qcow2: Path) -> None: for i in range(255): assert stream.read(1024 * 1024).strip(bytes([i])) == b"", f"Mismatch at offset {i * 1024 * 1024:#x}" + # Test with absolute path + with patch.object(Path, "open", lambda *args: None), patch.object(Path, "exists", return_value=False): + qcow2.image_data_file = "/absolute/path/to/nothing.qcow2" + with pytest.raises( + Error, + match=r"data-file '/absolute/path/to/nothing.qcow2' not found \(image_data_file = '/absolute/path/to/nothing.qcow2'\)", # noqa: E501 + ): + qcow2._open_data_file(None) + def test_backing_file(backing_chain_qcow2: tuple[Path, Path, Path]) -> None: file1, file2, file3 = backing_chain_qcow2 @@ -123,6 +132,15 @@ def test_backing_file(backing_chain_qcow2: tuple[Path, Path, Path]) -> None: assert stream.read(1024 * 1024).strip(b"\x00") == b"Something here four" assert stream.read(1024 * 1024).strip(b"\x00") == b"Something here five" + # Test with absolute path + with patch.object(Path, "open", lambda *args: None), patch.object(Path, "exists", return_value=False): + qcow2.auto_backing_file = "/absolute/path/to/nothing.qcow2" + with pytest.raises( + Error, + match=r"backing-file '/absolute/path/to/nothing.qcow2' not found \(auto_backing_file = '/absolute/path/to/nothing.qcow2'\)", # noqa: E501 + ): + qcow2._open_backing_file(None) + def test_snapshot(snapshot_qcow2: BinaryIO) -> None: qcow2 = QCow2(snapshot_qcow2) From 5c6faa4317bd9ca4aa9e2442690f2ce8768f02cc Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:58:13 +0200 Subject: [PATCH 3/3] Fix regex --- tests/disk/test_qcow2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/disk/test_qcow2.py b/tests/disk/test_qcow2.py index a8a8792..6734468 100644 --- a/tests/disk/test_qcow2.py +++ b/tests/disk/test_qcow2.py @@ -66,7 +66,7 @@ def test_data_file(data_file_qcow2: Path) -> None: qcow2.image_data_file = "/absolute/path/to/nothing.qcow2" with pytest.raises( Error, - match=r"data-file '/absolute/path/to/nothing.qcow2' not found \(image_data_file = '/absolute/path/to/nothing.qcow2'\)", # noqa: E501 + match=r"data-file '(?:[A-Z]:\\+)?[/\\]+absolute[/\\]+path[/\\]+to[/\\]+nothing\.qcow2' not found \(image_data_file = '/absolute/path/to/nothing\.qcow2'\)", # noqa: E501 ): qcow2._open_data_file(None) @@ -137,7 +137,7 @@ def test_backing_file(backing_chain_qcow2: tuple[Path, Path, Path]) -> None: qcow2.auto_backing_file = "/absolute/path/to/nothing.qcow2" with pytest.raises( Error, - match=r"backing-file '/absolute/path/to/nothing.qcow2' not found \(auto_backing_file = '/absolute/path/to/nothing.qcow2'\)", # noqa: E501 + match=r"backing-file '(?:[A-Z]:\\+)?[/\\]+absolute[/\\]+path[/\\]+to[/\\]+nothing\.qcow2' not found \(auto_backing_file = '/absolute/path/to/nothing\.qcow2'\)", # noqa: E501 ): qcow2._open_backing_file(None)