From fb47567f8882446d975b2b63b4cfa3cbc91185fa Mon Sep 17 00:00:00 2001 From: chenjianhu Date: Tue, 21 Oct 2025 16:42:14 +0800 Subject: [PATCH] Fix CVE-2025-8869 --- backport-CVE-2025-8869.patch | 272 +++++++++++++++++++++++++++++++++++ python-pip.spec | 6 +- 2 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2025-8869.patch diff --git a/backport-CVE-2025-8869.patch b/backport-CVE-2025-8869.patch new file mode 100644 index 0000000..14961f9 --- /dev/null +++ b/backport-CVE-2025-8869.patch @@ -0,0 +1,272 @@ +From 8783cd84d489bdd6ebef0a13bd1a01e540bdf37f Mon Sep 17 00:00:00 2001 +From: chenjianhu +Date: Tue, 21 Oct 2025 16:39:09 +0800 +Subject: [PATCH] Fix CVE-2025-8869 + +patch1: +From 12c171f193b7b38329a75bbaecabca814831d5e5 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Mon, 6 May 2024 13:20:47 +0200 +Subject: [PATCH] Use the ``data_filter`` when extracting tarballs, if it's + available. (#12214) + +Previous behaviour is used on Python without PEP-721 tarfile filters. (Note that the +feature is now in security releases of all supported versions.) + +A custom filter (which wraps `data_filter`) is used to retain pip-specific behaviour: +- Removing a common leading directory +- Setting the mode (Unix permissions) + +Compared to the previous behaviour, if a file can't be unpacked, the unpacking operation +will fail with `InstallError`, rather than skipping the individual file with +a `logger.warning`. This means that "some corrupt tar files" now can't be unpacked. + +Note that PEP 721 limits itself to sdists, this change affects unpacking any +other tar file. + +patch2: +From ea8941d5b2a215d1d990f7f171e10fe9d27f6825 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Wed, 26 Jun 2024 14:59:49 +0200 +Subject: [PATCH] untar_file: remove common leading directory before unpacking + +Fixes: #12781 +Fix finding hardlink targets in tar files with an ignored top-level directory. +--- + src/pip/_internal/utils/unpacking.py | 185 +++++++++++++++++++-------- + 1 file changed, 131 insertions(+), 54 deletions(-) + +diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py +index 620f31e..60e9010 100644 +--- a/src/pip/_internal/utils/unpacking.py ++++ b/src/pip/_internal/utils/unpacking.py +@@ -7,6 +7,7 @@ import logging + import os + import shutil + import stat ++import sys + import tarfile + import zipfile + +@@ -96,13 +97,17 @@ def is_within_directory(directory, target): + return prefix == abs_directory + + ++def _get_default_mode_plus_executable(): ++ return 0o777 & ~current_umask() | 0o111 ++ ++ + def set_extracted_file_to_default_mode_plus_executable(path): + # type: (Union[str, Text]) -> None + """ + Make file present at path have execute for user/group/world + (chmod +x) is no-op on windows per python docs + """ +- os.chmod(path, (0o777 & ~current_umask() | 0o111)) ++ os.chmod(path, _get_default_mode_plus_executable()) + + + def zip_item_is_executable(info): +@@ -166,8 +171,8 @@ def untar_file(filename, location): + Untar the file (with path `filename`) to the destination `location`. + All files are written based on system defaults and umask (i.e. permissions + are not preserved), except that regular file members with any execute +- permissions (user, group, or world) have "chmod +x" applied after being +- written. Note that for windows, any execute changes using os.chmod are ++ permissions (user, group, or world) have "chmod +x" applied on top of the ++ default. Note that for windows, any execute changes using os.chmod are + no-ops per the python docs. + """ + ensure_dir(location) +@@ -184,65 +189,137 @@ def untar_file(filename, location): + 'Cannot determine compression type for file %s', filename, + ) + mode = 'r:*' +- tar = tarfile.open(filename, mode) ++ ++ tar = tarfile.open(filename, mode, encoding="utf-8") # type: ignore + try: +- leading = has_leading_dir([ +- member.name for member in tar.getmembers() +- ]) +- for member in tar.getmembers(): +- fn = member.name ++ leading = has_leading_dir([member.name for member in tar.getmembers()]) ++ ++ # PEP 706 added `tarfile.data_filter`, and made some other changes to ++ # Python's tarfile module (see below). The features were backported to ++ # security releases. ++ try: ++ data_filter = tarfile.data_filter ++ except AttributeError: ++ _untar_without_filter(filename, location, tar, leading) ++ else: ++ default_mode_plus_executable = _get_default_mode_plus_executable() ++ + if leading: +- # https://github.com/python/mypy/issues/1174 +- fn = split_leading_dir(fn)[1] # type: ignore +- path = os.path.join(location, fn) +- if not is_within_directory(location, path): +- message = ( +- 'The tar file ({}) has a file ({}) trying to install ' +- 'outside target directory ({})' +- ) +- raise InstallationError( +- message.format(filename, path, location) +- ) +- if member.isdir(): +- ensure_dir(path) +- elif member.issym(): +- try: +- # https://github.com/python/typeshed/issues/2673 +- tar._extract_member(member, path) # type: ignore +- except Exception as exc: +- # Some corrupt tar files seem to produce this +- # (specifically bad symlinks) +- logger.warning( +- 'In the tar file %s the member %s is invalid: %s', +- filename, member.name, exc, +- ) +- continue +- else: ++ # Strip the leading directory from all files in the archive, ++ # including hardlink targets (which are relative to the ++ # unpack location). ++ for member in tar.getmembers(): ++ name_lead, name_rest = split_leading_dir(member.name) ++ member.name = name_rest ++ if member.islnk(): ++ lnk_lead, lnk_rest = split_leading_dir(member.linkname) ++ if lnk_lead == name_lead: ++ member.linkname = lnk_rest ++ ++ def pip_filter(member, path): ++ orig_mode = member.mode + try: +- fp = tar.extractfile(member) +- except (KeyError, AttributeError) as exc: +- # Some corrupt tar files seem to produce this +- # (specifically bad symlinks) +- logger.warning( +- 'In the tar file %s the member %s is invalid: %s', +- filename, member.name, exc, ++ try: ++ member = data_filter(member, location) ++ except tarfile.LinkOutsideDestinationError: ++ if sys.version_info[:3] in { ++ (3, 8, 17), ++ (3, 9, 17), ++ (3, 10, 12), ++ (3, 11, 4), ++ }: ++ # The tarfile filter in specific Python versions ++ # raises LinkOutsideDestinationError on valid input ++ # (https://github.com/python/cpython/issues/107845) ++ # Ignore the error there, but do use the ++ # more lax `tar_filter` ++ member = tarfile.tar_filter(member, location) ++ else: ++ raise ++ except tarfile.TarError as exc: ++ message = "Invalid member in the tar file {}: {}" ++ # Filter error messages mention the member name. ++ # No need to add it here. ++ raise InstallationError( ++ message.format( ++ filename, ++ exc, ++ ) + ) +- continue +- ensure_dir(os.path.dirname(path)) +- assert fp is not None +- with open(path, 'wb') as destfp: +- shutil.copyfileobj(fp, destfp) +- fp.close() +- # Update the timestamp (useful for cython compiled files) +- # https://github.com/python/typeshed/issues/2673 +- tar.utime(member, path) # type: ignore +- # member have any execute permissions for user/group/world? +- if member.mode & 0o111: +- set_extracted_file_to_default_mode_plus_executable(path) ++ if member.isfile() and orig_mode & 0o111: ++ member.mode = default_mode_plus_executable ++ else: ++ # See PEP 706 note above. ++ # The PEP changed this from `int` to `Optional[int]`, ++ # where None means "use the default". Mypy doesn't ++ # know this yet. ++ member.mode = None # type: ignore [assignment] ++ return member ++ ++ tar.extractall(location, filter=pip_filter) ++ + finally: + tar.close() + + ++def _untar_without_filter( ++ filename, ++ location, ++ tar, ++ leading, ++): ++ """Fallback for Python without tarfile.data_filter""" ++ for member in tar.getmembers(): ++ fn = member.name ++ if leading: ++ fn = split_leading_dir(fn)[1] ++ path = os.path.join(location, fn) ++ if not is_within_directory(location, path): ++ message = ( ++ "The tar file ({}) has a file ({}) trying to install " ++ "outside target directory ({})" ++ ) ++ raise InstallationError(message.format(filename, path, location)) ++ if member.isdir(): ++ ensure_dir(path) ++ elif member.issym(): ++ try: ++ tar._extract_member(member, path) ++ except Exception as exc: ++ # Some corrupt tar files seem to produce this ++ # (specifically bad symlinks) ++ logger.warning( ++ "In the tar file %s the member %s is invalid: %s", ++ filename, ++ member.name, ++ exc, ++ ) ++ continue ++ else: ++ try: ++ fp = tar.extractfile(member) ++ except (KeyError, AttributeError) as exc: ++ # Some corrupt tar files seem to produce this ++ # (specifically bad symlinks) ++ logger.warning( ++ "In the tar file %s the member %s is invalid: %s", ++ filename, ++ member.name, ++ exc, ++ ) ++ continue ++ ensure_dir(os.path.dirname(path)) ++ assert fp is not None ++ with open(path, "wb") as destfp: ++ shutil.copyfileobj(fp, destfp) ++ fp.close() ++ # Update the timestamp (useful for cython compiled files) ++ tar.utime(member, path) ++ # member have any execute permissions for user/group/world? ++ if member.mode & 0o111: ++ set_extracted_file_to_default_mode_plus_executable(path) ++ ++ + def unpack_file( + filename, # type: str + location, # type: str +-- +2.43.0 + diff --git a/python-pip.spec b/python-pip.spec index 148bbd6..34de0c6 100644 --- a/python-pip.spec +++ b/python-pip.spec @@ -7,7 +7,7 @@ pip is the package installer for Python. You can use pip to install packages fro %global bashcompdir %(b=$(pkg-config --variable=completionsdir bash-completion 2>/dev/null); echo ${b:-%{_sysconfdir}/bash_completion.d}) Name: python-%{srcname} Version: 20.2.2 -Release: 12 +Release: 13 Summary: A tool for installing and managing Python packages License: MIT and Python and ASL 2.0 and BSD and ISC and LGPLv2 and MPLv2.0 and (ASL 2.0 or BSD) URL: http://www.pip-installer.org @@ -27,6 +27,7 @@ Patch6007: backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redi Patch6008: backport-CVE-2024-47081.patch Patch6009: backport-CVE-2025-50181.patch Patch6010: backport-CVE-2023-32681.patch +Patch6011: backport-CVE-2025-8869.patch Source1: pip-allow-older-versions.patch @@ -164,6 +165,9 @@ install -p dist/%{python_wheelname} -t %{buildroot}%{python_wheeldir} %{python_wheeldir}/%{python_wheelname} %changelog +* Mon Oct 20 2025 chenjianhu - 20.2.2-13 +- Fix CVE-2025-8869 + * Thu Sep 18 2025 xieyanlong - 20.2.2-12 - Fix CVE-2023-32681 -- Gitee