Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python39 for openSUSE:Factory checked in at 2024-08-09 16:15:52 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python39 (Old) and /work/SRC/openSUSE:Factory/.python39.new.7232 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python39" Fri Aug 9 16:15:52 2024 rev:61 rq:1192673 version:3.9.19 Changes: -------- --- /work/SRC/openSUSE:Factory/python39/python39.changes 2024-07-30 11:57:01.475726571 +0200 +++ /work/SRC/openSUSE:Factory/.python39.new.7232/python39.changes 2024-08-09 16:16:58.308711635 +0200 @@ -1,0 +2,15 @@ +Wed Aug 7 12:12:42 UTC 2024 - Matej Cepl <mcepl@suse.com> + +- 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. +- Add CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch removing + support for anything but OpenSSL 1.1.1 or newer (bsc#1227233, + CVE-2024-5642). +- %{profileopt} variable is set according to the variable + %{do_profiling} (bsc#1227999) + +------------------------------------------------------------------- New: ---- CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch CVE-2024-6923-email-hdr-inject.patch bso1227999-reproducible-builds.patch BETA DEBUG BEGIN: New: and gh#python/cpython!121883. - Add CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch removing support for anything but OpenSSL 1.1.1 or newer (bsc#1227233, New: - Add CVE-2024-6923-email-hdr-inject.patch to prevent email header injection due to unquoted newlines (bsc#1228780, New: CVE-2024-6923). - Adding bso1227999-reproducible-builds.patch fixing bsc#1227999 adding reproducibility patches from gh#python/cpython!121872 BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python39.spec ++++++ --- /var/tmp/diff_new_pack.ekYQBA/_old 2024-08-09 16:16:59.480760569 +0200 +++ /var/tmp/diff_new_pack.ekYQBA/_new 2024-08-09 16:16:59.480760569 +0200 @@ -36,6 +36,12 @@ %bcond_without general %endif +%if 0%{?do_profiling} +%bcond_without profileopt +%else +%bcond_with profileopt +%endif + %define python_pkg_name python39 %if "%{python_pkg_name}" == "%{primary_python}" %define primary_interpreter 1 @@ -187,6 +193,15 @@ # PATCH-FIX-UPSTREAM CVE-2024-4032-private-IP-addrs.patch bsc#1226448 mcepl@suse.com # rearrange definition of private v global IP addresses Patch45: CVE-2024-4032-private-IP-addrs.patch +# PATCH-FIX-UPSTREAM bso1227999-reproducible-builds.patch bsc#1227999 mcepl@suse.com +# reproducibility patches +Patch46: 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 +Patch47: CVE-2024-6923-email-hdr-inject.patch +# PATCH-FIX-UPSTREAM CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch bsc#1227233 mcepl@suse.com +# Remove for support for anything but OpenSSL 1.1.1 or newer +Patch48: CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes @@ -447,12 +462,15 @@ %patch -P 39 -p1 %patch -P 40 -p1 %if 0%{?sle_version} && 0%{?sle_version} <= 150500 -%patch -P 41 -p1 +%patch -p1 -P 41 %endif -%patch -P 42 -p1 -%patch -P 43 -p1 -%patch -P 44 -p1 -%patch -P 45 -p1 +%patch -p1 -P 42 +%patch -p1 -P 43 +%patch -p1 -P 44 +%patch -p1 -P 45 +%patch -p1 -P 46 +%patch -p1 -P 47 +%patch -p1 -P 48 # drop Autoconf version requirement sed -i 's/^AC_PREREQ/dnl AC_PREREQ/' configure.ac ++++++ CVE-2024-5642-OpenSSL-API-buf-overread-NPN.patch ++++++ ++++ 1892 lines (skipped) ++++++ CVE-2024-6923-email-hdr-inject.patch ++++++ From f9ddc53ea850fb02d640a9b3263756d43fb6d868 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.9] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) GH-GH- Encode header parts that contain newlines 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. GH-GH- Verify that email headers are well-formed 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.9.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.11.10 + + 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.9.rst +++ b/Doc/whatsnew/3.9.rst @@ -1625,3 +1625,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`.) ++++++ bso1227999-reproducible-builds.patch ++++++ 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 @@ -80,7 +80,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 @@ -1254,7 +1254,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.