diff --git a/backport-CVE-2025-8869.patch b/backport-CVE-2025-8869.patch new file mode 100644 index 0000000000000000000000000000000000000000..20da5eb6fdee122a3e698edb2991326cf9cdfe50 --- /dev/null +++ b/backport-CVE-2025-8869.patch @@ -0,0 +1,267 @@ +From 32be901b6cc685aa07bdb4f5b681d1fae6ec6b15 Mon Sep 17 00:00:00 2001 +From: chenjianhu +Date: Tue, 25 Nov 2025 14:56:43 +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 | 178 +++++++++++++++++++-------- + 1 file changed, 129 insertions(+), 49 deletions(-) + +diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py +index 78b5c13..875e30e 100644 +--- a/src/pip/_internal/utils/unpacking.py ++++ b/src/pip/_internal/utils/unpacking.py +@@ -5,6 +5,7 @@ import logging + import os + import shutil + import stat ++import sys + import tarfile + import zipfile + from typing import Iterable, List, Optional +@@ -85,12 +86,16 @@ def is_within_directory(directory: str, target: str) -> bool: + return prefix == abs_directory + + ++def _get_default_mode_plus_executable() -> int: ++ return 0o777 & ~current_umask() | 0o111 ++ ++ + def set_extracted_file_to_default_mode_plus_executable(path: str) -> 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: ZipInfo) -> bool: +@@ -151,8 +156,8 @@ def untar_file(filename: str, location: str) -> None: + 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) +@@ -170,62 +175,137 @@ def untar_file(filename: str, location: str) -> None: + filename, + ) + mode = "r:*" ++ + tar = tarfile.open(filename, mode, encoding="utf-8") + try: + leading = has_leading_dir([member.name for member in tar.getmembers()]) +- for member in tar.getmembers(): +- fn = member.name ++ ++ # 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: +- 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: ++ # 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: tarfile.TarInfo, path: str) -> tarfile.TarInfo: ++ 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) +- 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) ++ 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: str, ++ location: str, ++ tar: tarfile.TarFile, ++ leading: bool, ++) -> None: ++ """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: str, + location: str, +-- +2.43.0 + diff --git a/python-pip.spec b/python-pip.spec index 6549dd46e5eabee19803c2ca7c44e1937c82649e..3862709be6c1559a43de734ac08514d448c1caeb 100644 --- a/python-pip.spec +++ b/python-pip.spec @@ -6,7 +6,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: 23.3.1 -Release: 5 +Release: 6 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 @@ -19,6 +19,7 @@ Patch6001: backport-CVE-2023-45803-Made-body-stripped-from-HTTP-requests.pa Patch6002: backport-CVE-2024-37891-Strip-Proxy-Authorization-header-on-redirects.patch Patch6003: backport-CVE-2024-47081.patch Patch6004: backport-CVE-2025-50181.patch +Patch6005: backport-CVE-2025-8869.patch Source10: pip-allow-older-versions.patch @@ -136,6 +137,9 @@ install -D -m0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/pip.conf %{python_wheeldir}/%{python_wheelname} %changelog +* Tue Nov 25 2025 chenjianhu - 23.3.1-6 +- Fix CVE-2025-8869 + * Wed Sep 10 2025 xiangyuning - 23.3.1-5 - Fix CVE-2025-50181 @@ -234,7 +238,7 @@ install -D -m0644 %{SOURCE1} %{buildroot}%{_sysconfdir}/pip.conf * Sat Nov 16 2019 huzhiyu - 18.0-11 - fix ca-certificate bad requires -* Fri Oct 15 2019 lvying - 18.0-10 +* Tue Oct 15 2019 lvying - 18.0-10 - Type:enhancement - ID:NA - SUG:NA