-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Add a function to check the security of symbolic links. #13550
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
Changes from 6 commits
2490eb2
3e15824
bbe7cc7
7f2a979
3390548
eaee181
dcd1ff5
399f4ea
fb0a8e6
b154d06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Add the _check_link_targetfunction to validate the file pointed to by a symlink. If path traversal | ||
| is detected, it should raise an InstallationErrorexception, similar to is_within_directory. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from _pytest.monkeypatch import MonkeyPatch | ||
|
|
||
| from pip._internal.exceptions import InstallationError | ||
| from pip._internal.utils.unpacking import is_within_directory, untar_file, unzip_file | ||
|
|
@@ -238,6 +239,141 @@ def test_unpack_tar_links(self, input_prefix: str, unpack_prefix: str) -> None: | |
| with open(os.path.join(unpack_dir, "symlink.txt"), "rb") as f: | ||
| assert f.read() == content | ||
|
|
||
| def test_unpack_normal_tar_link1_no_data_filter( | ||
| self, monkeypatch: MonkeyPatch | ||
| ) -> None: | ||
| """ | ||
| Test unpacking a normal tar with file containing soft links, but no data_filter | ||
| """ | ||
| if hasattr(tarfile, "data_filter"): | ||
| monkeypatch.delattr("tarfile.data_filter") | ||
|
|
||
| tar_filename = "test_tar_links_no_data_filter.tar" | ||
| tar_filepath = os.path.join(self.tempdir, tar_filename) | ||
|
|
||
| extract_path = os.path.join(self.tempdir, "extract_path") | ||
|
|
||
| with tarfile.open(tar_filepath, "w") as tar: | ||
| file_data = io.BytesIO(b"normal\n") | ||
| normal_file_tarinfo = tarfile.TarInfo(name="normal_file") | ||
| normal_file_tarinfo.size = len(file_data.getbuffer()) | ||
| tar.addfile(normal_file_tarinfo, fileobj=file_data) | ||
|
|
||
| info = tarfile.TarInfo("normal_symlink") | ||
| info.type = tarfile.SYMTYPE | ||
| info.linkpath = "normal_file" | ||
| tar.addfile(info) | ||
|
|
||
| untar_file(tar_filepath, extract_path) | ||
|
|
||
| assert os.path.islink(os.path.join(extract_path, "normal_symlink")) | ||
|
|
||
| link_path = os.readlink(os.path.join(extract_path, "normal_symlink")) | ||
| assert link_path == "normal_file" | ||
|
|
||
| with open(os.path.join(extract_path, "normal_symlink"), "rb") as f: | ||
| assert f.read() == b"normal\n" | ||
|
|
||
| def test_unpack_normal_tar_link2_no_data_filter( | ||
| self, monkeypatch: MonkeyPatch | ||
| ) -> None: | ||
| """ | ||
| Test unpacking a normal tar with file containing soft links, but no data_filter | ||
| """ | ||
| if hasattr(tarfile, "data_filter"): | ||
| monkeypatch.delattr("tarfile.data_filter") | ||
|
|
||
| tar_filename = "test_tar_links_no_data_filter.tar" | ||
| tar_filepath = os.path.join(self.tempdir, tar_filename) | ||
|
|
||
| extract_path = os.path.join(self.tempdir, "extract_path") | ||
|
|
||
| with tarfile.open(tar_filepath, "w") as tar: | ||
| file_data = io.BytesIO(b"normal\n") | ||
| normal_file_tarinfo = tarfile.TarInfo(name="normal_file") | ||
| normal_file_tarinfo.size = len(file_data.getbuffer()) | ||
| tar.addfile(normal_file_tarinfo, fileobj=file_data) | ||
|
|
||
| info = tarfile.TarInfo("sub/normal_symlink") | ||
| info.type = tarfile.SYMTYPE | ||
| info.linkpath = ".." + os.path.sep + "normal_file" | ||
| tar.addfile(info) | ||
|
|
||
| untar_file(tar_filepath, extract_path) | ||
|
|
||
| assert os.path.islink(os.path.join(extract_path, "sub", "normal_symlink")) | ||
|
|
||
| link_path = os.readlink(os.path.join(extract_path, "sub", "normal_symlink")) | ||
| assert link_path == ".." + os.path.sep + "normal_file" | ||
|
|
||
| with open(os.path.join(extract_path, "sub", "normal_symlink"), "rb") as f: | ||
| assert f.read() == b"normal\n" | ||
|
|
||
| def test_unpack_evil_tar_link1_no_data_filter( | ||
| self, monkeypatch: MonkeyPatch | ||
| ) -> None: | ||
| """ | ||
| Test unpacking a evil tar with file containing soft links, but no data_filter | ||
| """ | ||
| if hasattr(tarfile, "data_filter"): | ||
| monkeypatch.delattr("tarfile.data_filter") | ||
|
|
||
| tar_filename = "test_tar_links_no_data_filter.tar" | ||
| tar_filepath = os.path.join(self.tempdir, tar_filename) | ||
|
|
||
| import_filename = "import_file" | ||
| import_filepath = os.path.join(self.tempdir, import_filename) | ||
| open(import_filepath, "w").close() | ||
|
|
||
| extract_path = os.path.join(self.tempdir, "extract_path") | ||
|
|
||
| with tarfile.open(tar_filepath, "w") as tar: | ||
| info = tarfile.TarInfo("evil_symlink") | ||
| info.type = tarfile.SYMTYPE | ||
| info.linkpath = import_filepath | ||
| tar.addfile(info) | ||
|
|
||
| with pytest.raises(InstallationError) as e: | ||
| untar_file(tar_filepath, extract_path) | ||
|
|
||
| assert "trying to install outside target directory" in str(e.value) | ||
|
||
| assert "import_file" in str(e.value) | ||
|
|
||
| assert not os.path.exists(os.path.join(extract_path, "evil_symlink")) | ||
|
|
||
| def test_unpack_evil_tar_link2_no_data_filter( | ||
| self, monkeypatch: MonkeyPatch | ||
| ) -> None: | ||
| """ | ||
| Test unpacking a evil tar with file containing soft links, but no data_filter | ||
| """ | ||
| if hasattr(tarfile, "data_filter"): | ||
| monkeypatch.delattr("tarfile.data_filter") | ||
|
|
||
| tar_filename = "test_tar_links_no_data_filter.tar" | ||
| tar_filepath = os.path.join(self.tempdir, tar_filename) | ||
|
|
||
| import_filename = "import_file" | ||
| import_filepath = os.path.join(self.tempdir, import_filename) | ||
| open(import_filepath, "w").close() | ||
|
|
||
| extract_path = os.path.join(self.tempdir, "extract_path") | ||
|
|
||
| with tarfile.open(tar_filepath, "w") as tar: | ||
| info = tarfile.TarInfo("evil_symlink") | ||
| info.type = tarfile.SYMTYPE | ||
| info.linkpath = ".." + os.sep + import_filename | ||
| tar.addfile(info) | ||
|
|
||
| with pytest.raises(InstallationError) as e: | ||
| untar_file(tar_filepath, extract_path) | ||
|
|
||
| assert "trying to install outside target directory" in str(e.value) | ||
| assert ".." in str(e.value) | ||
| assert import_filename in str(e.value) | ||
|
|
||
| assert not os.path.exists(os.path.join(extract_path, "evil_symlink")) | ||
|
|
||
|
|
||
| def test_unpack_tar_unicode(tmpdir: Path) -> None: | ||
| test_tar = tmpdir / "test.tar" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.