Skip to content

Commit dcd1ff5

Browse files
committed
Replace _check_link_target with is_symlink_target_in_tar​​;
​​Add deprecation note for _untar_without_filter for future removal​
1 parent eaee181 commit dcd1ff5

File tree

2 files changed

+24
-29
lines changed

2 files changed

+24
-29
lines changed

news/13550.bugfix.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Add the _check_link_targetfunction to validate the file pointed to by a symlink. If path traversal
2-
is detected, it should raise an InstallationErrorexception, similar to is_within_directory.
1+
Pip will now raise an installation error for a source distribution when it includes a symlink that
2+
points outside the source distribution archive.

src/pip/_internal/utils/unpacking.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -248,36 +248,31 @@ def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo:
248248
tar.close()
249249

250250

251+
def is_symlink_target_in_tar(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> bool:
252+
"""Check if the file pointed to by the symbolic link is in the tar archive"""
253+
linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname)
254+
255+
linkname = os.path.normpath(linkname)
256+
if "\\" in linkname:
257+
linkname = linkname.replace("\\", "/")
258+
259+
try:
260+
tar.getmember(linkname)
261+
return True
262+
except KeyError:
263+
return False
264+
265+
251266
def _untar_without_filter(
252267
filename: str,
253268
location: str,
254269
tar: tarfile.TarFile,
255270
leading: bool,
256271
) -> None:
257272
"""Fallback for Python without tarfile.data_filter"""
258-
259-
def _check_link_target(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> None:
260-
linkname = "/".join(
261-
filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname))
262-
)
263-
264-
linkname = os.path.normpath(linkname)
265-
266-
try:
267-
tar.getmember(linkname)
268-
except KeyError:
269-
if "\\" in linkname or "/" in linkname:
270-
if "\\" in linkname:
271-
linkname = linkname.replace("\\", "/")
272-
else:
273-
linkname = linkname.replace("/", "\\")
274-
try:
275-
tar.getmember(linkname)
276-
except KeyError:
277-
raise KeyError(linkname)
278-
else:
279-
raise KeyError(linkname)
280-
273+
# NOTE: This function can be removed once pip requires CPython ≥ 3.12.​
274+
# PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
275+
# This feature is fully supported from CPython 3.12 onward.
281276
for member in tar.getmembers():
282277
fn = member.name
283278
if leading:
@@ -292,14 +287,14 @@ def _check_link_target(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> None:
292287
if member.isdir():
293288
ensure_dir(path)
294289
elif member.issym():
295-
try:
296-
_check_link_target(tar, member)
297-
except KeyError as exc:
290+
if not is_symlink_target_in_tar(tar, member):
298291
message = (
299292
"The tar file ({}) has a file ({}) trying to install "
300293
"outside target directory ({})"
301294
)
302-
raise InstallationError(message.format(filename, member.name, exc))
295+
raise InstallationError(
296+
message.format(filename, member.name, member.linkname)
297+
)
303298
try:
304299
tar._extract_member(member, path)
305300
except Exception as exc:

0 commit comments

Comments
 (0)