Skip to content

Commit

Permalink
Prevent path traversal when installing wheels directly
Browse files Browse the repository at this point in the history
  • Loading branch information
chrahunt committed Jul 10, 2020
1 parent 80a2a94 commit 9b7bb4b
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/pip/_internal/operations/install/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from pip._internal.utils.unpacking import (
current_umask,
is_within_directory,
set_extracted_file_to_default_mode_plus_executable,
zip_item_is_executable,
)
Expand Down Expand Up @@ -537,12 +538,24 @@ def is_dir_path(path):
# type: (RecordPath) -> bool
return path.endswith("/")

def assert_no_path_traversal(dest_dir_path, target_path):
# type: (text_type, text_type) -> None
if not is_within_directory(dest_dir_path, target_path):
message = (
"The wheel {!r} has a file {!r} trying to install"
" outside the target directory {!r}"
)
raise InstallationError(
message.format(wheel_path, target_path, dest_dir_path)
)

def root_scheme_file_maker(zip_file, dest):
# type: (ZipFile, text_type) -> Callable[[RecordPath], File]
def make_root_scheme_file(record_path):
# type: (RecordPath) -> File
normed_path = os.path.normpath(record_path)
dest_path = os.path.join(dest, normed_path)
assert_no_path_traversal(dest, dest_path)
return ZipBackedFile(record_path, dest_path, zip_file)

return make_root_scheme_file
Expand All @@ -562,6 +575,7 @@ def make_data_scheme_file(record_path):
_, scheme_key, dest_subpath = normed_path.split(os.path.sep, 2)
scheme_path = scheme_paths[scheme_key]
dest_path = os.path.join(scheme_path, dest_subpath)
assert_no_path_traversal(scheme_path, dest_path)
return ZipBackedFile(record_path, dest_path, zip_file)

return make_data_scheme_file
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from mock import patch
from pip._vendor.packaging.requirements import Requirement

from pip._internal.exceptions import InstallationError
from pip._internal.locations import get_scheme
from pip._internal.models.direct_url import (
DIRECT_URL_METADATA_NAME,
Expand Down Expand Up @@ -458,6 +459,26 @@ def test_dist_info_contains_empty_dir(self, data, tmpdir):
assert not os.path.isdir(
os.path.join(self.dest_dist_info, 'empty_dir'))

@pytest.mark.parametrize(
"path",
["/tmp/example", "../example", "./../example"]
)
def test_wheel_install_rejects_bad_paths(self, data, tmpdir, path):
self.prep(data, tmpdir)
wheel_path = make_wheel(
"simple", "0.1.0", extra_files={path: "example contents\n"}
).save_to_dir(tmpdir)
with pytest.raises(InstallationError) as e:
wheel.install_wheel(
"simple",
str(wheel_path),
scheme=self.scheme,
req_description="simple",
)

exc_text = str(e.value)
assert str(wheel_path) in exc_text
assert "example" in exc_text

class TestMessageAboutScriptsNotOnPATH(object):

Expand Down

0 comments on commit 9b7bb4b

Please sign in to comment.