Compare commits
11 Commits
Author | SHA256 | Date | |
---|---|---|---|
efa8febd12 | |||
59c649b520 | |||
8a73c83002 | |||
db9a09b8c2 | |||
1716dfe088 | |||
e761be5380 | |||
47f6e77cdd | |||
8a3ae3ab7b | |||
c2077eafb7 | |||
|
3d3eac0a3e | ||
f4d7952bf7 |
335
CVE-2024-6923-email-hdr-inject.patch
Normal file
335
CVE-2024-6923-email-hdr-inject.patch
Normal file
@ -0,0 +1,335 @@
|
||||
From ff3629b3cedf5e50a7a5f567283778fe5539a11e Mon Sep 17 00:00:00 2001
|
||||
From: Petr Viktorin <encukou@gmail.com>
|
||||
Date: Wed, 31 Jul 2024 00:19:48 +0200
|
||||
Subject: [PATCH] [3.10] gh-121650: Encode newlines in headers, and verify
|
||||
headers are sound (GH-122233)
|
||||
|
||||
Per RFC 2047:
|
||||
|
||||
> [...] these encoding schemes allow the
|
||||
> encoding of arbitrary octet values, mail readers that implement this
|
||||
> decoding should also ensure that display of the decoded data on the
|
||||
> recipient's terminal will not cause unwanted side-effects
|
||||
|
||||
It seems that the "quoted-word" scheme is a valid way to include
|
||||
a newline character in a header value, just like we already allow
|
||||
undecodable bytes or control characters.
|
||||
They do need to be properly quoted when serialized to text, though.
|
||||
|
||||
This should fail for custom fold() implementations that aren't careful
|
||||
about newlines.
|
||||
|
||||
(cherry picked from commit 097633981879b3c9de9a1dd120d3aa585ecc2384)
|
||||
|
||||
Co-authored-by: Petr Viktorin <encukou@gmail.com>
|
||||
Co-authored-by: Bas Bloemsaat <bas@bloemsaat.org>
|
||||
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
|
||||
---
|
||||
Doc/library/email.errors.rst | 6
|
||||
Doc/library/email.policy.rst | 18 ++
|
||||
Doc/whatsnew/3.10.rst | 12 +
|
||||
Lib/email/_header_value_parser.py | 12 +
|
||||
Lib/email/_policybase.py | 8 +
|
||||
Lib/email/errors.py | 4
|
||||
Lib/email/generator.py | 13 +-
|
||||
Lib/test/test_email/test_generator.py | 62 ++++++++++
|
||||
Lib/test/test_email/test_policy.py | 26 ++++
|
||||
Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst | 5
|
||||
10 files changed, 162 insertions(+), 4 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
|
||||
|
||||
--- a/Doc/library/email.errors.rst
|
||||
+++ b/Doc/library/email.errors.rst
|
||||
@@ -59,6 +59,12 @@ The following exception classes are defi
|
||||
:class:`~email.mime.image.MIMEImage`).
|
||||
|
||||
|
||||
+.. exception:: HeaderWriteError()
|
||||
+
|
||||
+ Raised when an error occurs when the :mod:`~email.generator` outputs
|
||||
+ headers.
|
||||
+
|
||||
+
|
||||
Here is the list of the defects that the :class:`~email.parser.FeedParser`
|
||||
can find while parsing messages. Note that the defects are added to the message
|
||||
where the problem was found, so for example, if a message nested inside a
|
||||
--- a/Doc/library/email.policy.rst
|
||||
+++ b/Doc/library/email.policy.rst
|
||||
@@ -229,6 +229,24 @@ added matters. To illustrate::
|
||||
|
||||
.. versionadded:: 3.6
|
||||
|
||||
+
|
||||
+ .. attribute:: verify_generated_headers
|
||||
+
|
||||
+ If ``True`` (the default), the generator will raise
|
||||
+ :exc:`~email.errors.HeaderWriteError` instead of writing a header
|
||||
+ that is improperly folded or delimited, such that it would
|
||||
+ be parsed as multiple headers or joined with adjacent data.
|
||||
+ Such headers can be generated by custom header classes or bugs
|
||||
+ in the ``email`` module.
|
||||
+
|
||||
+ As it's a security feature, this defaults to ``True`` even in the
|
||||
+ :class:`~email.policy.Compat32` policy.
|
||||
+ For backwards compatible, but unsafe, behavior, it must be set to
|
||||
+ ``False`` explicitly.
|
||||
+
|
||||
+ .. versionadded:: 3.10.15
|
||||
+
|
||||
+
|
||||
The following :class:`Policy` method is intended to be called by code using
|
||||
the email library to create policy instances with custom settings:
|
||||
|
||||
--- a/Doc/whatsnew/3.10.rst
|
||||
+++ b/Doc/whatsnew/3.10.rst
|
||||
@@ -2357,3 +2357,15 @@ ipaddress
|
||||
|
||||
* Fixed ``is_global`` and ``is_private`` behavior in ``IPv4Address``,
|
||||
``IPv6Address``, ``IPv4Network`` and ``IPv6Network``.
|
||||
+
|
||||
+email
|
||||
+-----
|
||||
+
|
||||
+* Headers with embedded newlines are now quoted on output.
|
||||
+
|
||||
+ The :mod:`~email.generator` will now refuse to serialize (write) headers
|
||||
+ that are improperly folded or delimited, such that they would be parsed as
|
||||
+ multiple headers or joined with adjacent data.
|
||||
+ If you need to turn this safety feature off,
|
||||
+ set :attr:`~email.policy.Policy.verify_generated_headers`.
|
||||
+ (Contributed by Bas Bloemsaat and Petr Viktorin in :gh:`121650`.)
|
||||
--- a/Lib/email/_header_value_parser.py
|
||||
+++ b/Lib/email/_header_value_parser.py
|
||||
@@ -92,6 +92,8 @@ TOKEN_ENDS = TSPECIALS | WSP
|
||||
ASPECIALS = TSPECIALS | set("*'%")
|
||||
ATTRIBUTE_ENDS = ASPECIALS | WSP
|
||||
EXTENDED_ATTRIBUTE_ENDS = ATTRIBUTE_ENDS - set('%')
|
||||
+NLSET = {'\n', '\r'}
|
||||
+SPECIALSNL = SPECIALS | NLSET
|
||||
|
||||
def quote_string(value):
|
||||
return '"'+str(value).replace('\\', '\\\\').replace('"', r'\"')+'"'
|
||||
@@ -2778,9 +2780,13 @@ def _refold_parse_tree(parse_tree, *, po
|
||||
wrap_as_ew_blocked -= 1
|
||||
continue
|
||||
tstr = str(part)
|
||||
- if part.token_type == 'ptext' and set(tstr) & SPECIALS:
|
||||
- # Encode if tstr contains special characters.
|
||||
- want_encoding = True
|
||||
+ if not want_encoding:
|
||||
+ if part.token_type == 'ptext':
|
||||
+ # Encode if tstr contains special characters.
|
||||
+ want_encoding = not SPECIALSNL.isdisjoint(tstr)
|
||||
+ else:
|
||||
+ # Encode if tstr contains newlines.
|
||||
+ want_encoding = not NLSET.isdisjoint(tstr)
|
||||
try:
|
||||
tstr.encode(encoding)
|
||||
charset = encoding
|
||||
--- a/Lib/email/_policybase.py
|
||||
+++ b/Lib/email/_policybase.py
|
||||
@@ -157,6 +157,13 @@ class Policy(_PolicyBase, metaclass=abc.
|
||||
message_factory -- the class to use to create new message objects.
|
||||
If the value is None, the default is Message.
|
||||
|
||||
+ verify_generated_headers
|
||||
+ -- if true, the generator verifies that each header
|
||||
+ they are properly folded, so that a parser won't
|
||||
+ treat it as multiple headers, start-of-body, or
|
||||
+ part of another header.
|
||||
+ This is a check against custom Header & fold()
|
||||
+ implementations.
|
||||
"""
|
||||
|
||||
raise_on_defect = False
|
||||
@@ -165,6 +172,7 @@ class Policy(_PolicyBase, metaclass=abc.
|
||||
max_line_length = 78
|
||||
mangle_from_ = False
|
||||
message_factory = None
|
||||
+ verify_generated_headers = True
|
||||
|
||||
def handle_defect(self, obj, defect):
|
||||
"""Based on policy, either raise defect or call register_defect.
|
||||
--- a/Lib/email/errors.py
|
||||
+++ b/Lib/email/errors.py
|
||||
@@ -29,6 +29,10 @@ class CharsetError(MessageError):
|
||||
"""An illegal charset was given."""
|
||||
|
||||
|
||||
+class HeaderWriteError(MessageError):
|
||||
+ """Error while writing headers."""
|
||||
+
|
||||
+
|
||||
# These are parsing defects which the parser was able to work around.
|
||||
class MessageDefect(ValueError):
|
||||
"""Base class for a message defect."""
|
||||
--- a/Lib/email/generator.py
|
||||
+++ b/Lib/email/generator.py
|
||||
@@ -14,12 +14,14 @@ import random
|
||||
from copy import deepcopy
|
||||
from io import StringIO, BytesIO
|
||||
from email.utils import _has_surrogates
|
||||
+from email.errors import HeaderWriteError
|
||||
|
||||
UNDERSCORE = '_'
|
||||
NL = '\n' # XXX: no longer used by the code below.
|
||||
|
||||
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||
fcre = re.compile(r'^From ', re.MULTILINE)
|
||||
+NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
||||
|
||||
|
||||
|
||||
@@ -223,7 +225,16 @@ class Generator:
|
||||
|
||||
def _write_headers(self, msg):
|
||||
for h, v in msg.raw_items():
|
||||
- self.write(self.policy.fold(h, v))
|
||||
+ folded = self.policy.fold(h, v)
|
||||
+ if self.policy.verify_generated_headers:
|
||||
+ linesep = self.policy.linesep
|
||||
+ if not folded.endswith(self.policy.linesep):
|
||||
+ raise HeaderWriteError(
|
||||
+ f'folded header does not end with {linesep!r}: {folded!r}')
|
||||
+ if NEWLINE_WITHOUT_FWSP.search(folded.removesuffix(linesep)):
|
||||
+ raise HeaderWriteError(
|
||||
+ f'folded header contains newline: {folded!r}')
|
||||
+ self.write(folded)
|
||||
# A blank line always separates headers from body
|
||||
self.write(self._NL)
|
||||
|
||||
--- a/Lib/test/test_email/test_generator.py
|
||||
+++ b/Lib/test/test_email/test_generator.py
|
||||
@@ -6,6 +6,7 @@ from email.message import EmailMessage
|
||||
from email.generator import Generator, BytesGenerator
|
||||
from email.headerregistry import Address
|
||||
from email import policy
|
||||
+import email.errors
|
||||
from test.test_email import TestEmailBase, parameterize
|
||||
|
||||
|
||||
@@ -216,6 +217,44 @@ class TestGeneratorBase:
|
||||
g.flatten(msg)
|
||||
self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
|
||||
+ def test_keep_encoded_newlines(self):
|
||||
+ msg = self.msgmaker(self.typ(textwrap.dedent("""\
|
||||
+ To: nobody
|
||||
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
|
||||
+
|
||||
+ None
|
||||
+ """)))
|
||||
+ expected = textwrap.dedent("""\
|
||||
+ To: nobody
|
||||
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
|
||||
+
|
||||
+ None
|
||||
+ """)
|
||||
+ s = self.ioclass()
|
||||
+ g = self.genclass(s, policy=self.policy.clone(max_line_length=80))
|
||||
+ g.flatten(msg)
|
||||
+ self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
+
|
||||
+ def test_keep_long_encoded_newlines(self):
|
||||
+ msg = self.msgmaker(self.typ(textwrap.dedent("""\
|
||||
+ To: nobody
|
||||
+ Subject: Bad subject=?UTF-8?Q?=0A?=Bcc: injection@example.com
|
||||
+
|
||||
+ None
|
||||
+ """)))
|
||||
+ expected = textwrap.dedent("""\
|
||||
+ To: nobody
|
||||
+ Subject: Bad subject
|
||||
+ =?utf-8?q?=0A?=Bcc:
|
||||
+ injection@example.com
|
||||
+
|
||||
+ None
|
||||
+ """)
|
||||
+ s = self.ioclass()
|
||||
+ g = self.genclass(s, policy=self.policy.clone(max_line_length=30))
|
||||
+ g.flatten(msg)
|
||||
+ self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
+
|
||||
|
||||
class TestGenerator(TestGeneratorBase, TestEmailBase):
|
||||
|
||||
@@ -224,6 +263,29 @@ class TestGenerator(TestGeneratorBase, T
|
||||
ioclass = io.StringIO
|
||||
typ = str
|
||||
|
||||
+ def test_verify_generated_headers(self):
|
||||
+ """gh-121650: by default the generator prevents header injection"""
|
||||
+ class LiteralHeader(str):
|
||||
+ name = 'Header'
|
||||
+ def fold(self, **kwargs):
|
||||
+ return self
|
||||
+
|
||||
+ for text in (
|
||||
+ 'Value\r\nBad Injection\r\n',
|
||||
+ 'NoNewLine'
|
||||
+ ):
|
||||
+ with self.subTest(text=text):
|
||||
+ message = message_from_string(
|
||||
+ "Header: Value\r\n\r\nBody",
|
||||
+ policy=self.policy,
|
||||
+ )
|
||||
+
|
||||
+ del message['Header']
|
||||
+ message['Header'] = LiteralHeader(text)
|
||||
+
|
||||
+ with self.assertRaises(email.errors.HeaderWriteError):
|
||||
+ message.as_string()
|
||||
+
|
||||
|
||||
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
|
||||
|
||||
--- a/Lib/test/test_email/test_policy.py
|
||||
+++ b/Lib/test/test_email/test_policy.py
|
||||
@@ -26,6 +26,7 @@ class PolicyAPITests(unittest.TestCase):
|
||||
'raise_on_defect': False,
|
||||
'mangle_from_': True,
|
||||
'message_factory': None,
|
||||
+ 'verify_generated_headers': True,
|
||||
}
|
||||
# These default values are the ones set on email.policy.default.
|
||||
# If any of these defaults change, the docs must be updated.
|
||||
@@ -277,6 +278,31 @@ class PolicyAPITests(unittest.TestCase):
|
||||
with self.assertRaises(email.errors.HeaderParseError):
|
||||
policy.fold("Subject", subject)
|
||||
|
||||
+ def test_verify_generated_headers(self):
|
||||
+ """Turning protection off allows header injection"""
|
||||
+ policy = email.policy.default.clone(verify_generated_headers=False)
|
||||
+ for text in (
|
||||
+ 'Header: Value\r\nBad: Injection\r\n',
|
||||
+ 'Header: NoNewLine'
|
||||
+ ):
|
||||
+ with self.subTest(text=text):
|
||||
+ message = email.message_from_string(
|
||||
+ "Header: Value\r\n\r\nBody",
|
||||
+ policy=policy,
|
||||
+ )
|
||||
+ class LiteralHeader(str):
|
||||
+ name = 'Header'
|
||||
+ def fold(self, **kwargs):
|
||||
+ return self
|
||||
+
|
||||
+ del message['Header']
|
||||
+ message['Header'] = LiteralHeader(text)
|
||||
+
|
||||
+ self.assertEqual(
|
||||
+ message.as_string(),
|
||||
+ f"{text}\nBody",
|
||||
+ )
|
||||
+
|
||||
# XXX: Need subclassing tests.
|
||||
# For adding subclassed objects, make sure the usual rules apply (subclass
|
||||
# wins), but that the order still works (right overrides left).
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2024-07-27-16-10-41.gh-issue-121650.nf6oc9.rst
|
||||
@@ -0,0 +1,5 @@
|
||||
+:mod:`email` headers with embedded newlines are now quoted on output. The
|
||||
+:mod:`~email.generator` will now refuse to serialize (write) headers that
|
||||
+are unsafely folded or delimited; see
|
||||
+:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas
|
||||
+Bloemsaat and Petr Viktorin in :gh:`121650`.)
|
136
CVE-2024-8088-inf-loop-zipfile_Path.patch
Normal file
136
CVE-2024-8088-inf-loop-zipfile_Path.patch
Normal file
@ -0,0 +1,136 @@
|
||||
---
|
||||
Lib/test/test_zipfile.py | 75 ++++++++++
|
||||
Lib/zipfile.py | 10 +
|
||||
Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst | 1
|
||||
Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst | 3
|
||||
4 files changed, 87 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/Lib/test/test_zipfile.py
|
||||
+++ b/Lib/test/test_zipfile.py
|
||||
@@ -3280,6 +3280,81 @@ with zipfile.ZipFile(io.BytesIO(), "w")
|
||||
zipfile.Path(zf)
|
||||
zf.extractall(source_path.parent)
|
||||
|
||||
+ def test_malformed_paths(self):
|
||||
+ """
|
||||
+ Path should handle malformed paths gracefully.
|
||||
+
|
||||
+ Paths with leading slashes are not visible.
|
||||
+
|
||||
+ Paths with dots are treated like regular files.
|
||||
+ """
|
||||
+ data = io.BytesIO()
|
||||
+ zf = zipfile.ZipFile(data, "w")
|
||||
+ zf.writestr("../parent.txt", b"content")
|
||||
+ zf.filename = ''
|
||||
+ root = zipfile.Path(zf)
|
||||
+ assert list(map(str, root.iterdir())) == ['../']
|
||||
+ assert root.joinpath('..').joinpath('parent.txt').read_bytes() == b'content'
|
||||
+
|
||||
+ def test_unsupported_names(self):
|
||||
+ """
|
||||
+ Path segments with special characters are readable.
|
||||
+
|
||||
+ On some platforms or file systems, characters like
|
||||
+ ``:`` and ``?`` are not allowed, but they are valid
|
||||
+ in the zip file.
|
||||
+ """
|
||||
+ data = io.BytesIO()
|
||||
+ zf = zipfile.ZipFile(data, "w")
|
||||
+ zf.writestr("path?", b"content")
|
||||
+ zf.writestr("V: NMS.flac", b"fLaC...")
|
||||
+ zf.filename = ''
|
||||
+ root = zipfile.Path(zf)
|
||||
+ contents = root.iterdir()
|
||||
+ assert next(contents).name == 'path?'
|
||||
+ assert next(contents).name == 'V: NMS.flac'
|
||||
+ assert root.joinpath('V: NMS.flac').read_bytes() == b"fLaC..."
|
||||
+
|
||||
+ def test_backslash_not_separator(self):
|
||||
+ """
|
||||
+ In a zip file, backslashes are not separators.
|
||||
+ """
|
||||
+ data = io.BytesIO()
|
||||
+ zf = zipfile.ZipFile(data, "w")
|
||||
+ zf.writestr(DirtyZipInfo.for_name("foo\\bar", zf), b"content")
|
||||
+ zf.filename = ''
|
||||
+ root = zipfile.Path(zf)
|
||||
+ (first,) = root.iterdir()
|
||||
+ assert not first.is_dir()
|
||||
+ assert first.name == 'foo\\bar'
|
||||
+
|
||||
+
|
||||
+class DirtyZipInfo(zipfile.ZipInfo):
|
||||
+ """
|
||||
+ Bypass name sanitization.
|
||||
+ """
|
||||
+
|
||||
+ def __init__(self, filename, *args, **kwargs):
|
||||
+ super().__init__(filename, *args, **kwargs)
|
||||
+ self.filename = filename
|
||||
+
|
||||
+ @classmethod
|
||||
+ def for_name(cls, name, archive):
|
||||
+ """
|
||||
+ Construct the same way that ZipFile.writestr does.
|
||||
+
|
||||
+ TODO: extract this functionality and re-use
|
||||
+ """
|
||||
+ self = cls(filename=name, date_time=time.localtime(time.time())[:6])
|
||||
+ self.compress_type = archive.compression
|
||||
+ self.compress_level = archive.compresslevel
|
||||
+ if self.filename.endswith('/'): # pragma: no cover
|
||||
+ self.external_attr = 0o40775 << 16 # drwxrwxr-x
|
||||
+ self.external_attr |= 0x10 # MS-DOS directory flag
|
||||
+ else:
|
||||
+ self.external_attr = 0o600 << 16 # ?rw-------
|
||||
+ return self
|
||||
+
|
||||
|
||||
class StripExtraTests(unittest.TestCase):
|
||||
# Note: all of the "z" characters are technically invalid, but up
|
||||
--- a/Lib/zipfile.py
|
||||
+++ b/Lib/zipfile.py
|
||||
@@ -9,6 +9,7 @@ import io
|
||||
import itertools
|
||||
import os
|
||||
import posixpath
|
||||
+import re
|
||||
import shutil
|
||||
import stat
|
||||
import struct
|
||||
@@ -2151,7 +2152,7 @@ def _parents(path):
|
||||
def _ancestry(path):
|
||||
"""
|
||||
Given a path with elements separated by
|
||||
- posixpath.sep, generate all elements of that path
|
||||
+ posixpath.sep, generate all elements of that path.
|
||||
|
||||
>>> list(_ancestry('b/d'))
|
||||
['b/d', 'b']
|
||||
@@ -2163,9 +2164,14 @@ def _ancestry(path):
|
||||
['b']
|
||||
>>> list(_ancestry(''))
|
||||
[]
|
||||
+
|
||||
+ Multiple separators are treated like a single.
|
||||
+
|
||||
+ >>> list(_ancestry('//b//d///f//'))
|
||||
+ ['//b//d///f', '//b//d', '//b']
|
||||
"""
|
||||
path = path.rstrip(posixpath.sep)
|
||||
- while path and path != posixpath.sep:
|
||||
+ while path.rstrip(posixpath.sep):
|
||||
yield path
|
||||
path, tail = posixpath.split(path)
|
||||
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2024-08-11-14-08-04.gh-issue-122905.7tDsxA.rst
|
||||
@@ -0,0 +1 @@
|
||||
+:class:`zipfile.Path` objects now sanitize names from the zipfile.
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2024-08-26-13-45-20.gh-issue-123270.gXHvNJ.rst
|
||||
@@ -0,0 +1,3 @@
|
||||
+Applied a more surgical fix for malformed payloads in :class:`zipfile.Path`
|
||||
+causing infinite loops (gh-122905) without breaking contents using
|
||||
+legitimate characters.
|
@ -13,8 +13,10 @@ Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
Lib/site.py | 9 ++++++++-
|
||||
2 files changed, 21 insertions(+), 3 deletions(-)
|
||||
|
||||
--- a/Lib/distutils/command/install.py
|
||||
+++ b/Lib/distutils/command/install.py
|
||||
Index: Python-3.10.14/Lib/distutils/command/install.py
|
||||
===================================================================
|
||||
--- Python-3.10.14.orig/Lib/distutils/command/install.py
|
||||
+++ Python-3.10.14/Lib/distutils/command/install.py
|
||||
@@ -441,8 +441,19 @@ class install(Command):
|
||||
raise DistutilsOptionError(
|
||||
"must not supply exec-prefix without prefix")
|
||||
@ -37,8 +39,10 @@ Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
|
||||
else:
|
||||
if self.exec_prefix is None:
|
||||
--- a/Lib/site.py
|
||||
+++ b/Lib/site.py
|
||||
Index: Python-3.10.14/Lib/site.py
|
||||
===================================================================
|
||||
--- Python-3.10.14.orig/Lib/site.py
|
||||
+++ Python-3.10.14/Lib/site.py
|
||||
@@ -390,8 +390,15 @@ def getsitepackages(prefixes=None):
|
||||
return sitepackages
|
||||
|
||||
@ -56,3 +60,128 @@ Fedora Change: https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
for sitedir in getsitepackages(prefixes):
|
||||
if os.path.isdir(sitedir):
|
||||
addsitedir(sitedir, known_paths)
|
||||
Index: Python-3.10.14/Lib/sysconfig.py
|
||||
===================================================================
|
||||
--- Python-3.10.14.orig/Lib/sysconfig.py
|
||||
+++ Python-3.10.14/Lib/sysconfig.py
|
||||
@@ -117,6 +117,19 @@ if _HAS_USER_BASE:
|
||||
},
|
||||
}
|
||||
|
||||
+# This is used by distutils.command.install in the stdlib
|
||||
+# as well as pypa/distutils (e.g. bundled in setuptools).
|
||||
+# The self.prefix value is set to sys.prefix + /local/
|
||||
+# if neither RPM build nor virtual environment is
|
||||
+# detected to make distutils install packages
|
||||
+# into the separate location.
|
||||
+# https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+if (not (hasattr(sys, 'real_prefix') or
|
||||
+ sys.prefix != sys.base_prefix) and
|
||||
+ 'RPM_BUILD_ROOT' not in os.environ):
|
||||
+ _prefix_addition = '/local'
|
||||
+
|
||||
+
|
||||
_SCHEME_KEYS = ('stdlib', 'platstdlib', 'purelib', 'platlib', 'include',
|
||||
'scripts', 'data')
|
||||
|
||||
@@ -136,6 +149,16 @@ _variable_rx = r"([a-zA-Z][a-zA-Z0-9_]+)
|
||||
_findvar1_rx = r"\$\(([A-Za-z][A-Za-z0-9_]*)\)"
|
||||
_findvar2_rx = r"\${([A-Za-z][A-Za-z0-9_]*)}"
|
||||
|
||||
+# For a brief period of time in the Fedora 36 life cycle,
|
||||
+# this installation scheme existed and was documented in the release notes.
|
||||
+# For backwards compatibility, we keep it here (at least on 3.10 and 3.11).
|
||||
+_INSTALL_SCHEMES['rpm_prefix'] = _INSTALL_SCHEMES['posix_prefix']
|
||||
+
|
||||
+# For a brief period of time in the Fedora 36 life cycle,
|
||||
+# this installation scheme existed and was documented in the release notes.
|
||||
+# For backwards compatibility, we keep it here (at least on 3.10 and 3.11).
|
||||
+_INSTALL_SCHEMES['rpm_prefix'] = _INSTALL_SCHEMES['posix_prefix']
|
||||
+
|
||||
|
||||
def _safe_realpath(path):
|
||||
try:
|
||||
@@ -211,11 +234,39 @@ def _extend_dict(target_dict, other_dict
|
||||
target_dict[key] = value
|
||||
|
||||
|
||||
+_CONFIG_VARS_LOCAL = None
|
||||
+
|
||||
+
|
||||
+def _config_vars_local():
|
||||
+ # This function returns the config vars with prefixes amended to /usr/local
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ global _CONFIG_VARS_LOCAL
|
||||
+ if _CONFIG_VARS_LOCAL is None:
|
||||
+ _CONFIG_VARS_LOCAL = dict(get_config_vars())
|
||||
+ _CONFIG_VARS_LOCAL['base'] = '/usr/local'
|
||||
+ _CONFIG_VARS_LOCAL['platbase'] = '/usr/local'
|
||||
+ return _CONFIG_VARS_LOCAL
|
||||
+
|
||||
+
|
||||
def _expand_vars(scheme, vars):
|
||||
res = {}
|
||||
if vars is None:
|
||||
vars = {}
|
||||
- _extend_dict(vars, get_config_vars())
|
||||
+
|
||||
+ # when we are not in a virtual environment or an RPM build
|
||||
+ # we change '/usr' to '/usr/local'
|
||||
+ # to avoid surprises, we explicitly check for the /usr/ prefix
|
||||
+ # Python virtual environments have different prefixes
|
||||
+ # we only do this for posix_prefix, not to mangle the venv scheme
|
||||
+ # posix_prefix is used by sudo pip install
|
||||
+ # we only change the defaults here, so explicit --prefix will take precedence
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ if (scheme == 'posix_prefix' and
|
||||
+ _PREFIX == '/usr' and
|
||||
+ 'RPM_BUILD_ROOT' not in os.environ):
|
||||
+ _extend_dict(vars, _config_vars_local())
|
||||
+ else:
|
||||
+ _extend_dict(vars, get_config_vars())
|
||||
|
||||
for key, value in _INSTALL_SCHEMES[scheme].items():
|
||||
if os.name in ('posix', 'nt'):
|
||||
Index: Python-3.10.14/Lib/test/test_sysconfig.py
|
||||
===================================================================
|
||||
--- Python-3.10.14.orig/Lib/test/test_sysconfig.py
|
||||
+++ Python-3.10.14/Lib/test/test_sysconfig.py
|
||||
@@ -105,8 +105,19 @@ class TestSysConfig(unittest.TestCase):
|
||||
for scheme in _INSTALL_SCHEMES:
|
||||
for name in _INSTALL_SCHEMES[scheme]:
|
||||
expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars)
|
||||
+ tested = get_path(name, scheme)
|
||||
+ # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
|
||||
+ if tested.startswith('/usr/local'):
|
||||
+ # /usr/local should only be used in posix_prefix
|
||||
+ self.assertEqual(scheme, 'posix_prefix')
|
||||
+ # Fedora CI runs tests for venv and virtualenv that check for other prefixes
|
||||
+ self.assertEqual(sys.prefix, '/usr')
|
||||
+ # When building the RPM of Python, %check runs this with RPM_BUILD_ROOT set
|
||||
+ # Fedora CI runs this with RPM_BUILD_ROOT unset
|
||||
+ self.assertNotIn('RPM_BUILD_ROOT', os.environ)
|
||||
+ tested = tested.replace('/usr/local', '/usr')
|
||||
self.assertEqual(
|
||||
- os.path.normpath(get_path(name, scheme)),
|
||||
+ os.path.normpath(tested),
|
||||
os.path.normpath(expected),
|
||||
)
|
||||
|
||||
@@ -263,7 +274,7 @@ class TestSysConfig(unittest.TestCase):
|
||||
self.assertTrue(os.path.isfile(config_h), config_h)
|
||||
|
||||
def test_get_scheme_names(self):
|
||||
- wanted = ['nt', 'posix_home', 'posix_prefix']
|
||||
+ wanted = ['nt', 'posix_home', 'posix_prefix', 'rpm_prefix']
|
||||
if HAS_USER_BASE:
|
||||
wanted.extend(['nt_user', 'osx_framework_user', 'posix_user'])
|
||||
self.assertEqual(get_scheme_names(), tuple(sorted(wanted)))
|
||||
@@ -274,6 +285,8 @@ class TestSysConfig(unittest.TestCase):
|
||||
cmd = "-c", "import sysconfig; print(sysconfig.get_platform())"
|
||||
self.assertEqual(py.call_real(*cmd), py.call_link(*cmd))
|
||||
|
||||
+ @unittest.skipIf('RPM_BUILD_ROOT' not in os.environ,
|
||||
+ "Test doesn't expect Fedora's paths")
|
||||
def test_user_similar(self):
|
||||
# Issue #8759: make sure the posix scheme for the users
|
||||
# is similar to the global posix_prefix one
|
||||
|
BIN
bluez-devel-vendor.tar.xz
(Stored with Git LFS)
BIN
bluez-devel-vendor.tar.xz
(Stored with Git LFS)
Binary file not shown.
37
bso1227999-reproducible-builds.patch
Normal file
37
bso1227999-reproducible-builds.patch
Normal file
@ -0,0 +1,37 @@
|
||||
From ac2b8869724d7a57d9b5efbdce2f20423214e8bb Mon Sep 17 00:00:00 2001
|
||||
From: "Bernhard M. Wiedemann" <bwiedemann@suse.de>
|
||||
Date: Tue, 16 Jul 2024 21:39:33 +0200
|
||||
Subject: [PATCH] Allow to override build date with SOURCE_DATE_EPOCH
|
||||
|
||||
to make builds reproducible.
|
||||
See https://reproducible-builds.org/ for why this is good
|
||||
and https://reproducible-builds.org/specs/source-date-epoch/
|
||||
for the definition of this variable.
|
||||
---
|
||||
Doc/conf.py | 3 ++-
|
||||
Doc/library/functions.rst | 2 +-
|
||||
2 files changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
--- a/Doc/conf.py
|
||||
+++ b/Doc/conf.py
|
||||
@@ -89,7 +89,8 @@ html_short_title = '%s Documentation' %
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
-html_last_updated_fmt = '%b %d, %Y'
|
||||
+html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
|
||||
+html_last_updated_fmt = time.strftime('%b %d, %Y (%H:%M UTC)', time.gmtime(html_time))
|
||||
|
||||
# Path to find HTML templates.
|
||||
templates_path = ['tools/templates']
|
||||
--- a/Doc/library/functions.rst
|
||||
+++ b/Doc/library/functions.rst
|
||||
@@ -1320,7 +1320,7 @@ are always available. They are listed h
|
||||
(where :func:`open` is declared), :mod:`os`, :mod:`os.path`, :mod:`tempfile`,
|
||||
and :mod:`shutil`.
|
||||
|
||||
- .. audit-event:: open file,mode,flags open
|
||||
+ .. audit-event:: open path,mode,flags open
|
||||
|
||||
The ``mode`` and ``flags`` arguments may have been modified or inferred from
|
||||
the original call.
|
@ -1,3 +1,42 @@
|
||||
-------------------------------------------------------------------
|
||||
Wed Aug 28 16:54:34 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
|
||||
|
||||
- Add CVE-2024-8088-inf-loop-zipfile_Path.patch to prevent
|
||||
malformed payload to cause infinite loops in zipfile.Path
|
||||
(bsc#1229704, CVE-2024-8088).
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Wed Aug 7 13:40:44 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
|
||||
|
||||
- Add CVE-2024-6923-email-hdr-inject.patch to prevent email
|
||||
header injection due to unquoted newlines (bsc#1228780,
|
||||
CVE-2024-6923).
|
||||
- Adding bso1227999-reproducible-builds.patch fixing bsc#1227999
|
||||
adding reproducibility patches from gh#python/cpython!121872
|
||||
and gh#python/cpython!121883.
|
||||
- %{profileopt} variable is set according to the variable
|
||||
%{do_profiling} (bsc#1227999)
|
||||
- Update bluez-devel-vendor.tar.xz
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Jul 22 21:20:55 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
|
||||
|
||||
- Remove %suse_update_desktop_file macro as it is not useful any
|
||||
more.
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Jul 15 12:14:57 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
|
||||
|
||||
- Stop using %%defattr, it seems to be breaking proper executable
|
||||
attributes on /usr/bin/ scripts (bsc#1227378).
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Jul 2 10:31:26 UTC 2024 - Daniel Garcia <daniel.garcia@suse.com>
|
||||
|
||||
- Update F00251-change-user-install-location.patch to make pip and
|
||||
modern tools install directly in /usr/local when used by the user.
|
||||
bsc#1225660
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Tue Jun 25 21:57:40 UTC 2024 - Matej Cepl <mcepl@cepl.eu>
|
||||
|
||||
|
@ -36,6 +36,12 @@
|
||||
%bcond_without general
|
||||
%endif
|
||||
|
||||
%if 0%{?do_profiling}
|
||||
%bcond_without profileopt
|
||||
%else
|
||||
%bcond_with profileopt
|
||||
%endif
|
||||
|
||||
%define python_pkg_name python310
|
||||
%if "%{python_pkg_name}" == "%{primary_python}"
|
||||
%define primary_interpreter 1
|
||||
@ -101,7 +107,6 @@ Obsoletes: python39%{?1:-%{1}}
|
||||
# pyexpat.cpython-35m-armv7-linux-gnueabihf
|
||||
# _md5.cpython-38m-x86_64-linux-gnu.so
|
||||
%define dynlib() %{sitedir}/lib-dynload/%{1}.cpython-%{abi_tag}-%{archname}-%{_os}%{?_gnu}%{?armsuffix}.so
|
||||
%bcond_without profileopt
|
||||
Name: %{python_pkg_name}%{psuffix}
|
||||
Version: 3.10.14
|
||||
Release: 0
|
||||
@ -198,6 +203,15 @@ Patch22: CVE-2023-52425-libexpat-2.6.0-backport.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2024-4032-private-IP-addrs.patch bsc#1226448 mcepl@suse.com
|
||||
# rearrange definition of private v global IP addresses
|
||||
Patch23: CVE-2024-4032-private-IP-addrs.patch
|
||||
# PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com
|
||||
# reproducibility patches
|
||||
Patch24: bso1227999-reproducible-builds.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2024-6923-email-hdr-inject.patch bsc#1228780 mcepl@suse.com
|
||||
# prevent email header injection, patch from gh#python/cpython!122608
|
||||
Patch25: CVE-2024-6923-email-hdr-inject.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2024-8088-inf-loop-zipfile_Path.patch bsc#1229704 mcepl@suse.com
|
||||
# avoid denial of service in zipfile
|
||||
Patch26: CVE-2024-8088-inf-loop-zipfile_Path.patch
|
||||
BuildRequires: autoconf-archive
|
||||
BuildRequires: automake
|
||||
BuildRequires: fdupes
|
||||
@ -243,7 +257,6 @@ BuildRequires: gettext
|
||||
BuildRequires: readline-devel
|
||||
BuildRequires: sqlite-devel
|
||||
BuildRequires: timezone
|
||||
BuildRequires: update-desktop-files
|
||||
BuildRequires: pkgconfig(ncurses)
|
||||
BuildRequires: pkgconfig(tk)
|
||||
BuildRequires: pkgconfig(x11)
|
||||
@ -477,6 +490,9 @@ other applications.
|
||||
%patch -p1 -P 21
|
||||
%patch -p1 -P 22
|
||||
%patch -p1 -P 23
|
||||
%patch -p1 -P 24
|
||||
%patch -p1 -P 25
|
||||
%patch -p1 -P 26
|
||||
|
||||
# drop Autoconf version requirement
|
||||
sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac
|
||||
@ -708,7 +724,6 @@ done
|
||||
cp %{SOURCE19} idle%{python_version}.desktop
|
||||
sed -i -e 's:idle3:idle%{python_version}:g' idle%{python_version}.desktop
|
||||
install -m 644 -D -t %{buildroot}%{_datadir}/applications idle%{python_version}.desktop
|
||||
%suse_update_desktop_file idle%{python_version}
|
||||
|
||||
cp %{SOURCE20} idle%{python_version}.appdata.xml
|
||||
sed -i -e 's:idle3.desktop:idle%{python_version}.desktop:g' idle%{python_version}.appdata.xml
|
||||
@ -816,25 +831,21 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
|
||||
%if %{with general}
|
||||
%files -n %{python_pkg_name}-tk
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/tkinter
|
||||
%exclude %{sitedir}/tkinter/test
|
||||
%{dynlib _tkinter}
|
||||
|
||||
%files -n %{python_pkg_name}-curses
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/curses
|
||||
%{dynlib _curses}
|
||||
%{dynlib _curses_panel}
|
||||
|
||||
%files -n %{python_pkg_name}-dbm
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/dbm
|
||||
%{dynlib _dbm}
|
||||
%{dynlib _gdbm}
|
||||
|
||||
%files -n %{python_pkg_name}
|
||||
%defattr(644, root, root, 755)
|
||||
%dir %{sitedir}
|
||||
%dir %{sitedir}/lib-dynload
|
||||
%{sitedir}/sqlite3
|
||||
@ -846,7 +857,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%endif
|
||||
|
||||
%files -n %{python_pkg_name}-idle
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/idlelib
|
||||
%dir %{_sysconfdir}/idle%{python_version}
|
||||
%config %{_sysconfdir}/idle%{python_version}/*
|
||||
@ -884,11 +894,9 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%postun -n libpython%{so_version} -p /sbin/ldconfig
|
||||
|
||||
%files -n libpython%{so_version}
|
||||
%defattr(644, root,root)
|
||||
%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}
|
||||
|
||||
%files -n %{python_pkg_name}-tools
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/turtledemo
|
||||
%if %{primary_interpreter}
|
||||
%{_bindir}/2to3
|
||||
@ -897,7 +905,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%doc %{_docdir}/%{name}/Tools
|
||||
|
||||
%files -n %{python_pkg_name}-devel
|
||||
%defattr(644, root, root, 755)
|
||||
%{_libdir}/libpython%{python_abi}.so
|
||||
%if %{primary_interpreter}
|
||||
%{_libdir}/libpython3.so
|
||||
@ -905,7 +912,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%{_libdir}/pkgconfig/*
|
||||
%{_includedir}/python%{python_abi}
|
||||
%{sitedir}/config-%{python_abi}-*
|
||||
%defattr(755, root, root)
|
||||
%{_bindir}/python%{python_abi}-config
|
||||
%if %{primary_interpreter}
|
||||
%{_bindir}/python3-config
|
||||
@ -918,7 +924,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%{_datadir}/gdb/auto-load/%{_libdir}/libpython%{python_abi}.so.%{so_major}.%{so_minor}-gdb.py
|
||||
|
||||
%files -n %{python_pkg_name}-testsuite
|
||||
%defattr(644, root, root, 755)
|
||||
%{sitedir}/test
|
||||
%{sitedir}/*/test
|
||||
%{sitedir}/*/tests
|
||||
@ -935,7 +940,6 @@ echo %{sitedir}/_import_failed > %{buildroot}/%{sitedir}/site-packages/zzzz-impo
|
||||
%dir %{sitedir}/tkinter
|
||||
|
||||
%files -n %{python_pkg_name}-base
|
||||
%defattr(644, root, root, 755)
|
||||
# docs
|
||||
%dir %{_docdir}/%{name}
|
||||
%doc %{_docdir}/%{name}/README.rst
|
||||
|
Loading…
x
Reference in New Issue
Block a user