import signal
import subprocess
import sys
+import tarfile
import uuid
from pathlib import Path
from types import FrameType
continue
chown_to_running_user(path)
+
+
+def safe_tar_extract(tar: tarfile.TarFile, path: Path=Path("."), *, numeric_owner: bool=False) -> None:
+ """Extract a tar without CVE-2007-4559.
+
+ Throws a MkosiException if a member of the tar resolves to a path that would
+ be outside of the passed in target path.
+
+ Omits the member argument from TarFile.extractall, since we don't need it at
+ the moment.
+
+ See https://github.com/advisories/GHSA-gw9q-c7gh-j9vm
+ """
+ path = path.resolve()
+ for member in tar.getmembers():
+ target = path / member.name
+ try:
+ # a.relative_to(b) throws a ValueError if a is not a subpath of b
+ target.resolve().relative_to(path)
+ except ValueError as e:
+ raise MkosiException(f"Attempted path traversal in tar file {tar.name!r}") from e
+
+ tar.extractall(path, numeric_owner=numeric_owner)
die,
root_home,
run_workspace_command,
+ safe_tar_extract,
)
ARCHITECTURES = {
with tarfile.open(stage3_tar_path) as tfd:
MkosiPrinter.print_step(f"Extracting {stage3_tar.name} to "
f"{stage3_tmp_extract}")
- tfd.extractall(stage3_tmp_extract, numeric_owner=True)
+ safe_tar_extract(tfd, stage3_tmp_extract, numeric_owner=True)
# REMOVEME : pathetic attempt have this merged :)
# remove once upstream ships the current *baselayout-999*
# SPDX-License-Identifier: LGPL-2.1+
import os
+import secrets
+import tarfile
from pathlib import Path
-from mkosi.backend import Distribution, PackageType, PartitionTable, set_umask, workspace
+import pytest
+
+from mkosi.backend import (
+ Distribution,
+ MkosiException,
+ PackageType,
+ PartitionTable,
+ safe_tar_extract,
+ set_umask,
+ workspace,
+)
def test_distribution() -> None:
# When disk_size() cascade upwards to last_partition_offset, if clause.
table.last_partition_sector = 32
assert table.disk_size() == 36864
+
+
+def test_safe_tar_extract(tmp_path: Path) -> None:
+ name = secrets.token_hex()
+ testfile = tmp_path / name
+ testfile.write_text("Evil exploit\n")
+
+ safe_tar = tmp_path / "safe.tar.gz"
+ with tarfile.TarFile.open(safe_tar, "x:gz") as t:
+ t.add(testfile, arcname=name)
+
+ evil_tar = tmp_path / "evil.tar.gz"
+ with tarfile.TarFile.open(evil_tar, "x:gz") as t:
+ t.add(testfile, arcname=f"../../../../../../../../../../../../../../tmp/{name}")
+
+ safe_target = tmp_path / "safe_target"
+ with tarfile.TarFile.open(safe_tar) as t:
+ safe_tar_extract(t, safe_target)
+ assert (safe_target / name).exists()
+
+ evil_target = tmp_path / "evil_target"
+ with pytest.raises(MkosiException):
+ with tarfile.TarFile.open(evil_tar) as t:
+ safe_tar_extract(t, evil_target)
+ assert not (evil_target / name).exists()
+ assert not (Path("/tmp") / name).exists()