Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use conda-package-handling.api.extract instead of tarfile.TarFile.extractall #5390

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions conda_build/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from pathlib import Path
from typing import TYPE_CHECKING

from conda_package_handling.api import extract

from .utils import ensure_list, filter_info_files, walk

if TYPE_CHECKING:
Expand Down Expand Up @@ -129,13 +131,9 @@ def extract_temporary_directory(file_path):
Positional arguments:
file_path (str) -- the file path to the source package tar file
"""
temporary_directory = tempfile.mkdtemp()

source = tarfile.open(file_path)
source.extractall(temporary_directory)
source.close()

return temporary_directory
tmp = tempfile.mkdtemp()
extract(file_path, dest_dir=tmp)
return tmp


def update_dependencies(new_dependencies, existing_dependencies):
Expand Down
5 changes: 3 additions & 2 deletions conda_build/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from conda.gateways.disk.create import TemporaryDirectory
from conda.models.records import PackageRecord
from conda.models.version import VersionOrder
from conda_package_handling.api import extract
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API can only extract .conda or .tar.bz2, not any other .tgz etc. variant.


from . import environ, exceptions, source, utils
from .exceptions import DependencyNeedsBuildingError
Expand Down Expand Up @@ -933,8 +934,8 @@ def open_recipe(recipe: str | os.PathLike | Path) -> Iterator[Path]:
yield recipe
elif recipe.suffixes in [[".tar"], [".tar", ".gz"], [".tgz"], [".tar", ".bz2"]]:
# extract the recipe to a temporary directory
with TemporaryDirectory() as tmp, tarfile.open(recipe, "r:*") as tar:
tar.extractall(path=tmp)
with TemporaryDirectory() as tmp:
extract(recipe, dest_dir=tmp)
yield Path(tmp)
elif recipe.suffix == ".yaml":
# read the recipe from the parent directory
Expand Down
40 changes: 7 additions & 33 deletions conda_build/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import stat
import subprocess
import sys
import tarfile
import tempfile
import time
import urllib.parse as urlparse
Expand Down Expand Up @@ -44,7 +43,6 @@
from threading import Thread
from typing import TYPE_CHECKING, Iterable, overload

import conda_package_handling.api
import filelock
import libarchive
import yaml
Expand All @@ -65,6 +63,7 @@
from conda.models.records import PackageRecord
from conda.models.version import VersionOrder
from conda.utils import unix_path_to_win
from conda_package_handling.api import extract

from .exceptions import BuildLockError

Expand Down Expand Up @@ -446,9 +445,7 @@ def get_recipe_abspath(recipe):
):
recipe_dir = tempfile.mkdtemp()
if recipe.lower().endswith(CONDA_PACKAGE_EXTENSIONS):
import conda_package_handling.api

conda_package_handling.api.extract(recipe, recipe_dir)
extract(recipe, dest_dir=recipe_dir)
else:
tar_xf(recipe, recipe_dir)
# At some stage the old build system started to tar up recipes.
Expand Down Expand Up @@ -783,32 +780,13 @@ def _tar_xf_fallback(tarball, dir_path, mode="r:*"):
from .os_utils.external import find_executable

if tarball.lower().endswith(".tar.z"):
uncompress = find_executable("uncompress")
if not uncompress:
uncompress = find_executable("gunzip")
uncompress = find_executable("uncompress") or find_executable("gunzip")
if not uncompress:
sys.exit(
"""\
uncompress (or gunzip) is required to unarchive .z source files.
"""
)
sys.exit("uncompress/gunzip is required to unarchive .z source files.")
check_call_env([uncompress, "-f", tarball])
tarball = tarball[:-2]

t = tarfile.open(tarball, mode)
members = t.getmembers()
for i, member in enumerate(members, 0):
if os.path.isabs(member.name):
member.name = os.path.relpath(member.name, "/")
cwd = os.path.realpath(os.getcwd())
if not os.path.realpath(member.name).startswith(cwd):
member.name = member.name.replace("../", "")
if not os.path.realpath(member.name).startswith(cwd):
sys.exit("tarball contains unsafe path: " + member.name + " cwd is: " + cwd)
members[i] = member

t.extractall(path=dir_path)
t.close()
extract(tarball, dest_dir=dir_path)
beckermr marked this conversation as resolved.
Show resolved Hide resolved


def tar_xf_file(tarball, entries):
Expand Down Expand Up @@ -1136,13 +1114,9 @@ def package_has_file(package_path, file_path, refresh_mode="modified"):
# This version does nothing to the package cache.
with TemporaryDirectory() as td:
if file_path.startswith("info"):
conda_package_handling.api.extract(
package_path, dest_dir=td, components="info"
)
extract(package_path, dest_dir=td, components="info")
else:
conda_package_handling.api.extract(
package_path, dest_dir=td, components=file_path
)
extract(package_path, dest_dir=td, components=file_path)
resolved_file_path = os.path.join(td, file_path)
if os.path.exists(resolved_file_path):
# TODO :: Remove this text-mode load. Files are binary.
Expand Down
19 changes: 19 additions & 0 deletions news/5390-tarfile-extract-data
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* Use `tarfile.TarFile.extract[all](filter='data')` for improved tarball extraction security (e.g., disallow paths outside of or linking outside of the destination, remove group & other write/executable permissions, etc.). (#5390)

### Bug fixes

* <news item>

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ filterwarnings = [
# ignore conda-index error
"ignore::PendingDeprecationWarning:conda_index",
"ignore::DeprecationWarning:conda_index",
"ignore:Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata:DeprecationWarning",
# ignore conda-package-streaming error
"ignore:Python 3.14 will, by default, filter extracted tar archives and reject files or modify their metadata:DeprecationWarning:conda_package_streaming",
]
markers = [
"serial: execute test serially (to avoid race conditions)",
Expand Down
Loading