python3/00476-cve-2026-1299.patch
Lumir Balhar ddf314d2b9 Security fixes for CVE-2026-0865, CVE-2025-15366, CVE-2025-15367, CVE-2026-1299
Resolves: RHEL-143063, RHEL-143120, RHEL-144860
2026-02-02 12:42:47 +01:00

184 lines
8.1 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From c5cf3f478ef5dc435cc53c49e4e59a4819032265 Mon Sep 17 00:00:00 2001
From: Lumir Balhar <lbalhar@redhat.com>
Date: Mon, 2 Feb 2026 12:39:40 +0100
Subject: [PATCH] 00476: CVE-2026-1299
gh-144125: email: verify headers are sound in BytesGenerator
(cherry picked from commit 8cdf6204f4ae821f32993f8fc6bad0d318f95f36)
Co-authored-by: Seth Michael Larson <seth@python.org>
Co-authored-by: Denis Ledoux <dle@odoo.com>
Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com>
Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com>
Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com>
The fix for the CVE uncovered a known issue in handling
policy.linesep lengths fixed by:
bpo-34424: Handle different policy.linesep lengths correctly. (#8803)
(cherry-picked from commit 45b2f8893c1b7ab3b3981a966f82e42beea82106)
Co-authored-by: Jens Troeger <jenstroeger@users.noreply.github.com>
---
Lib/email/_header_value_parser.py | 2 +-
Lib/email/generator.py | 15 ++++++++++-
Lib/test/test_email/test_generator.py | 26 ++++++++++++++++++-
Lib/test/test_email/test_policy.py | 6 ++++-
.../2018-08-18-14-47-00.bpo-34424.wAlRuS.rst | 2 ++
...-01-21-12-34-05.gh-issue-144125.TAz5uo.rst | 4 +++
6 files changed, 51 insertions(+), 4 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2018-08-18-14-47-00.bpo-34424.wAlRuS.rst
create mode 100644 Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
diff --git a/Lib/email/_header_value_parser.py b/Lib/email/_header_value_parser.py
index dab4cbb..541c297 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -2638,7 +2638,7 @@ def _refold_parse_tree(parse_tree, *, policy):
want_encoding = False
last_ew = None
if part.syntactic_break:
- encoded_part = part.fold(policy=policy)[:-1] # strip nl
+ encoded_part = part.fold(policy=policy)[:-len(policy.linesep)]
if policy.linesep not in encoded_part:
# It fits on a single line
if len(encoded_part) > maxlen - len(lines[-1]):
diff --git a/Lib/email/generator.py b/Lib/email/generator.py
index 6deb95b..6b08d84 100644
--- a/Lib/email/generator.py
+++ b/Lib/email/generator.py
@@ -22,6 +22,7 @@ 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]')
+NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
@@ -429,7 +430,19 @@ class BytesGenerator(Generator):
# This is almost the same as the string version, except for handling
# strings with 8bit bytes.
for h, v in msg.raw_items():
- self._fp.write(self.policy.fold_binary(h, v))
+ folded = self.policy.fold_binary(h, v)
+ if self.policy.verify_generated_headers:
+ linesep = self.policy.linesep.encode()
+ if not folded.endswith(linesep):
+ raise HeaderWriteError(
+ f'folded header does not end with {linesep!r}: {folded!r}')
+ folded_no_linesep = folded
+ if folded.endswith(linesep):
+ folded_no_linesep = folded[:-len(linesep)]
+ if NEWLINE_WITHOUT_FWSP_BYTES.search(folded_no_linesep):
+ raise HeaderWriteError(
+ f'folded header contains newline: {folded!r}')
+ self._fp.write(folded)
# A blank line always separates headers from body
self.write(self._NL)
diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py
index cdf1075..23adb06 100644
--- a/Lib/test/test_email/test_generator.py
+++ b/Lib/test/test_email/test_generator.py
@@ -4,6 +4,7 @@ import unittest
from email import message_from_string, message_from_bytes
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
@@ -263,7 +264,7 @@ class TestGenerator(TestGeneratorBase, TestEmailBase):
typ = str
def test_verify_generated_headers(self):
- """gh-121650: by default the generator prevents header injection"""
+ # gh-121650: by default the generator prevents header injection
class LiteralHeader(str):
name = 'Header'
def fold(self, **kwargs):
@@ -284,6 +285,8 @@ class TestGenerator(TestGeneratorBase, TestEmailBase):
with self.assertRaises(email.errors.HeaderWriteError):
message.as_string()
+ with self.assertRaises(email.errors.HeaderWriteError):
+ message.as_bytes()
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
@@ -353,6 +356,27 @@ class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
g.flatten(msg)
self.assertEqual(s.getvalue(), expected)
+ def test_smtp_policy(self):
+ msg = EmailMessage()
+ msg["From"] = Address(addr_spec="foo@bar.com", display_name="Páolo")
+ msg["To"] = Address(addr_spec="bar@foo.com", display_name="Dinsdale")
+ msg["Subject"] = "Nudge nudge, wink, wink"
+ msg.set_content("oh boy, know what I mean, know what I mean?")
+ expected = textwrap.dedent("""\
+ From: =?utf-8?q?P=C3=A1olo?= <foo@bar.com>
+ To: Dinsdale <bar@foo.com>
+ Subject: Nudge nudge, wink, wink
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+
+ oh boy, know what I mean, know what I mean?
+ """).encode().replace(b"\n", b"\r\n")
+ s = io.BytesIO()
+ g = BytesGenerator(s, policy=policy.SMTP)
+ g.flatten(msg)
+ self.assertEqual(s.getvalue(), expected)
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py
index 6793422..f56236f 100644
--- a/Lib/test/test_email/test_policy.py
+++ b/Lib/test/test_email/test_policy.py
@@ -239,7 +239,7 @@ class PolicyAPITests(unittest.TestCase):
self.assertEqual(newpolicy.__dict__, {'raise_on_defect': True})
def test_verify_generated_headers(self):
- """Turning protection off allows header injection"""
+ # 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',
@@ -262,6 +262,10 @@ class PolicyAPITests(unittest.TestCase):
message.as_string(),
f"{text}\nBody",
)
+ self.assertEqual(
+ message.as_bytes(),
+ f"{text}\nBody".encode(),
+ )
# XXX: Need subclassing tests.
# For adding subclassed objects, make sure the usual rules apply (subclass
diff --git a/Misc/NEWS.d/next/Library/2018-08-18-14-47-00.bpo-34424.wAlRuS.rst b/Misc/NEWS.d/next/Library/2018-08-18-14-47-00.bpo-34424.wAlRuS.rst
new file mode 100644
index 0000000..2b384cd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-08-18-14-47-00.bpo-34424.wAlRuS.rst
@@ -0,0 +1,2 @@
+Fix serialization of messages containing encoded strings when the
+policy.linesep is set to a multi-character string. Patch by Jens Troeger.
diff --git a/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
new file mode 100644
index 0000000..e6333e7
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst
@@ -0,0 +1,4 @@
+:mod:`~email.generator.BytesGenerator` 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`).
--
2.52.0