[packages/python] - backport security fixes from Gentoo/Debian/RHEL; rel 11

arekm arekm at pld-linux.org
Tue Aug 18 14:49:33 CEST 2026


commit d265a4e262afa1319ff48749f6dd0d4a06fdfb2e
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Tue Aug 18 14:48:06 2026 +0200

    - backport security fixes from Gentoo/Debian/RHEL; rel 11

 CVE-2015-20107.patch                   | 440 +++++++++++++++++++++++++++++++++
 CVE-2019-20907.patch                   |  39 +++
 CVE-2020-26116.patch                   |  93 +++++++
 CVE-2020-27619.patch                   | 119 +++++++++
 CVE-2020-8492.patch                    | 213 ++++++++++++++++
 CVE-2021-23336.patch                   | 390 +++++++++++++++++++++++++++++
 CVE-2021-3177.patch                    | 181 ++++++++++++++
 CVE-2021-3733.patch                    |  39 +++
 CVE-2021-3737.patch                    |  83 +++++++
 CVE-2021-4189.patch                    | 133 ++++++++++
 CVE-2022-0391.patch                    | 151 +++++++++++
 CVE-2022-45061.patch                   | 118 +++++++++
 CVE-2022-48560.patch                   | 155 ++++++++++++
 CVE-2022-48565.patch                   |  91 +++++++
 CVE-2022-48566.patch                   |  48 ++++
 CVE-2023-24329.patch                   | 264 ++++++++++++++++++++
 CVE-2023-40217.patch                   | 321 ++++++++++++++++++++++++
 python-bpo-42278-pydoc-mktemp.patch    |  59 +++++
 python-bpo-43124-smtplib-crlf.patch    | 180 ++++++++++++++
 python-bpo-46756-urllib-auth.patch     |  77 ++++++
 python-configure-c99.patch             |  52 ++++
 python-expat-2.4.5-tests.patch         |  86 +++++++
 python-test-support-unlink-errno.patch |  21 ++
 python.spec                            |  53 +++-
 24 files changed, 3405 insertions(+), 1 deletion(-)
---
diff --git a/python.spec b/python.spec
index 09c579d..bed0294 100644
--- a/python.spec
+++ b/python.spec
@@ -24,6 +24,9 @@
 # tests which fail because of some unknown/unresolved reason (this list should be ideally just %{nil})
 %define		broken_tests test_doctest test_pydoc test_distutils test_gdb
 
+# C sources predate C23, which GCC 15 defaults to (asdl.h: typedef enum {false, true} bool)
+%define		specflags	-std=gnu17
+
 %define py_ver		2.7
 %define py_prefix	%{_prefix}
 %define py_libdir	%{py_prefix}/%{_lib}/python%{py_ver}
@@ -42,7 +45,7 @@ Summary(tr.UTF-8):	X arayüzlü, yüksek düzeyli, kabuk yorumlayıcı dili
 Summary(uk.UTF-8):	Мова програмування дуже високого рівня з X-інтерфейсом
 Name:		python
 Version:	%{py_ver}.18
-Release:	10
+Release:	11
 Epoch:		1
 License:	PSF
 Group:		Development/Languages/Python
@@ -66,6 +69,31 @@ Patch9:		https://bugs.python.org/file21896/nonexistent_user.patch
 Patch10:	%{name}-BLDLIBRARY.patch
 Patch11:	openssl3.patch
 Patch12:	gcc14.patch
+# Post-2.7.18 backports; upstream CPython 2.7 has had no commits since 2020-04-19.
+# Sources: Gentoo python-gentoo-patches-2.7.18_p16, Debian 2.7.18-8+deb11u1, RHEL python2-2.7.18-17.
+Patch13:	CVE-2019-20907.patch
+Patch14:	CVE-2020-8492.patch
+Patch15:	CVE-2020-26116.patch
+Patch16:	CVE-2022-48565.patch
+Patch17:	CVE-2020-27619.patch
+Patch18:	CVE-2022-48566.patch
+Patch19:	CVE-2021-3177.patch
+Patch20:	CVE-2021-23336.patch
+Patch21:	CVE-2021-4189.patch
+Patch22:	CVE-2021-3733.patch
+Patch23:	CVE-2021-3737.patch
+Patch24:	%{name}-bpo-43124-smtplib-crlf.patch
+Patch25:	%{name}-bpo-42278-pydoc-mktemp.patch
+Patch26:	%{name}-bpo-46756-urllib-auth.patch
+Patch27:	CVE-2022-0391.patch
+Patch28:	CVE-2022-45061.patch
+Patch29:	CVE-2022-48560.patch
+Patch30:	CVE-2023-24329.patch
+Patch31:	CVE-2023-40217.patch
+Patch32:	CVE-2015-20107.patch
+Patch33:	%{name}-expat-2.4.5-tests.patch
+Patch34:	%{name}-configure-c99.patch
+Patch35:	%{name}-test-support-unlink-errno.patch
 URL:		https://www.python.org/
 BuildRequires:	autoconf >= 2.65
 BuildRequires:	automake
@@ -601,6 +629,29 @@ napisanego w Pythonie.
 %patch -P10 -p1
 %patch -P11 -p1
 %patch -P12 -p1
+%patch -P13 -p1
+%patch -P14 -p1
+%patch -P15 -p1
+%patch -P16 -p1
+%patch -P17 -p1
+%patch -P18 -p1
+%patch -P19 -p1
+%patch -P20 -p1
+%patch -P21 -p1
+%patch -P22 -p1
+%patch -P23 -p1
+%patch -P24 -p1
+%patch -P25 -p1
+%patch -P26 -p1
+%patch -P27 -p1
+%patch -P28 -p1
+%patch -P29 -p1
+%patch -P30 -p1
+%patch -P31 -p1
+%patch -P32 -p1
+%patch -P33 -p1
+%patch -P34 -p1
+%patch -P35 -p1
 
 tar xjf %{SOURCE1}
 
diff --git a/CVE-2015-20107.patch b/CVE-2015-20107.patch
new file mode 100644
index 0000000..edc1843
--- /dev/null
+++ b/CVE-2015-20107.patch
@@ -0,0 +1,440 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Petr Viktorin <encukou at gmail.com>
+Date: Fri, 3 Jun 2022 11:43:35 +0200
+Subject: [PATCH] 00382-cve-2015-20107.patch
+
+00382 #
+Make mailcap refuse to match unsafe filenames/types/params (GH-91993)
+
+Upstream: https://github.com/python/cpython/issues/68966
+
+Tracker bug: https://bugzilla.redhat.com/show_bug.cgi?id=2075390
+
+Backported from python3.
+---
+ Doc/library/mailcap.rst                       |  12 +
+ Lib/mailcap.py                                |  29 +-
+ Lib/test/mailcap.txt                          |  39 +++
+ Lib/test/test_mailcap.py                      | 259 ++++++++++++++++++
+ ...2-04-27-18-25-30.gh-issue-68966.gjS8zs.rst |   4 +
+ 5 files changed, 341 insertions(+), 2 deletions(-)
+ create mode 100644 Lib/test/mailcap.txt
+ create mode 100644 Lib/test/test_mailcap.py
+ create mode 100644 Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst
+
+diff --git a/Doc/library/mailcap.rst b/Doc/library/mailcap.rst
+index 750d085796f..5f75ee6086e 100644
+--- a/Doc/library/mailcap.rst
++++ b/Doc/library/mailcap.rst
+@@ -54,6 +54,18 @@ standard.  However, mailcap files are supported on most Unix systems.
+    use) to determine whether or not the mailcap line applies.  :func:`findmatch`
+    will automatically check such conditions and skip the entry if the check fails.
+ 
++   .. versionchanged:: 3.11
++
++      To prevent security issues with shell metacharacters (symbols that have
++      special effects in a shell command line), ``findmatch`` will refuse
++      to inject ASCII characters other than alphanumerics and ``@+=:,./-_``
++      into the returned command line.
++
++      If a disallowed character appears in *filename*, ``findmatch`` will always
++      return ``(None, None)`` as if no entry was found.
++      If such a character appears elsewhere (a value in *plist* or in *MIMEtype*),
++      ``findmatch`` will ignore all mailcap entries which use that value.
++      A :mod:`warning <warnings>` will be raised in either case.
+ 
+ .. function:: getcaps()
+ 
+diff --git a/Lib/mailcap.py b/Lib/mailcap.py
+index 04077ba0db2..1108b447b1d 100644
+--- a/Lib/mailcap.py
++++ b/Lib/mailcap.py
+@@ -1,9 +1,18 @@
+ """Mailcap file handling.  See RFC 1524."""
+ 
+ import os
++import warnings
++import re
+ 
+ __all__ = ["getcaps","findmatch"]
+ 
++
++_find_unsafe = re.compile(r'[^\xa1-\xff\w at +=:,./-]').search
++
++class UnsafeMailcapInput(Warning):
++    """Warning raised when refusing unsafe input"""
++
++
+ # Part 1: top-level interface.
+ 
+ def getcaps():
+@@ -144,15 +153,22 @@ def findmatch(caps, MIMEtype, key='view', filename="/dev/null", plist=[]):
+     entry to use.
+ 
+     """
++    if _find_unsafe(filename):
++        msg = "Refusing to use mailcap with filename %r. Use a safe temporary filename." % (filename,)
++        warnings.warn(msg, UnsafeMailcapInput)
++        return None, None
+     entries = lookup(caps, MIMEtype, key)
+     # XXX This code should somehow check for the needsterminal flag.
+     for e in entries:
+         if 'test' in e:
+             test = subst(e['test'], filename, plist)
++            if test is None:
++                continue
+             if test and os.system(test) != 0:
+                 continue
+         command = subst(e[key], MIMEtype, filename, plist)
+-        return command, e
++        if command is not None:
++            return command, e
+     return None, None
+ 
+ def lookup(caps, MIMEtype, key=None):
+@@ -184,6 +200,10 @@ def subst(field, MIMEtype, filename, plist=[]):
+             elif c == 's':
+                 res = res + filename
+             elif c == 't':
++                if _find_unsafe(MIMEtype):
++                    msg = "Refusing to substitute MIME type %r into a shell command." % (MIMEtype,)
++                    warnings.warn(msg, UnsafeMailcapInput)
++                    return None
+                 res = res + MIMEtype
+             elif c == '{':
+                 start = i
+@@ -191,7 +211,12 @@ def subst(field, MIMEtype, filename, plist=[]):
+                     i = i+1
+                 name = field[start:i]
+                 i = i+1
+-                res = res + findparam(name, plist)
++                param = findparam(name, plist)
++                if _find_unsafe(param):
++                    msg = "Refusing to substitute parameter %r (%s) into a shell command" % (param, name)
++                    warnings.warn(msg, UnsafeMailcapInput)
++                    return None
++                res = res + param
+             # XXX To do:
+             # %n == number of parts if type is multipart/*
+             # %F == list of alternating type and filename for parts
+diff --git a/Lib/test/mailcap.txt b/Lib/test/mailcap.txt
+new file mode 100644
+index 00000000000..08a76e65941
+--- /dev/null
++++ b/Lib/test/mailcap.txt
+@@ -0,0 +1,39 @@
++# Mailcap file for test_mailcap; based on RFC 1524
++# Referred to by test_mailcap.py
++
++#
++# This is a comment.
++#
++
++application/frame; showframe %s; print="cat %s | lp"
++application/postscript; ps-to-terminal %s;\
++    needsterminal
++application/postscript; ps-to-terminal %s; \
++    compose=idraw %s
++application/x-dvi; xdvi %s
++application/x-movie; movieplayer %s; compose=moviemaker %s; \
++       description="Movie"; \
++       x11-bitmap="/usr/lib/Zmail/bitmaps/movie.xbm"
++application/*; echo "This is \"%t\" but \
++       is 50 \% Greek to me" \; cat %s; copiousoutput
++
++audio/basic; showaudio %s; compose=audiocompose %s; edit=audiocompose %s;\
++description="An audio fragment"
++audio/* ; /usr/local/bin/showaudio %t
++
++image/rgb; display %s
++#image/gif; display %s
++image/x-xwindowdump; display %s
++
++# The continuation char shouldn't \
++# make a difference in a comment.
++
++message/external-body; showexternal %s %{access-type} %{name} %{site} \
++    %{directory} %{mode} %{server}; needsterminal; composetyped = extcompose %s; \
++    description="A reference to data stored in an external location"
++
++text/richtext; shownonascii iso-8859-8 -e richtext -p %s; test=test "`echo \
++    %{charset} | tr '[A-Z]' '[a-z]'`"  = iso-8859-8; copiousoutput
++
++video/*; animate %s
++video/mpeg; mpeg_play %s
+\ No newline at end of file
+diff --git a/Lib/test/test_mailcap.py b/Lib/test/test_mailcap.py
+new file mode 100644
+index 00000000000..35da7fb0741
+--- /dev/null
++++ b/Lib/test/test_mailcap.py
+@@ -0,0 +1,259 @@
++import copy
++import os
++import sys
++import test.support
++import unittest
++from test import support as os_helper
++from test import support as warnings_helper
++from collections import OrderedDict
++
++import mailcap
++
++
++# Location of mailcap file
++MAILCAPFILE = test.support.findfile("mailcap.txt")
++
++# Dict to act as mock mailcap entry for this test
++# The keys and values should match the contents of MAILCAPFILE
++
++MAILCAPDICT = {
++    'application/x-movie':
++        [{'compose': 'moviemaker %s',
++          'x11-bitmap': '"/usr/lib/Zmail/bitmaps/movie.xbm"',
++          'description': '"Movie"',
++          'view': 'movieplayer %s',
++          'lineno': 4}],
++    'application/*':
++        [{'copiousoutput': '',
++          'view': 'echo "This is \\"%t\\" but        is 50 \\% Greek to me" \\; cat %s',
++          'lineno': 5}],
++    'audio/basic':
++        [{'edit': 'audiocompose %s',
++          'compose': 'audiocompose %s',
++          'description': '"An audio fragment"',
++          'view': 'showaudio %s',
++          'lineno': 6}],
++    'video/mpeg':
++        [{'view': 'mpeg_play %s', 'lineno': 13}],
++    'application/postscript':
++        [{'needsterminal': '', 'view': 'ps-to-terminal %s', 'lineno': 1},
++         {'compose': 'idraw %s', 'view': 'ps-to-terminal %s', 'lineno': 2}],
++    'application/x-dvi':
++        [{'view': 'xdvi %s', 'lineno': 3}],
++    'message/external-body':
++        [{'composetyped': 'extcompose %s',
++          'description': '"A reference to data stored in an external location"',
++          'needsterminal': '',
++          'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
++          'lineno': 10}],
++    'text/richtext':
++        [{'test': 'test "`echo     %{charset} | tr \'[A-Z]\' \'[a-z]\'`"  = iso-8859-8',
++          'copiousoutput': '',
++          'view': 'shownonascii iso-8859-8 -e richtext -p %s',
++          'lineno': 11}],
++    'image/x-xwindowdump':
++        [{'view': 'display %s', 'lineno': 9}],
++    'audio/*':
++        [{'view': '/usr/local/bin/showaudio %t', 'lineno': 7}],
++    'video/*':
++        [{'view': 'animate %s', 'lineno': 12}],
++    'application/frame':
++        [{'print': '"cat %s | lp"', 'view': 'showframe %s', 'lineno': 0}],
++    'image/rgb':
++        [{'view': 'display %s', 'lineno': 8}]
++}
++
++# In Python 2, mailcap doesn't return line numbers.
++# This test suite is copied from Python 3.11; for easier backporting we keep
++# data from there and remove the lineno.
++# So, for Python 2, MAILCAPDICT_DEPRECATED is the same as MAILCAPDICT
++MAILCAPDICT_DEPRECATED = MAILCAPDICT
++for entry_list in MAILCAPDICT_DEPRECATED.values():
++    for entry in entry_list:
++        entry.pop('lineno')
++
++
++class HelperFunctionTest(unittest.TestCase):
++
++    def test_listmailcapfiles(self):
++        # The return value for listmailcapfiles() will vary by system.
++        # So verify that listmailcapfiles() returns a list of strings that is of
++        # non-zero length.
++        mcfiles = mailcap.listmailcapfiles()
++        self.assertIsInstance(mcfiles, list)
++        for m in mcfiles:
++            self.assertIsInstance(m, str)
++        with os_helper.EnvironmentVarGuard() as env:
++            # According to RFC 1524, if MAILCAPS env variable exists, use that
++            # and only that.
++            if "MAILCAPS" in env:
++                env_mailcaps = env["MAILCAPS"].split(os.pathsep)
++            else:
++                env_mailcaps = ["/testdir1/.mailcap", "/testdir2/mailcap"]
++                env["MAILCAPS"] = os.pathsep.join(env_mailcaps)
++                mcfiles = mailcap.listmailcapfiles()
++        self.assertEqual(env_mailcaps, mcfiles)
++
++    def test_readmailcapfile(self):
++        # Test readmailcapfile() using test file. It should match MAILCAPDICT.
++        with open(MAILCAPFILE, 'r') as mcf:
++                d = mailcap.readmailcapfile(mcf)
++        self.assertDictEqual(d, MAILCAPDICT_DEPRECATED)
++
++    def test_lookup(self):
++        # Test without key
++
++        # In Python 2, 'video/mpeg' is tried before 'video/*'
++        # (unfixed bug: https://github.com/python/cpython/issues/59182 )
++        # So, these are in reverse order:
++        expected = [{'view': 'mpeg_play %s', },
++                    {'view': 'animate %s', }]
++        actual = mailcap.lookup(MAILCAPDICT, 'video/mpeg')
++        self.assertListEqual(expected, actual)
++
++        # Test with key
++        key = 'compose'
++        expected = [{'edit': 'audiocompose %s',
++                     'compose': 'audiocompose %s',
++                     'description': '"An audio fragment"',
++                     'view': 'showaudio %s',
++                     }]
++        actual = mailcap.lookup(MAILCAPDICT, 'audio/basic', key)
++        self.assertListEqual(expected, actual)
++
++        # Test on user-defined dicts without line numbers
++        expected = [{'view': 'mpeg_play %s'}, {'view': 'animate %s'}]
++        actual = mailcap.lookup(MAILCAPDICT_DEPRECATED, 'video/mpeg')
++        self.assertListEqual(expected, actual)
++
++    def test_subst(self):
++        plist = ['id=1', 'number=2', 'total=3']
++        # test case: ([field, MIMEtype, filename, plist=[]], <expected string>)
++        test_cases = [
++            (["", "audio/*", "foo.txt"], ""),
++            (["echo foo", "audio/*", "foo.txt"], "echo foo"),
++            (["echo %s", "audio/*", "foo.txt"], "echo foo.txt"),
++            (["echo %t", "audio/*", "foo.txt"], None),
++            (["echo %t", "audio/wav", "foo.txt"], "echo audio/wav"),
++            (["echo \\%t", "audio/*", "foo.txt"], "echo %t"),
++            (["echo foo", "audio/*", "foo.txt", plist], "echo foo"),
++            (["echo %{total}", "audio/*", "foo.txt", plist], "echo 3")
++        ]
++        for tc in test_cases:
++            self.assertEqual(mailcap.subst(*tc[0]), tc[1])
++
++class GetcapsTest(unittest.TestCase):
++
++    def test_mock_getcaps(self):
++        # Test mailcap.getcaps() using mock mailcap file in this dir.
++        # Temporarily override any existing system mailcap file by pointing the
++        # MAILCAPS environment variable to our mock file.
++        with os_helper.EnvironmentVarGuard() as env:
++            env["MAILCAPS"] = MAILCAPFILE
++            caps = mailcap.getcaps()
++            self.assertDictEqual(caps, MAILCAPDICT)
++
++    def test_system_mailcap(self):
++        # Test mailcap.getcaps() with mailcap file(s) on system, if any.
++        caps = mailcap.getcaps()
++        self.assertIsInstance(caps, dict)
++        mailcapfiles = mailcap.listmailcapfiles()
++        existingmcfiles = [mcf for mcf in mailcapfiles if os.path.exists(mcf)]
++        if existingmcfiles:
++            # At least 1 mailcap file exists, so test that.
++            for (k, v) in caps.items():
++                self.assertIsInstance(k, str)
++                self.assertIsInstance(v, list)
++                for e in v:
++                    self.assertIsInstance(e, dict)
++        else:
++            # No mailcap files on system. getcaps() should return empty dict.
++            self.assertEqual({}, caps)
++
++
++class FindmatchTest(unittest.TestCase):
++
++    def test_findmatch(self):
++
++        # default findmatch arguments
++        c = MAILCAPDICT
++        fname = "foo.txt"
++        plist = ["access-type=default", "name=john", "site=python.org",
++                 "directory=/tmp", "mode=foo", "server=bar"]
++        audio_basic_entry = {
++            'edit': 'audiocompose %s',
++            'compose': 'audiocompose %s',
++            'description': '"An audio fragment"',
++            'view': 'showaudio %s',
++        }
++        audio_entry = {"view": "/usr/local/bin/showaudio %t", }
++        video_entry = {'view': 'animate %s', }
++        mpeg_entry = {'view': 'mpeg_play %s', }
++        message_entry = {
++            'composetyped': 'extcompose %s',
++            'description': '"A reference to data stored in an external location"', 'needsterminal': '',
++            'view': 'showexternal %s %{access-type} %{name} %{site}     %{directory} %{mode} %{server}',
++        }
++
++        # test case: (findmatch args, findmatch keyword args, expected output)
++        #   positional args: caps, MIMEtype
++        #   keyword args: key="view", filename="/dev/null", plist=[]
++        #   output: (command line, mailcap entry)
++        cases = [
++            ([{}, "video/mpeg"], {}, (None, None)),
++            ([c, "foo/bar"], {}, (None, None)),
++
++            # In Python 2, 'video/mpeg' is tried before 'video/*'
++            # (unfixed bug: https://github.com/python/cpython/issues/59182 )
++            #([c, "video/mpeg"], {}, ('animate /dev/null', video_entry)),
++            ([c, "video/mpeg"], {}, ('mpeg_play /dev/null', mpeg_entry)),
++
++            ([c, "audio/basic", "edit"], {}, ("audiocompose /dev/null", audio_basic_entry)),
++            ([c, "audio/basic", "compose"], {}, ("audiocompose /dev/null", audio_basic_entry)),
++            ([c, "audio/basic", "description"], {}, ('"An audio fragment"', audio_basic_entry)),
++            ([c, "audio/basic", "foobar"], {}, (None, None)),
++            ([c, "video/*"], {"filename": fname}, ("animate %s" % fname, video_entry)),
++            ([c, "audio/basic", "compose"],
++             {"filename": fname},
++             ("audiocompose %s" % fname, audio_basic_entry)),
++            ([c, "audio/basic"],
++             {"key": "description", "filename": fname},
++             ('"An audio fragment"', audio_basic_entry)),
++            ([c, "audio/*"],
++             {"filename": fname},
++             (None, None)),
++            ([c, "audio/wav"],
++             {"filename": fname},
++             ("/usr/local/bin/showaudio audio/wav", audio_entry)),
++            ([c, "message/external-body"],
++             {"plist": plist},
++             ("showexternal /dev/null default john python.org     /tmp foo bar", message_entry))
++        ]
++        self._run_cases(cases)
++
++    @unittest.skipUnless(os.name == "posix", "Requires 'test' command on system")
++    @unittest.skipIf(sys.platform == "vxworks", "'test' command is not supported on VxWorks")
++    def test_test(self):
++        # findmatch() will automatically check any "test" conditions and skip
++        # the entry if the check fails.
++        caps = {"test/pass": [{"test": "test 1 -eq 1"}],
++                "test/fail": [{"test": "test 1 -eq 0"}]}
++        # test case: (findmatch args, findmatch keyword args, expected output)
++        #   positional args: caps, MIMEtype, key ("test")
++        #   keyword args: N/A
++        #   output: (command line, mailcap entry)
++        cases = [
++            # findmatch will return the mailcap entry for test/pass because it evaluates to true
++            ([caps, "test/pass", "test"], {}, ("test 1 -eq 1", {"test": "test 1 -eq 1"})),
++            # findmatch will return None because test/fail evaluates to false
++            ([caps, "test/fail", "test"], {}, (None, None))
++        ]
++        self._run_cases(cases)
++
++    def _run_cases(self, cases):
++        for c in cases:
++            self.assertEqual(mailcap.findmatch(*c[0], **c[1]), c[2])
++
++
++def test_main():
++    test.support.run_unittest(HelperFunctionTest, GetcapsTest, FindmatchTest)
+diff --git a/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst
+new file mode 100644
+index 00000000000..da81a1f6993
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2022-04-27-18-25-30.gh-issue-68966.gjS8zs.rst
+@@ -0,0 +1,4 @@
++The deprecated mailcap module now refuses to inject unsafe text (filenames,
++MIME types, parameters) into shell commands. Instead of using such text, it
++will warn and act as if a match was not found (or for test commands, as if
++the test failed).
diff --git a/CVE-2019-20907.patch b/CVE-2019-20907.patch
new file mode 100644
index 0000000..fa05cf0
--- /dev/null
+++ b/CVE-2019-20907.patch
@@ -0,0 +1,39 @@
+From 893e6e3aee483d262df70656a68f63f601720fcd Mon Sep 17 00:00:00 2001
+From: Rishi <rishi_devan at mail.com>
+Date: Wed, 15 Jul 2020 13:51:00 +0200
+Subject: [PATCH 01/36] bpo-39017: Avoid infinite loop in the tarfile module
+ (GH-21454)
+
+Avoid infinite loop when reading specially crafted TAR files using the tarfile module
+(CVE-2019-20907).
+
+[stripped test to avoid binary patch]
+---
+ Lib/tarfile.py                                                  | 2 ++
+ .../next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst       | 1 +
+ 2 files changed, 3 insertions(+)
+ create mode 100644 Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst
+
+diff --git a/Lib/tarfile.py b/Lib/tarfile.py
+index adf91d5382..574a6bb279 100644
+--- a/Lib/tarfile.py
++++ b/Lib/tarfile.py
+@@ -1400,6 +1400,8 @@ class TarInfo(object):
+ 
+             length, keyword = match.groups()
+             length = int(length)
++            if length == 0:
++                raise InvalidHeaderError("invalid header")
+             value = buf[match.end(2) + 1:match.start(1) + length - 1]
+ 
+             keyword = keyword.decode("utf8")
+diff --git a/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst
+new file mode 100644
+index 0000000000..ad26676f8b
+--- /dev/null
++++ b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst
+@@ -0,0 +1 @@
++Avoid infinite loop when reading specially crafted TAR files using the tarfile module (CVE-2019-20907).
+-- 
+2.38.1
+
diff --git a/CVE-2020-26116.patch b/CVE-2020-26116.patch
new file mode 100644
index 0000000..8f57119
--- /dev/null
+++ b/CVE-2020-26116.patch
@@ -0,0 +1,93 @@
+From 138e2caeb4827ccfd1eaff2cf63afb79dfeeb3c4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Thu, 10 Sep 2020 13:39:48 +0200
+Subject: [PATCH 03/36] bpo-39603: Prevent header injection in http methods
+ (GH-18485) (GH-21539)
+
+reject control chars in http method in http.client.putrequest to prevent http header injection
+(cherry picked from commit 8ca8a2e8fb068863c1138f07e3098478ef8be12e)
+
+Co-authored-by: AMIR <31338382+amiremohamadi at users.noreply.github.com>
+
+[rebased for py2.7]
+---
+ Lib/httplib.py           | 17 +++++++++++++++++
+ Lib/test/test_httplib.py | 20 ++++++++++++++++++++
+ 2 files changed, 37 insertions(+)
+
+diff --git a/Lib/httplib.py b/Lib/httplib.py
+index fcc4152aaf..81a08d5d71 100644
+--- a/Lib/httplib.py
++++ b/Lib/httplib.py
+@@ -257,6 +257,10 @@ _contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f-\xff]')
+ #  _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$")
+ # We are more lenient for assumed real world compatibility purposes.
+ 
++# These characters are not allowed within HTTP method names
++# to prevent http header injection.
++_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]')
++
+ # We always set the Content-Length header for these methods because some
+ # servers will otherwise respond with a 411
+ _METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'}
+@@ -935,6 +939,8 @@ class HTTPConnection:
+         else:
+             raise CannotSendRequest()
+ 
++        self._validate_method(method)
++
+         # Save the method for use later in the response phase
+         self._method = method
+ 
+@@ -1020,6 +1026,17 @@ class HTTPConnection:
+         # On Python 2, request is already encoded (default)
+         return request
+ 
++    def _validate_method(self, method):
++        """Validate a method name for putrequest."""
++        # prevent http header injection
++        match = _contains_disallowed_method_pchar_re.search(method)
++        if match:
++            msg = (
++                "method can't contain control characters. {method!r} "
++                "(found at least {matched!r})"
++            ).format(matched=match.group(), method=method)
++            raise ValueError(msg)
++
+     def _validate_path(self, url):
+         """Validate a url for putrequest."""
+         # Prevent CVE-2019-9740.
+diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
+index d8a57f7353..e20a0986dc 100644
+--- a/Lib/test/test_httplib.py
++++ b/Lib/test/test_httplib.py
+@@ -384,6 +384,26 @@ class HeaderTests(TestCase):
+             with self.assertRaisesRegexp(ValueError, 'Invalid header'):
+                 conn.putheader(name, value)
+ 
++    def test_invalid_method_names(self):
++        methods = (
++            'GET\r',
++            'POST\n',
++            'PUT\n\r',
++            'POST\nValue',
++            'POST\nHOST:abc',
++            'GET\nrHost:abc\n',
++            'POST\rRemainder:\r',
++            'GET\rHOST:\n',
++            '\nPUT'
++        )
++
++        for method in methods:
++            with self.assertRaisesRegexp(
++                    ValueError, "method can't contain control characters"):
++                conn = httplib.HTTPConnection('example.com')
++                conn.sock = FakeSocket(None)
++                conn.request(method=method, url="/")
++
+ 
+ class BasicTest(TestCase):
+     def test_status_lines(self):
+-- 
+2.38.1
+
diff --git a/CVE-2020-27619.patch b/CVE-2020-27619.patch
new file mode 100644
index 0000000..c429672
--- /dev/null
+++ b/CVE-2020-27619.patch
@@ -0,0 +1,119 @@
+From 6a6c4240fa1e628dbcca09fdde39aea4d8eb6138 Mon Sep 17 00:00:00 2001
+From: "Miss Skeleton (bot)" <31488909+miss-islington at users.noreply.github.com>
+Date: Mon, 19 Oct 2020 21:46:10 -0700
+Subject: [PATCH 05/36] bpo-41944: No longer call eval() on content received
+ via HTTP in the CJK codec tests (GH-22566) (GH-22579)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+(cherry picked from commit 2ef5caa58febc8968e670e39e3d37cf8eef3cab8)
+
+Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>
+
+Rebased for Python 2.7 by Michał Górny <mgorny at gentoo.org>
+---
+ Lib/test/multibytecodec_support.py            | 23 +++++++------------
+ .../2020-10-05-17-43-46.bpo-41944.rf1dYb.rst  |  1 +
+ 2 files changed, 9 insertions(+), 15 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Tests/2020-10-05-17-43-46.bpo-41944.rf1dYb.rst
+
+diff --git a/Lib/test/multibytecodec_support.py b/Lib/test/multibytecodec_support.py
+index 5b2329b6d8..b7d7a3aba7 100644
+--- a/Lib/test/multibytecodec_support.py
++++ b/Lib/test/multibytecodec_support.py
+@@ -279,30 +279,23 @@ class TestBase_Mapping(unittest.TestCase):
+             self._test_mapping_file_plain()
+ 
+     def _test_mapping_file_plain(self):
+-        _unichr = lambda c: eval("u'\\U%08x'" % int(c, 16))
+-        unichrs = lambda s: u''.join(_unichr(c) for c in s.split('+'))
++        def unichrs(s):
++            return ''.join(chr(int(x, 16)) for x in s.split('+'))
++
+         urt_wa = {}
+ 
+         with self.open_mapping_file() as f:
+             for line in f:
+                 if not line:
+                     break
+-                data = line.split('#')[0].strip().split()
++                data = line.split('#')[0].split()
+                 if len(data) != 2:
+                     continue
+ 
+-                csetval = eval(data[0])
+-                if csetval <= 0x7F:
+-                    csetch = chr(csetval & 0xff)
+-                elif csetval >= 0x1000000:
+-                    csetch = chr(csetval >> 24) + chr((csetval >> 16) & 0xff) + \
+-                             chr((csetval >> 8) & 0xff) + chr(csetval & 0xff)
+-                elif csetval >= 0x10000:
+-                    csetch = chr(csetval >> 16) + \
+-                             chr((csetval >> 8) & 0xff) + chr(csetval & 0xff)
+-                elif csetval >= 0x100:
+-                    csetch = chr(csetval >> 8) + chr(csetval & 0xff)
+-                else:
++                if data[0][:2] != '0x':
++                    self.fail("Invalid line: {line!r}".format(line=line))
++                csetch = bytes.fromhex(data[0][2:])
++                if len(csetch) == 1 and 0x80 <= csetch[0]:
+                     continue
+ 
+                 unich = unichrs(data[1])
+diff --git a/Misc/NEWS.d/next/Tests/2020-10-05-17-43-46.bpo-41944.rf1dYb.rst b/Misc/NEWS.d/next/Tests/2020-10-05-17-43-46.bpo-41944.rf1dYb.rst
+new file mode 100644
+index 0000000000..4f9782f1c8
+--- /dev/null
++++ b/Misc/NEWS.d/next/Tests/2020-10-05-17-43-46.bpo-41944.rf1dYb.rst
+@@ -0,0 +1 @@
++Tests for CJK codecs no longer call ``eval()`` on content received via HTTP.
+-- 
+2.38.1
+
+From ed1aa2f4738efe948242f252bcb0aa0b4314d2a2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Fri, 5 Mar 2021 10:34:50 +0100
+Subject: [PATCH 09/36] py2-ize the CJK codec test
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Michał Górny <mgorny at gentoo.org>
+---
+ Lib/test/multibytecodec_support.py | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/Lib/test/multibytecodec_support.py b/Lib/test/multibytecodec_support.py
+index b7d7a3aba7..661ef9ee37 100644
+--- a/Lib/test/multibytecodec_support.py
++++ b/Lib/test/multibytecodec_support.py
+@@ -2,6 +2,7 @@
+ #   Common Unittest Routines for CJK codecs
+ #
+ 
++import binascii
+ import codecs
+ import os
+ import re
+@@ -280,7 +281,7 @@ class TestBase_Mapping(unittest.TestCase):
+ 
+     def _test_mapping_file_plain(self):
+         def unichrs(s):
+-            return ''.join(chr(int(x, 16)) for x in s.split('+'))
++            return ''.join(unichr(int(x, 16)) for x in s.split('+'))
+ 
+         urt_wa = {}
+ 
+@@ -294,7 +295,7 @@ class TestBase_Mapping(unittest.TestCase):
+ 
+                 if data[0][:2] != '0x':
+                     self.fail("Invalid line: {line!r}".format(line=line))
+-                csetch = bytes.fromhex(data[0][2:])
++                csetch = binascii.a2b_hex(data[0][2:])
+                 if len(csetch) == 1 and 0x80 <= csetch[0]:
+                     continue
+ 
+-- 
+2.38.1
+
diff --git a/CVE-2020-8492.patch b/CVE-2020-8492.patch
new file mode 100644
index 0000000..de21686
--- /dev/null
+++ b/CVE-2020-8492.patch
@@ -0,0 +1,213 @@
+From 2273e65e11dd0234f2f51ebaef61fc6e848d4059 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Thu, 10 Sep 2020 13:35:39 +0200
+Subject: [PATCH 02/36] bpo-39503: CVE-2020-8492: Fix AbstractBasicAuthHandler
+ (GH-18284) (GH-19304)
+
+The AbstractBasicAuthHandler class of the urllib.request module uses
+an inefficient regular expression which can be exploited by an
+attacker to cause a denial of service. Fix the regex to prevent the
+catastrophic backtracking. Vulnerability reported by Ben Caller
+and Matt Schwager.
+
+AbstractBasicAuthHandler of urllib.request now parses all
+WWW-Authenticate HTTP headers and accepts multiple challenges per
+header: use the realm of the first Basic challenge.
+
+Co-Authored-By: Serhiy Storchaka <storchaka at gmail.com>
+(cherry picked from commit 0b297d4ff1c0e4480ad33acae793fbaf4bf015b4)
+
+[rebased for py2.7]
+---
+ Lib/test/test_urllib2.py | 81 ++++++++++++++++++++++++++--------------
+ Lib/urllib2.py           | 60 +++++++++++++++++++++++------
+ 2 files changed, 101 insertions(+), 40 deletions(-)
+
+diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
+index 20a0f58143..0adbb13c43 100644
+--- a/Lib/test/test_urllib2.py
++++ b/Lib/test/test_urllib2.py
+@@ -1128,42 +1128,67 @@ class HandlerTests(unittest.TestCase):
+         self.assertEqual(req.get_host(), "proxy.example.com:3128")
+         self.assertEqual(req.get_header("Proxy-authorization"),"FooBar")
+ 
+-    def test_basic_auth(self, quote_char='"'):
++    def check_basic_auth(self, headers, realm):
+         opener = OpenerDirector()
+         password_manager = MockPasswordManager()
+         auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
+-        realm = "ACME Widget Store"
+-        http_handler = MockHTTPHandler(
+-            401, 'WWW-Authenticate: Basic realm=%s%s%s\r\n\r\n' %
+-            (quote_char, realm, quote_char) )
++        body = '\r\n'.join(headers) + '\r\n\r\n'
++        http_handler = MockHTTPHandler(401, body)
+         opener.add_handler(auth_handler)
+         opener.add_handler(http_handler)
+         self._test_basic_auth(opener, auth_handler, "Authorization",
+                               realm, http_handler, password_manager,
+                               "http://acme.example.com/protected",
+-                              "http://acme.example.com/protected"
+-                             )
+-
+-    def test_basic_auth_with_single_quoted_realm(self):
+-        self.test_basic_auth(quote_char="'")
+-
+-    def test_basic_auth_with_unquoted_realm(self):
+-        opener = OpenerDirector()
+-        password_manager = MockPasswordManager()
+-        auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
+-        realm = "ACME Widget Store"
+-        http_handler = MockHTTPHandler(
+-            401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm)
+-        opener.add_handler(auth_handler)
+-        opener.add_handler(http_handler)
+-        msg = "Basic Auth Realm was unquoted"
+-        with test_support.check_warnings((msg, UserWarning)):
+-            self._test_basic_auth(opener, auth_handler, "Authorization",
+-                                  realm, http_handler, password_manager,
+-                                  "http://acme.example.com/protected",
+-                                  "http://acme.example.com/protected"
+-                                 )
+-
++                              "http://acme.example.com/protected")
++
++    def test_basic_auth(self):
++        realm = "realm2 at example.com"
++        realm2 = "realm2 at example.com"
++        basic = 'Basic realm="{realm}"'.format(realm=realm)
++        basic2 = 'Basic realm="{realm2}"'.format(realm2=realm2)
++        other_no_realm = 'Otherscheme xxx'
++        digest = ('Digest realm="{realm2}", '
++                  'qop="auth, auth-int", '
++                  'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", '
++                  'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
++                  .format(realm2=realm2))
++        for realm_str in (
++            # test "quote" and 'quote'
++            'Basic realm="{realm}"'.format(realm=realm),
++            "Basic realm='{realm}'".format(realm=realm),
++
++            # charset is ignored
++            'Basic realm="{realm}", charset="UTF-8"'.format(realm=realm),
++
++            # Multiple challenges per header
++            ', '.join((basic, basic2)),
++            ', '.join((basic, other_no_realm)),
++            ', '.join((other_no_realm, basic)),
++            ', '.join((basic, digest)),
++            ', '.join((digest, basic)),
++        ):
++            headers = ['WWW-Authenticate: {realm_str}'
++                       .format(realm_str=realm_str)]
++            self.check_basic_auth(headers, realm)
++
++        # no quote: expect a warning
++        with test_support.check_warnings(("Basic Auth Realm was unquoted",
++                                     UserWarning)):
++            headers = ['WWW-Authenticate: Basic realm={realm}'
++                       .format(realm=realm)]
++            self.check_basic_auth(headers, realm)
++
++        # Multiple headers: one challenge per header.
++        # Use the first Basic realm.
++        for challenges in (
++            [basic,  basic2],
++            [basic,  digest],
++            [digest, basic],
++        ):
++            headers = ['WWW-Authenticate: {challenge}'
++                       .format(challenge=challenge)
++                       for challenge in challenges]
++            self.check_basic_auth(headers, realm)
+ 
+     def test_proxy_basic_auth(self):
+         opener = OpenerDirector()
+diff --git a/Lib/urllib2.py b/Lib/urllib2.py
+index 8b634ada37..b2d1fad6f2 100644
+--- a/Lib/urllib2.py
++++ b/Lib/urllib2.py
+@@ -856,8 +856,15 @@ class AbstractBasicAuthHandler:
+ 
+     # allow for double- and single-quoted realm values
+     # (single quotes are a violation of the RFC, but appear in the wild)
+-    rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
+-                    'realm=(["\']?)([^"\']*)\\2', re.I)
++    rx = re.compile('(?:^|,)'   # start of the string or ','
++                    '[ \t]*'    # optional whitespaces
++                    '([^ \t]+)' # scheme like "Basic"
++                    '[ \t]+'    # mandatory whitespaces
++                    # realm=xxx
++                    # realm='xxx'
++                    # realm="xxx"
++                    'realm=(["\']?)([^"\']*)\\2',
++                    re.I)
+ 
+     # XXX could pre-emptively send auth info already accepted (RFC 2617,
+     # end of section 2, and section 1.2 immediately after "credentials"
+@@ -869,23 +876,52 @@ class AbstractBasicAuthHandler:
+         self.passwd = password_mgr
+         self.add_password = self.passwd.add_password
+ 
++    def _parse_realm(self, header):
++        # parse WWW-Authenticate header: accept multiple challenges per header
++        found_challenge = False
++        for mo in AbstractBasicAuthHandler.rx.finditer(header):
++            scheme, quote, realm = mo.groups()
++            if quote not in ['"', "'"]:
++                warnings.warn("Basic Auth Realm was unquoted",
++                              UserWarning, 3)
++
++            yield (scheme, realm)
++
++            found_challenge = True
++
++        if not found_challenge:
++            if header:
++                scheme = header.split()[0]
++            else:
++                scheme = ''
++            yield (scheme, None)
+ 
+     def http_error_auth_reqed(self, authreq, host, req, headers):
+         # host may be an authority (without userinfo) or a URL with an
+         # authority
+-        # XXX could be multiple headers
+-        authreq = headers.get(authreq, None)
++        headers = headers.getheaders(authreq)
++        if not headers:
++            # no header found
++            return
+ 
+-        if authreq:
+-            mo = AbstractBasicAuthHandler.rx.search(authreq)
+-            if mo:
+-                scheme, quote, realm = mo.groups()
+-                if quote not in ['"', "'"]:
+-                    warnings.warn("Basic Auth Realm was unquoted",
+-                                  UserWarning, 2)
+-                if scheme.lower() == 'basic':
++        unsupported = None
++        for header in headers:
++            for scheme, realm in self._parse_realm(header):
++                if scheme.lower() != 'basic':
++                    unsupported = scheme
++                    continue
++
++                if realm is not None:
++                    # Use the first matching Basic challenge.
++                    # Ignore following challenges even if they use the Basic
++                    # scheme.
+                     return self.retry_http_basic_auth(host, req, realm)
+ 
++        if unsupported is not None:
++            raise ValueError("AbstractBasicAuthHandler does not "
++                             "support the following scheme: %r"
++                             % (scheme,))
++
+     def retry_http_basic_auth(self, host, req, realm):
+         user, pw = self.passwd.find_user_password(realm, host)
+         if pw is not None:
+-- 
+2.38.1
+
diff --git a/CVE-2021-23336.patch b/CVE-2021-23336.patch
new file mode 100644
index 0000000..f47be77
--- /dev/null
+++ b/CVE-2021-23336.patch
@@ -0,0 +1,390 @@
+From 255d1d464a7f45a20986aa014c78e85ef47e6591 Mon Sep 17 00:00:00 2001
+From: Senthil Kumaran <senthil at uthcode.com>
+Date: Mon, 15 Feb 2021 11:16:43 -0800
+Subject: [PATCH 08/36] [3.6] bpo-42967: only use '&' as a query string
+ separator (GH-24297)  (GH-24532)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+bpo-42967: [security] Address a web cache-poisoning issue reported in
+urllib.parse.parse_qsl().
+
+urllib.parse will only us "&" as query string separator by default
+instead of both ";" and "&" as allowed in earlier versions. An optional
+argument seperator with default value "&" is added to specify the
+separator.
+
+Co-authored-by: Éric Araujo <merwok at netwok.org>
+Co-authored-by: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
+Co-authored-by: Adam Goldschmidt <adamgold7 at gmail.com>
+
+Rebased for Python 2.7 by Michał Górny
+---
+ Doc/library/cgi.rst                           |  7 +++-
+ Doc/library/urlparse.rst                      | 23 ++++++++++-
+ Lib/cgi.py                                    | 20 +++++++---
+ Lib/test/test_cgi.py                          | 29 +++++++++++---
+ Lib/test/test_urlparse.py                     | 38 +++++++++----------
+ Lib/urlparse.py                               | 22 ++++++++---
+ .../2021-02-14-15-59-16.bpo-42967.YApqDS.rst  |  1 +
+ 7 files changed, 100 insertions(+), 40 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst
+
+diff --git a/Doc/library/cgi.rst b/Doc/library/cgi.rst
+index ecd62c8c01..b85cdd8b61 100644
+--- a/Doc/library/cgi.rst
++++ b/Doc/library/cgi.rst
+@@ -285,10 +285,10 @@ These are useful if you want more control, or if you want to employ some of the
+ algorithms implemented in this module in other circumstances.
+ 
+ 
+-.. function:: parse(fp[, environ[, keep_blank_values[, strict_parsing]]])
++.. function:: parse(fp[, environ[, keep_blank_values[, strict_parsing]]], separator="&")
+ 
+    Parse a query in the environment or from a file (the file defaults to
+-   ``sys.stdin`` and environment defaults to ``os.environ``).  The *keep_blank_values* and *strict_parsing* parameters are
++   ``sys.stdin`` and environment defaults to ``os.environ``).  The *keep_blank_values*, *strict_parsing* and *separator* parameters are
+    passed to :func:`urlparse.parse_qs` unchanged.
+ 
+ 
+@@ -316,6 +316,9 @@ algorithms implemented in this module in other circumstances.
+    Note that this does not parse nested multipart parts --- use
+    :class:`FieldStorage` for that.
+ 
++   .. versionchanged:: 3.6.13
++      Added the *separator* parameter.
++
+ 
+ .. function:: parse_header(string)
+ 
+diff --git a/Doc/library/urlparse.rst b/Doc/library/urlparse.rst
+index 0989c88c30..2f8e4c5a44 100644
+--- a/Doc/library/urlparse.rst
++++ b/Doc/library/urlparse.rst
+@@ -136,7 +136,7 @@ The :mod:`urlparse` module defines the following functions:
+       now raise :exc:`ValueError`.
+ 
+ 
+-.. function:: parse_qs(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]])
++.. function:: parse_qs(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]], separator='&')
+ 
+    Parse a query string given as a string argument (data of type
+    :mimetype:`application/x-www-form-urlencoded`).  Data are returned as a
+@@ -157,6 +157,9 @@ The :mod:`urlparse` module defines the following functions:
+    read. If set, then throws a :exc:`ValueError` if there are more than
+    *max_num_fields* fields read.
+ 
++   The optional argument *separator* is the symbol to use for separating the
++   query arguments. It defaults to ``&``.
++
+    Use the :func:`urllib.urlencode` function to convert such dictionaries into
+    query strings.
+ 
+@@ -166,7 +169,14 @@ The :mod:`urlparse` module defines the following functions:
+    .. versionchanged:: 2.7.16
+       Added *max_num_fields* parameter.
+ 
+-.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]])
++   .. versionchanged:: 2.7.18-gentoo
++      Added *separator* parameter with the default value of ``&``. Earlier
++      Python versions allowed using both ``;`` and ``&`` as query parameter
++      separator. This has been changed to allow only a single separator key,
++      with ``&`` as the default separator.
++
++
++.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]], separator='&')
+ 
+    Parse a query string given as a string argument (data of type
+    :mimetype:`application/x-www-form-urlencoded`).  Data are returned as a list of
+@@ -186,6 +196,9 @@ The :mod:`urlparse` module defines the following functions:
+    read. If set, then throws a :exc:`ValueError` if there are more than
+    *max_num_fields* fields read.
+ 
++   The optional argument *separator* is the symbol to use for separating the
++   query arguments. It defaults to ``&``.
++
+    Use the :func:`urllib.urlencode` function to convert such lists of pairs into
+    query strings.
+ 
+@@ -195,6 +208,12 @@ The :mod:`urlparse` module defines the following functions:
+    .. versionchanged:: 2.7.16
+       Added *max_num_fields* parameter.
+ 
++   .. versionchanged:: 2.7.18-gentoo
++      Added *separator* parameter with the default value of ``&``. Earlier
++      Python versions allowed using both ``;`` and ``&`` as query parameter
++      separator. This has been changed to allow only a single separator key,
++      with ``&`` as the default separator.
++
+ .. function:: urlunparse(parts)
+ 
+    Construct a URL from a tuple as returned by ``urlparse()``. The *parts* argument
+diff --git a/Lib/cgi.py b/Lib/cgi.py
+index 5b903e0347..9d0848b6b1 100755
+--- a/Lib/cgi.py
++++ b/Lib/cgi.py
+@@ -121,7 +121,8 @@ log = initlog           # The current logging function
+ # 0 ==> unlimited input
+ maxlen = 0
+ 
+-def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
++def parse(fp=None, environ=os.environ, keep_blank_values=0,
++          strict_parsing=0, separator='&'):
+     """Parse a query in the environment or from a file (default stdin)
+ 
+         Arguments, all optional:
+@@ -140,6 +141,9 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
+         strict_parsing: flag indicating what to do with parsing errors.
+             If false (the default), errors are silently ignored.
+             If true, errors raise a ValueError exception.
++
++        separator: str. The symbol to use for separating the query arguments.
++            Defaults to &.
+     """
+     if fp is None:
+         fp = sys.stdin
+@@ -171,7 +175,8 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
+         else:
+             qs = ""
+         environ['QUERY_STRING'] = qs    # XXX Shouldn't, really
+-    return urlparse.parse_qs(qs, keep_blank_values, strict_parsing)
++    return urlparse.parse_qs(qs, keep_blank_values, strict_parsing,
++                             separator=separator)
+ 
+ 
+ # parse query string function called from urlparse,
+@@ -395,7 +400,7 @@ class FieldStorage:
+ 
+     def __init__(self, fp=None, headers=None, outerboundary="",
+                  environ=os.environ, keep_blank_values=0, strict_parsing=0,
+-                 max_num_fields=None):
++                 max_num_fields=None, separator='&'):
+         """Constructor.  Read multipart/* until last part.
+ 
+         Arguments, all optional:
+@@ -430,6 +435,7 @@ class FieldStorage:
+         self.keep_blank_values = keep_blank_values
+         self.strict_parsing = strict_parsing
+         self.max_num_fields = max_num_fields
++        self.separator = separator
+         if 'REQUEST_METHOD' in environ:
+             method = environ['REQUEST_METHOD'].upper()
+         self.qs_on_post = None
+@@ -613,7 +619,8 @@ class FieldStorage:
+         if self.qs_on_post:
+             qs += '&' + self.qs_on_post
+         query = urlparse.parse_qsl(qs, self.keep_blank_values,
+-                                   self.strict_parsing, self.max_num_fields)
++                                   self.strict_parsing, self.max_num_fields,
++                                   separator=self.separator)
+         self.list = [MiniFieldStorage(key, value) for key, value in query]
+         self.skip_lines()
+ 
+@@ -629,7 +636,8 @@ class FieldStorage:
+             query = urlparse.parse_qsl(self.qs_on_post,
+                                        self.keep_blank_values,
+                                        self.strict_parsing,
+-                                       self.max_num_fields)
++                                       self.max_num_fields,
++                                       separator=self.separator)
+             self.list.extend(MiniFieldStorage(key, value)
+                              for key, value in query)
+             FieldStorageClass = None
+@@ -649,7 +657,7 @@ class FieldStorage:
+             headers = rfc822.Message(self.fp)
+             part = klass(self.fp, headers, ib,
+                          environ, keep_blank_values, strict_parsing,
+-                         max_num_fields)
++                         max_num_fields, separator=self.separator)
+ 
+             if max_num_fields is not None:
+                 max_num_fields -= 1
+diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py
+index 743c2afbd4..f414faa23b 100644
+--- a/Lib/test/test_cgi.py
++++ b/Lib/test/test_cgi.py
+@@ -61,12 +61,9 @@ parse_strict_test_cases = [
+     ("", ValueError("bad query field: ''")),
+     ("&", ValueError("bad query field: ''")),
+     ("&&", ValueError("bad query field: ''")),
+-    (";", ValueError("bad query field: ''")),
+-    (";&;", ValueError("bad query field: ''")),
+     # Should the next few really be valid?
+     ("=", {}),
+     ("=&=", {}),
+-    ("=;=", {}),
+     # This rest seem to make sense
+     ("=a", {'': ['a']}),
+     ("&=a", ValueError("bad query field: ''")),
+@@ -81,8 +78,6 @@ parse_strict_test_cases = [
+     ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
+     ("a=a+b&a=b+a", {'a': ['a b', 'b a']}),
+     ("x=1&y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
+-    ("x=1;y=2.0&z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
+-    ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
+     ("Hbc5161168c542333633315dee1182227:key_store_seqid=400006&cuyer=r&view=bustomer&order_id=0bb2e248638833d48cb7fed300000f1b&expire=964546263&lobale=en-US&kid=130003.300038&ss=env",
+      {'Hbc5161168c542333633315dee1182227:key_store_seqid': ['400006'],
+       'cuyer': ['r'],
+@@ -188,6 +183,30 @@ class CgiTests(unittest.TestCase):
+             self.assertEqual(expect[k], v)
+         self.assertItemsEqual(expect.values(), d.values())
+ 
++    def test_separator(self):
++        parse_semicolon = [
++            ("x=1;y=2.0", {'x': ['1'], 'y': ['2.0']}),
++            ("x=1;y=2.0;z=2-3.%2b0", {'x': ['1'], 'y': ['2.0'], 'z': ['2-3.+0']}),
++            (";", ValueError("bad query field: ''")),
++            (";;", ValueError("bad query field: ''")),
++            ("=;a", ValueError("bad query field: 'a'")),
++            (";b=a", ValueError("bad query field: ''")),
++            ("b;=a", ValueError("bad query field: 'b'")),
++            ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
++            ("a=a+b;a=b+a", {'a': ['a b', 'b a']}),
++        ]
++        for orig, expect in parse_semicolon:
++            env = {'QUERY_STRING': orig}
++            fs = cgi.FieldStorage(separator=';', environ=env)
++            if isinstance(expect, dict):
++                for key in expect.keys():
++                    expect_val = expect[key]
++                    self.assertIn(key, fs)
++                    if len(expect_val) > 1:
++                        self.assertEqual(fs.getvalue(key), expect_val)
++                    else:
++                        self.assertEqual(fs.getvalue(key), expect_val[0])
++
+     def test_log(self):
+         cgi.log("Testing")
+ 
+diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
+index 86c4a0595c..0b2107339a 100644
+--- a/Lib/test/test_urlparse.py
++++ b/Lib/test/test_urlparse.py
+@@ -24,16 +24,20 @@ parse_qsl_test_cases = [
+     ("&a=b", [('a', 'b')]),
+     ("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]),
+     ("a=1&a=2", [('a', '1'), ('a', '2')]),
+-    (";", []),
+-    (";;", []),
+-    (";a=b", [('a', 'b')]),
+-    ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
+-    ("a=1;a=2", [('a', '1'), ('a', '2')]),
+-    (b";", []),
+-    (b";;", []),
+-    (b";a=b", [(b'a', b'b')]),
+-    (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
+-    (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
++    (b"", []),
++    (b"&", []),
++    (b"&&", []),
++    (b"=", [(b'', b'')]),
++    (b"=a", [(b'', b'a')]),
++    (b"a", [(b'a', b'')]),
++    (b"a=", [(b'a', b'')]),
++    (b"&a=b", [(b'a', b'b')]),
++    (b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
++    (b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]),
++    (";a=b", [(';a', 'b')]),
++    ("a=a+b;b=b+c", [('a', 'a b;b=b c')]),
++    (b";a=b", [(b';a', b'b')]),
++    (b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]),
+ ]
+ 
+ parse_qs_test_cases = [
+@@ -57,16 +61,10 @@ parse_qs_test_cases = [
+     (b"&a=b", {b'a': [b'b']}),
+     (b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
+     (b"a=1&a=2", {b'a': [b'1', b'2']}),
+-    (";", {}),
+-    (";;", {}),
+-    (";a=b", {'a': ['b']}),
+-    ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
+-    ("a=1;a=2", {'a': ['1', '2']}),
+-    (b";", {}),
+-    (b";;", {}),
+-    (b";a=b", {b'a': [b'b']}),
+-    (b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
+-    (b"a=1;a=2", {b'a': [b'1', b'2']}),
++    (";a=b", {';a': ['b']}),
++    ("a=a+b;b=b+c", {'a': ['a b;b=b c']}),
++    (b";a=b", {b';a': [b'b']}),
++    (b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}),
+ ]
+ 
+ class UrlParseTestCase(unittest.TestCase):
+diff --git a/Lib/urlparse.py b/Lib/urlparse.py
+index 798b467b60..6c32727fce 100644
+--- a/Lib/urlparse.py
++++ b/Lib/urlparse.py
+@@ -382,7 +382,8 @@ def unquote(s):
+             append(item)
+     return ''.join(res)
+ 
+-def parse_qs(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None):
++def parse_qs(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None,
++             separator='&'):
+     """Parse a query given as a string argument.
+ 
+         Arguments:
+@@ -402,17 +403,22 @@ def parse_qs(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None):
+ 
+         max_num_fields: int. If set, then throws a ValueError if there
+             are more than n fields read by parse_qsl().
++
++        separator: str. The symbol to use for separating the query arguments.
++            Defaults to &.
++
+     """
+     dict = {}
+     for name, value in parse_qsl(qs, keep_blank_values, strict_parsing,
+-                                 max_num_fields):
++                                 max_num_fields, separator=separator):
+         if name in dict:
+             dict[name].append(value)
+         else:
+             dict[name] = [value]
+     return dict
+ 
+-def parse_qsl(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None):
++def parse_qsl(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None,
++              separator='&'):
+     """Parse a query given as a string argument.
+ 
+     Arguments:
+@@ -432,17 +438,23 @@ def parse_qsl(qs, keep_blank_values=0, strict_parsing=0, max_num_fields=None):
+     max_num_fields: int. If set, then throws a ValueError if there
+         are more than n fields read by parse_qsl().
+ 
++        separator: str. The symbol to use for separating the query arguments.
++            Defaults to &.
++
+     Returns a list, as G-d intended.
+     """
++    if not separator or (not isinstance(separator, (str, bytes))):
++        raise ValueError("Separator must be of type string or bytes.")
++
+     # If max_num_fields is defined then check that the number of fields
+     # is less than max_num_fields. This prevents a memory exhaustion DOS
+     # attack via post bodies with many fields.
+     if max_num_fields is not None:
+-        num_fields = 1 + qs.count('&') + qs.count(';')
++        num_fields = 1 + qs.count(separator)
+         if max_num_fields < num_fields:
+             raise ValueError('Max number of fields exceeded')
+ 
+-    pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
++    pairs = [s1 for s1 in qs.split(separator)]
+     r = []
+     for name_value in pairs:
+         if not name_value and not strict_parsing:
+diff --git a/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst b/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst
+new file mode 100644
+index 0000000000..f08489b414
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst
+@@ -0,0 +1 @@
++Fix web cache poisoning vulnerability by defaulting the query args separator to ``&``, and allowing the user to choose a custom separator.
+-- 
+2.38.1
+
diff --git a/CVE-2021-3177.patch b/CVE-2021-3177.patch
new file mode 100644
index 0000000..adbd360
--- /dev/null
+++ b/CVE-2021-3177.patch
@@ -0,0 +1,181 @@
+From fab838b2ee7cfb9037c24f0f18dfe01aa379b3f7 Mon Sep 17 00:00:00 2001
+From: Benjamin Peterson <benjamin at python.org>
+Date: Mon, 18 Jan 2021 15:11:46 -0600
+Subject: [PATCH 07/36] [3.6] closes bpo-42938: Replace snprintf with Python
+ unicode formatting in ctypes param reprs. (GH-24250)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+(cherry picked from commit 916610ef90a0d0761f08747f7b0905541f0977c7)
+
+Co-authored-by: Benjamin Peterson <benjamin at python.org>
+Rebased for Python 2.7 by Michał Górny <mgorny at gentoo.org>
+---
+ Lib/ctypes/test/test_parameters.py            | 43 ++++++++++++++++
+ .../2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst  |  2 +
+ Modules/_ctypes/callproc.c                    | 49 +++++++++----------
+ 3 files changed, 69 insertions(+), 25 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst
+
+diff --git a/Lib/ctypes/test/test_parameters.py b/Lib/ctypes/test/test_parameters.py
+index 23c1b6e225..3456882ccb 100644
+--- a/Lib/ctypes/test/test_parameters.py
++++ b/Lib/ctypes/test/test_parameters.py
+@@ -206,6 +206,49 @@ class SimpleTypesTestCase(unittest.TestCase):
+         with self.assertRaises(ZeroDivisionError):
+             WorseStruct().__setstate__({}, b'foo')
+ 
++    def test_parameter_repr(self):
++        from ctypes import (
++            c_bool,
++            c_char,
++            c_wchar,
++            c_byte,
++            c_ubyte,
++            c_short,
++            c_ushort,
++            c_int,
++            c_uint,
++            c_long,
++            c_ulong,
++            c_longlong,
++            c_ulonglong,
++            c_float,
++            c_double,
++            c_longdouble,
++            c_char_p,
++            c_wchar_p,
++            c_void_p,
++        )
++        self.assertRegexpMatches(repr(c_bool.from_param(True)), r"^<cparam '\?' at 0x[A-Fa-f0-9]+>$")
++        self.assertEqual(repr(c_char.from_param('a')), "<cparam 'c' (a)>")
++        self.assertRegexpMatches(repr(c_wchar.from_param('a')), r"^<cparam 'u' at 0x[A-Fa-f0-9]+>$")
++        self.assertEqual(repr(c_byte.from_param(98)), "<cparam 'b' (98)>")
++        self.assertEqual(repr(c_ubyte.from_param(98)), "<cparam 'B' (98)>")
++        self.assertEqual(repr(c_short.from_param(511)), "<cparam 'h' (511)>")
++        self.assertEqual(repr(c_ushort.from_param(511)), "<cparam 'H' (511)>")
++        self.assertRegexpMatches(repr(c_int.from_param(20000)), r"^<cparam '[li]' \(20000\)>$")
++        self.assertRegexpMatches(repr(c_uint.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$")
++        self.assertRegexpMatches(repr(c_long.from_param(20000)), r"^<cparam '[li]' \(20000\)>$")
++        self.assertRegexpMatches(repr(c_ulong.from_param(20000)), r"^<cparam '[LI]' \(20000\)>$")
++        self.assertRegexpMatches(repr(c_longlong.from_param(20000)), r"^<cparam '[liq]' \(20000\)>$")
++        self.assertRegexpMatches(repr(c_ulonglong.from_param(20000)), r"^<cparam '[LIQ]' \(20000\)>$")
++        self.assertEqual(repr(c_float.from_param(1.5)), "<cparam 'f' (1.5)>")
++        self.assertEqual(repr(c_double.from_param(1.5)), "<cparam 'd' (1.5)>")
++        self.assertEqual(repr(c_double.from_param(1e300)), "<cparam 'd' (1e+300)>")
++        self.assertRegexpMatches(repr(c_longdouble.from_param(1.5)), r"^<cparam ('d' \(1.5\)|'g' at 0x[A-Fa-f0-9]+)>$")
++        self.assertRegexpMatches(repr(c_char_p.from_param(b'hihi')), "^<cparam 'z' \(0x[A-Fa-f0-9]+\)>$")
++        self.assertRegexpMatches(repr(c_wchar_p.from_param('hihi')), "^<cparam 'Z' \(0x[A-Fa-f0-9]+\)>$")
++        self.assertRegexpMatches(repr(c_void_p.from_param(0x12)), r"^<cparam 'P' \(0x0*12\)>$")
++
+ ################################################################
+ 
+ if __name__ == '__main__':
+diff --git a/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst b/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst
+new file mode 100644
+index 0000000000..7df65a156f
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst
+@@ -0,0 +1,2 @@
++Avoid static buffers when computing the repr of :class:`ctypes.c_double` and
++:class:`ctypes.c_longdouble` values.
+diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c
+index 066fefc0cc..421addf353 100644
+--- a/Modules/_ctypes/callproc.c
++++ b/Modules/_ctypes/callproc.c
+@@ -460,50 +460,51 @@ PyCArg_dealloc(PyCArgObject *self)
+ static PyObject *
+ PyCArg_repr(PyCArgObject *self)
+ {
+-    char buffer[256];
+     switch(self->tag) {
+     case 'b':
+     case 'B':
+-        sprintf(buffer, "<cparam '%c' (%d)>",
++        return PyString_FromFormat("<cparam '%c' (%d)>",
+             self->tag, self->value.b);
+-        break;
+     case 'h':
+     case 'H':
+-        sprintf(buffer, "<cparam '%c' (%d)>",
++        return PyString_FromFormat("<cparam '%c' (%d)>",
+             self->tag, self->value.h);
+-        break;
+     case 'i':
+     case 'I':
+-        sprintf(buffer, "<cparam '%c' (%d)>",
++        return PyString_FromFormat("<cparam '%c' (%d)>",
+             self->tag, self->value.i);
+-        break;
+     case 'l':
+     case 'L':
+-        sprintf(buffer, "<cparam '%c' (%ld)>",
++        return PyString_FromFormat("<cparam '%c' (%ld)>",
+             self->tag, self->value.l);
+-        break;
+ 
+ #ifdef HAVE_LONG_LONG
+     case 'q':
+     case 'Q':
+-        sprintf(buffer,
++        return PyString_FromFormat(
+             "<cparam '%c' (%" PY_FORMAT_LONG_LONG "d)>",
+             self->tag, self->value.q);
+-        break;
+ #endif
+     case 'd':
+-        sprintf(buffer, "<cparam '%c' (%f)>",
+-            self->tag, self->value.d);
+-        break;
+-    case 'f':
+-        sprintf(buffer, "<cparam '%c' (%f)>",
+-            self->tag, self->value.f);
+-        break;
+-
++    case 'f': {
++        PyObject *f = PyFloat_FromDouble((self->tag == 'f') ? self->value.f : self->value.d);
++        if (f == NULL) {
++            return NULL;
++        }
++        PyObject *r = PyObject_Repr(f);
++        if (r == NULL) {
++            Py_DECREF(f);
++            return NULL;
++        }
++        PyObject *result = PyString_FromFormat(
++            "<cparam '%c' (%s)>", self->tag, PyString_AsString(r));
++        Py_DECREF(r);
++        Py_DECREF(f);
++        return result;
++    }
+     case 'c':
+-        sprintf(buffer, "<cparam '%c' (%c)>",
++        return PyString_FromFormat("<cparam '%c' (%c)>",
+             self->tag, self->value.c);
+-        break;
+ 
+ /* Hm, are these 'z' and 'Z' codes useful at all?
+    Shouldn't they be replaced by the functionality of c_string
+@@ -512,16 +513,14 @@ PyCArg_repr(PyCArgObject *self)
+     case 'z':
+     case 'Z':
+     case 'P':
+-        sprintf(buffer, "<cparam '%c' (%p)>",
++        return PyString_FromFormat("<cparam '%c' (%p)>",
+             self->tag, self->value.p);
+         break;
+ 
+     default:
+-        sprintf(buffer, "<cparam '%c' at %p>",
++        return PyString_FromFormat("<cparam '%c' at %p>",
+             self->tag, self);
+-        break;
+     }
+-    return PyString_FromString(buffer);
+ }
+ 
+ static PyMemberDef PyCArgType_members[] = {
+-- 
+2.38.1
+
diff --git a/CVE-2021-3733.patch b/CVE-2021-3733.patch
new file mode 100644
index 0000000..98d15df
--- /dev/null
+++ b/CVE-2021-3733.patch
@@ -0,0 +1,39 @@
+From f9e5d7ae605bd9739b1cf3423f03daa909ee4a0c Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Wed, 7 Apr 2021 04:45:05 -0700
+Subject: [PATCH 11/36] bpo-43075: Fix ReDoS in urllib AbstractBasicAuthHandler
+ (GH-24391)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fix Regular Expression Denial of Service (ReDoS) vulnerability in
+urllib.request.AbstractBasicAuthHandler. The ReDoS-vulnerable regex
+has quadratic worst-case complexity and it allows cause a denial of
+service when identifying crafted invalid RFCs. This ReDoS issue is on
+the client side and needs remote attackers to control the HTTP server.
+(cherry picked from commit 7215d1ae25525c92b026166f9d5cac85fb1defe1)
+
+Co-authored-by: Yeting Li <liyt at ios.ac.cn>
+(backported to Python 2.7 by Michał Górny)
+---
+ Lib/urllib2.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Lib/urllib2.py b/Lib/urllib2.py
+index b2d1fad6f2..c92fb6afb8 100644
+--- a/Lib/urllib2.py
++++ b/Lib/urllib2.py
+@@ -858,7 +858,7 @@ class AbstractBasicAuthHandler:
+     # (single quotes are a violation of the RFC, but appear in the wild)
+     rx = re.compile('(?:^|,)'   # start of the string or ','
+                     '[ \t]*'    # optional whitespaces
+-                    '([^ \t]+)' # scheme like "Basic"
++                    '([^ \t,]+)' # scheme like "Basic"
+                     '[ \t]+'    # mandatory whitespaces
+                     # realm=xxx
+                     # realm='xxx'
+-- 
+2.38.1
+
diff --git a/CVE-2021-3737.patch b/CVE-2021-3737.patch
new file mode 100644
index 0000000..64622f9
--- /dev/null
+++ b/CVE-2021-3737.patch
@@ -0,0 +1,83 @@
+From dae96acef25d8c95aff9673e29aa5ddee862e9b4 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Sat, 19 Jun 2021 20:46:09 +0200
+Subject: [PATCH 13/36] Backport bpo-44022: Fix http client infinite line
+ reading (DoS) after a HTTP 100 Continue (GH-25916)
+
+Backport the fix from the following commit:
+
+    commit 47895e31b6f626bc6ce47d175fe9d43c1098909d
+    Author: Gen Xu <xgbarry at gmail.com>
+    Date:   2021-05-06 00:42:41 +0200
+
+        bpo-44022: Fix http client infinite line reading (DoS) after a HTTP 100 Continue (GH-25916)
+
+        Fixes http.client potential denial of service where it could get stuck reading lines from a malicious server after a 100 Continue response.
+
+        Co-authored-by: Gregory P. Smith <greg at krypto.org>
+
+Instead of reusing the header reading code, I have just added explicit
+counter to avoid having to refactor the old code.
+
+Plus the improved test from:
+
+    commit e60ab843cbb016fb6ff8b4f418641ac05a9b2fcc
+    Author: Gregory P. Smith <greg at krypto.org>
+    Date:   2021-06-03 05:43:38 +0200
+
+        bpo-44022: Improve the regression test. (GH-26503)
+
+        It wasn't actually detecting the regression due to the
+        assertion being too lenient.
+---
+ Lib/httplib.py           |  5 ++++-
+ Lib/test/test_httplib.py | 13 +++++++++++++
+ 2 files changed, 17 insertions(+), 1 deletion(-)
+
+diff --git a/Lib/httplib.py b/Lib/httplib.py
+index 81a08d5d71..ebfa59ff93 100644
+--- a/Lib/httplib.py
++++ b/Lib/httplib.py
+@@ -453,11 +453,14 @@ class HTTPResponse:
+             if status != CONTINUE:
+                 break
+             # skip the header from the 100 response
++            header_count = 0
+             while True:
+                 skip = self.fp.readline(_MAXLINE + 1)
+                 if len(skip) > _MAXLINE:
+                     raise LineTooLong("header line")
+-                skip = skip.strip()
++                header_count += 1
++                if header_count > _MAXHEADERS:
++                    raise HTTPException("got more than %d headers" % _MAXHEADERS)
+                 if not skip:
+                     break
+                 if self.debuglevel > 0:
+diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
+index e20a0986dc..5f6b1d0b20 100644
+--- a/Lib/test/test_httplib.py
++++ b/Lib/test/test_httplib.py
+@@ -675,6 +675,19 @@ class BasicTest(TestCase):
+         resp = httplib.HTTPResponse(FakeSocket(body))
+         self.assertRaises(httplib.LineTooLong, resp.begin)
+ 
++    def test_overflowing_header_limit_after_100(self):
++        body = (
++            'HTTP/1.1 100 OK\r\n'
++            'r\n' * 32768
++        )
++        resp = httplib.HTTPResponse(FakeSocket(body))
++        with self.assertRaises(httplib.HTTPException) as cm:
++            resp.begin()
++        # We must assert more because other reasonable errors that we
++        # do not want can also be HTTPException derived.
++        self.assertIn('got more than ', str(cm.exception))
++        self.assertIn('headers', str(cm.exception))
++
+     def test_overflowing_chunked_line(self):
+         body = (
+             'HTTP/1.1 200 OK\r\n'
+-- 
+2.38.1
+
diff --git a/CVE-2021-4189.patch b/CVE-2021-4189.patch
new file mode 100644
index 0000000..92933da
--- /dev/null
+++ b/CVE-2021-4189.patch
@@ -0,0 +1,133 @@
+From fe31479be5ea4e611bd485516e48c0ce46be987c Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Tue, 16 Mar 2021 14:08:30 -0700
+Subject: [PATCH 10/36] [3.6] bpo-43285 Make ftplib not trust the PASV
+ response. (GH-24838) (GH-24881) (GH-24882)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The IPv4 address value returned from the server in response to the PASV command
+should not be trusted.  This prevents a malicious FTP server from using the
+response to probe IPv4 address and port combinations on the client network.
+
+Instead of using the returned address, we use the IP address we're
+already connected to.  This is the strategy other ftp clients adopted,
+and matches the only strategy available for the modern IPv6 EPSV command
+where the server response must return a port number and nothing else.
+
+For the rare user who _wants_ this ugly behavior, set a `trust_server_pasv_ipv4_address`
+attribute on your `ftplib.FTP` instance to True..
+(cherry picked from commit 0ab152c6b5d95caa2dc1a30fa96e10258b5f188e)
+
+Co-authored-by: Gregory P. Smith <greg at krypto.org>
+(cherry picked from commit 664d1d16274b47eea6ec92572e1ebf3939a6fa0c)
+
+Rebased for Python 2.7 by Michał Górny <mgorny at gentoo.org>
+---
+ Lib/ftplib.py                                 |  9 +++++-
+ Lib/test/test_ftplib.py                       | 29 +++++++++++++++++--
+ .../2021-03-13-03-48-14.bpo-43285.g-Hah3.rst  |  8 +++++
+ 3 files changed, 43 insertions(+), 3 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst
+
+diff --git a/Lib/ftplib.py b/Lib/ftplib.py
+index 6644554792..0550f0ab9f 100644
+--- a/Lib/ftplib.py
++++ b/Lib/ftplib.py
+@@ -108,6 +108,8 @@ class FTP:
+     file = None
+     welcome = None
+     passiveserver = 1
++    # Disables https://bugs.python.org/issue43285 security if set to True.
++    trust_server_pasv_ipv4_address = False
+ 
+     # Initialization method (called by class instantiation).
+     # Initialize host to localhost, port to standard ftp port
+@@ -310,8 +312,13 @@ class FTP:
+         return sock
+ 
+     def makepasv(self):
++        """Internal: Does the PASV or EPSV handshake -> (address, port)"""
+         if self.af == socket.AF_INET:
+-            host, port = parse227(self.sendcmd('PASV'))
++            untrusted_host, port = parse227(self.sendcmd('PASV'))
++            if self.trust_server_pasv_ipv4_address:
++                host = untrusted_host
++            else:
++                host = self.sock.getpeername()[0]
+         else:
+             host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
+         return host, port
+diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
+index 8a3eb067a4..f3217d686d 100644
+--- a/Lib/test/test_ftplib.py
++++ b/Lib/test/test_ftplib.py
+@@ -67,6 +67,10 @@ class DummyFTPHandler(asynchat.async_chat):
+         self.rest = None
+         self.next_retr_data = RETR_DATA
+         self.push('220 welcome')
++        # We use this as the string IPv4 address to direct the client
++        # to in response to a PASV command.  To test security behavior.
++        # https://bugs.python.org/issue43285/.
++        self.fake_pasv_server_ip = '252.253.254.255'
+ 
+     def collect_incoming_data(self, data):
+         self.in_buffer.append(data)
+@@ -109,8 +113,9 @@ class DummyFTPHandler(asynchat.async_chat):
+         sock.bind((self.socket.getsockname()[0], 0))
+         sock.listen(5)
+         sock.settimeout(10)
+-        ip, port = sock.getsockname()[:2]
+-        ip = ip.replace('.', ',')
++        port = sock.getsockname()[1]
++        ip = self.fake_pasv_server_ip
++        ip = ip.replace('.', ','); p1 = port // 256; p2 = port % 256
+         p1, p2 = divmod(port, 256)
+         self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
+         conn, addr = sock.accept()
+@@ -577,6 +582,26 @@ class TestFTPClass(TestCase):
+         # IPv4 is in use, just make sure send_epsv has not been used
+         self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv')
+ 
++    def test_makepasv_issue43285_security_disabled(self):
++        """Test the opt-in to the old vulnerable behavior."""
++        self.client.trust_server_pasv_ipv4_address = True
++        bad_host, port = self.client.makepasv()
++        self.assertEqual(
++                bad_host, self.server.handler_instance.fake_pasv_server_ip)
++        # Opening and closing a connection keeps the dummy server happy
++        # instead of timing out on accept.
++        socket.create_connection((self.client.sock.getpeername()[0], port),
++                                 timeout=TIMEOUT).close()
++
++    def test_makepasv_issue43285_security_enabled_default(self):
++        self.assertFalse(self.client.trust_server_pasv_ipv4_address)
++        trusted_host, port = self.client.makepasv()
++        self.assertNotEqual(
++                trusted_host, self.server.handler_instance.fake_pasv_server_ip)
++        # Opening and closing a connection keeps the dummy server happy
++        # instead of timing out on accept.
++        socket.create_connection((trusted_host, port), timeout=TIMEOUT).close()
++
+     def test_line_too_long(self):
+         self.assertRaises(ftplib.Error, self.client.sendcmd,
+                           'x' * self.client.maxline * 2)
+diff --git a/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst b/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst
+new file mode 100644
+index 0000000000..8312b7e885
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2021-03-13-03-48-14.bpo-43285.g-Hah3.rst
+@@ -0,0 +1,8 @@
++:mod:`ftplib` no longer trusts the IP address value returned from the server
++in response to the PASV command by default.  This prevents a malicious FTP
++server from using the response to probe IPv4 address and port combinations
++on the client network.
++
++Code that requires the former vulnerable behavior may set a
++``trust_server_pasv_ipv4_address`` attribute on their
++:class:`ftplib.FTP` instances to ``True`` to re-enable it.
+-- 
+2.38.1
+
diff --git a/CVE-2022-0391.patch b/CVE-2022-0391.patch
new file mode 100644
index 0000000..5c45417
--- /dev/null
+++ b/CVE-2022-0391.patch
@@ -0,0 +1,151 @@
+From 76cd81d60310d65d01f9d7b48a8985d8ab89c8b4 Mon Sep 17 00:00:00 2001
+From: Senthil Kumaran <senthil at uthcode.com>
+Date: Thu, 29 Apr 2021 10:16:50 -0700
+Subject: [PATCH] bpo-43882 - urllib.parse should sanitize urls containing
+ ASCII newline and tabs. (GH-25595)
+
+* issue43882 - urllib.parse should sanitize urls containing ASCII newline and tabs.
+
+Co-authored-by: Gregory P. Smith <greg at krypto.org>
+Co-authored-by: Serhiy Storchaka <storchaka at gmail.com>
+---
+ Doc/library/urllib.parse.rst                  | 13 +++++++++
+ Lib/test/test_urlparse.py                     | 29 +++++++++++++++++++
+ Lib/urllib/parse.py                           |  6 ++++
+ .../2021-04-25-07-46-37.bpo-43882.Jpwx85.rst  |  6 ++++
+ 4 files changed, 54 insertions(+)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-04-25-07-46-37.bpo-43882.Jpwx85.rst
+
+From 985ac016373403e8ad41f8d563c4355ffa8d49ff Mon Sep 17 00:00:00 2001
+From: Senthil Kumaran <senthil at uthcode.com>
+Date: Wed, 5 May 2021 15:50:05 -0700
+Subject: [PATCH] bpo-43882 Remove the newline, and tab early. From query and
+ fragments. (GH-25921)
+
+---
+ Lib/test/test_urlparse.py | 24 ++++++++++++++++--------
+ Lib/urllib/parse.py       |  8 +++++---
+ 2 files changed, 21 insertions(+), 11 deletions(-)
+
+Backport:
+ * Drop Misc/NEWS.d
+ * urllib.parse -> urlparse
+ * Update hunk context
+
+diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
+index 67c2120819..0aaac56288 100644
+--- a/Doc/library/urlparse.rst
++++ b/Doc/library/urlparse.rst
+@@ -312,6 +312,9 @@ or on combining URL components into a URL string.
+    decomposed before parsing, or is not a Unicode string, no error will be
+    raised.
+ 
++   Following the `WHATWG spec`_ that updates RFC 3986, ASCII newline
++   ``\n``, ``\r`` and tab ``\t`` characters are stripped from the URL.
++
+    .. versionadded:: 2.2
+
+    .. versionchanged:: 2.5
+@@ -320,6 +323,10 @@ or on combining URL components into a URL string.
+       Characters that affect netloc parsing under NFKC normalization will
+       now raise :exc:`ValueError`.
+ 
++   .. versionchanged:: 2.7 security update
++      ASCII newline and tab characters are stripped from the URL.
++
++.. _WHATWG spec: https://url.spec.whatwg.org/#concept-basic-url-parser
+ 
+ .. function:: urlunsplit(parts)
+ 
+@@ -674,6 +681,10 @@ task isn't already covered by the URL parsing functions above.
+ 
+ .. seealso::
+ 
++   `WHATWG`_ -  URL Living standard
++      Working Group for the URL Standard that defines URLs, domains, IP addresses, the
++      application/x-www-form-urlencoded format, and their API.
++
+    :rfc:`3986` - Uniform Resource Identifiers
+       This is the current standard (STD66). Any changes to urlparse module
+       should conform to this. Certain deviations could be observed, which are
+@@ -697,6 +708,8 @@ task isn't already covered by the URL parsing functions above.
+ 
+    :rfc:`1738` - Uniform Resource Locators (URL)
+       This specifies the formal syntax and semantics of absolute URLs.
++
++.. _WHATWG: https://url.spec.whatwg.org/
+ 
+
+ .. _urlparse-result-object:
+diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
+--- a/Lib/test/test_urlparse.py
++++ b/Lib/test/test_urlparse.py
+@@ -612,6 +612,43 @@
+         self.assertEqual(p1.params, 'phone-context=+1-914-555')
+
+
++    def test_urlsplit_remove_unsafe_bytes(self):
++        # Remove ASCII tabs and newlines from input
++        url = "http\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
++        p = urlparse.urlsplit(url)
++        self.assertEqual(p.scheme, "http")
++        self.assertEqual(p.netloc, "www.python.org")
++        self.assertEqual(p.path, "/javascript:alert('msg')/")
++        self.assertEqual(p.query, "query=something")
++        self.assertEqual(p.fragment, "fragment")
++        self.assertEqual(p.username, None)
++        self.assertEqual(p.password, None)
++        self.assertEqual(p.hostname, "www.python.org")
++        self.assertEqual(p.port, None)
++        self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
++
++        # Remove ASCII tabs and newlines from input as bytes.
++        url = b"http\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
++        p = urlparse.urlsplit(url)
++        self.assertEqual(p.scheme, b"http")
++        self.assertEqual(p.netloc, b"www.python.org")
++        self.assertEqual(p.path, b"/javascript:alert('msg')/")
++        self.assertEqual(p.query, b"query=something")
++        self.assertEqual(p.fragment, b"fragment")
++        self.assertEqual(p.username, None)
++        self.assertEqual(p.password, None)
++        self.assertEqual(p.hostname, b"www.python.org")
++        self.assertEqual(p.port, None)
++        self.assertEqual(p.geturl(), b"http://www.python.org/javascript:alert('msg')/?query=something#fragment")
++
++        # with scheme as cache-key
++        url = "http://www.python.org/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
++        scheme = "ht\ntp"
++        for _ in range(2):
++            p = urlparse.urlsplit(url, scheme=scheme)
++            self.assertEqual(p.scheme, "http")
++            self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
++
+     def test_attributes_bad_port(self):
+         """Check handling of non-integer ports."""
+         p = urlparse.urlsplit("http://www.example.net:foo")
+diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
+--- a/Lib/urlparse.py
++++ b/Lib/urlparse.py
+@@ -78,6 +78,9 @@
+                 '0123456789'
+                 '+-.')
+ 
++# Unsafe bytes to be removed per WHATWG spec
++_UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n']
++
+ MAX_CACHE_SIZE = 20
+ _parse_cache = {}
+
+@@ -456,6 +456,11 @@ def urlsplit(url, scheme='', allow_fragments=True):
+     Return a 5-tuple: (scheme, netloc, path, query, fragment).
+     Note that we don't break the components up in smaller bits
+     (e.g. netloc is a single string) and we don't expand % escapes."""
++
++    for b in _UNSAFE_URL_BYTES_TO_REMOVE:
++        url = url.replace(b, "")
++        scheme = scheme.replace(b, "")
++
+     allow_fragments = bool(allow_fragments)
+     key = url, scheme, allow_fragments, type(url), type(scheme)
+     cached = _parse_cache.get(key, None)
diff --git a/CVE-2022-45061.patch b/CVE-2022-45061.patch
new file mode 100644
index 0000000..dbcdc03
--- /dev/null
+++ b/CVE-2022-45061.patch
@@ -0,0 +1,118 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Mon, 7 Nov 2022 19:22:14 -0800
+Subject: [PATCH] 
+ 00394-cve-2022-45061-cpu-denial-of-service-via-inefficient-idna-decoder.patch
+
+00394 #
+gh-98433: Fix quadratic time idna decoding.
+
+There was an unnecessary quadratic loop in idna decoding. This restores
+the behavior to linear.
+
+Backported from python3.
+
+(cherry picked from commit a6f6c3a3d6f2b580f2d87885c9b8a9350ad7bf15)
+
+Co-authored-by: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
+Co-authored-by: Gregory P. Smith <greg at krypto.org>
+---
+ Lib/encodings/idna.py                         | 32 +++++++++----------
+ Lib/test/test_codecs.py                       |  6 ++++
+ ...2-11-04-09-29-36.gh-issue-98433.l76c5G.rst |  6 ++++
+ 3 files changed, 27 insertions(+), 17 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst
+
+diff --git a/Lib/encodings/idna.py b/Lib/encodings/idna.py
+index ea90d67142f..2ce798cf47e 100644
+--- a/Lib/encodings/idna.py
++++ b/Lib/encodings/idna.py
+@@ -39,23 +39,21 @@ def nameprep(label):
+ 
+     # Check bidi
+     RandAL = map(stringprep.in_table_d1, label)
+-    for c in RandAL:
+-        if c:
+-            # There is a RandAL char in the string. Must perform further
+-            # tests:
+-            # 1) The characters in section 5.8 MUST be prohibited.
+-            # This is table C.8, which was already checked
+-            # 2) If a string contains any RandALCat character, the string
+-            # MUST NOT contain any LCat character.
+-            if filter(stringprep.in_table_d2, label):
+-                raise UnicodeError("Violation of BIDI requirement 2")
+-
+-            # 3) If a string contains any RandALCat character, a
+-            # RandALCat character MUST be the first character of the
+-            # string, and a RandALCat character MUST be the last
+-            # character of the string.
+-            if not RandAL[0] or not RandAL[-1]:
+-                raise UnicodeError("Violation of BIDI requirement 3")
++    if any(RandAL):
++        # There is a RandAL char in the string. Must perform further
++        # tests:
++        # 1) The characters in section 5.8 MUST be prohibited.
++        # This is table C.8, which was already checked
++        # 2) If a string contains any RandALCat character, the string
++        # MUST NOT contain any LCat character.
++        if any(stringprep.in_table_d2(x) for x in label):
++            raise UnicodeError("Violation of BIDI requirement 2")
++        # 3) If a string contains any RandALCat character, a
++        # RandALCat character MUST be the first character of the
++        # string, and a RandALCat character MUST be the last
++        # character of the string.
++        if not RandAL[0] or not RandAL[-1]:
++            raise UnicodeError("Violation of BIDI requirement 3")
+ 
+     return label
+ 
+diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
+index 0ec8bf5a4b4..76428e1794a 100644
+--- a/Lib/test/test_codecs.py
++++ b/Lib/test/test_codecs.py
+@@ -1318,6 +1318,12 @@ class IDNACodecTest(unittest.TestCase):
+         self.assertEqual(u"pyth\xf6n.org".encode("idna"), "xn--pythn-mua.org")
+         self.assertEqual(u"pyth\xf6n.org.".encode("idna"), "xn--pythn-mua.org.")
+ 
++    def test_builtin_decode_length_limit(self):
++        with self.assertRaisesRegexp(UnicodeError, "too long"):
++            (b"xn--016c"+b"a"*1100).decode("idna")
++        with self.assertRaisesRegexp(UnicodeError, "too long"):
++            (b"xn--016c"+b"a"*70).decode("idna")
++
+     def test_stream(self):
+         import StringIO
+         r = codecs.getreader("idna")(StringIO.StringIO("abc"))
+diff --git a/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst b/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst
+new file mode 100644
+index 00000000000..5185fac2e29
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2022-11-04-09-29-36.gh-issue-98433.l76c5G.rst
+@@ -0,0 +1,6 @@
++The IDNA codec decoder used on DNS hostnames by :mod:`socket` or :mod:`asyncio`
++related name resolution functions no longer involves a quadratic algorithm.
++This prevents a potential CPU denial of service if an out-of-spec excessive
++length hostname involving bidirectional characters were decoded. Some protocols
++such as :mod:`urllib` http ``3xx`` redirects potentially allow for an attacker
++to supply such a name.
+diff -urNp a/Lib/encodings/idna.py b/Lib/encodings/idna.py
+--- a/Lib/encodings/idna.py	2023-02-16 08:58:06.884171667 +0100
++++ b/Lib/encodings/idna.py	2023-02-16 08:59:31.931296399 +0100
+@@ -101,6 +101,16 @@ def ToASCII(label):
+     raise UnicodeError("label empty or too long")
+ 
+ def ToUnicode(label):
++    if len(label) > 1024:
++        # Protection from https://github.com/python/cpython/issues/98433.
++        # https://datatracker.ietf.org/doc/html/rfc5894#section-6
++        # doesn't specify a label size limit prior to NAMEPREP. But having
++        # one makes practical sense.
++        # This leaves ample room for nameprep() to remove Nothing characters
++        # per https://www.rfc-editor.org/rfc/rfc3454#section-3.1 while still
++        # preventing us from wasting time decoding a big thing that'll just
++        # hit the actual <= 63 length limit in Step 6.
++        raise UnicodeError("label way too long")
+     # Step 1: Check for ASCII
+     if isinstance(label, str):
+         pure_ascii = True
diff --git a/CVE-2022-48560.patch b/CVE-2022-48560.patch
new file mode 100644
index 0000000..26fdcad
--- /dev/null
+++ b/CVE-2022-48560.patch
@@ -0,0 +1,155 @@
+From 79f89e6e5a659846d1068e8b1bd8e491ccdef861 Mon Sep 17 00:00:00 2001
+From: Pablo Galindo <Pablogsal at gmail.com>
+Date: Thu, 23 Jan 2020 14:07:05 +0000
+Subject: [PATCH] bpo-39421: Fix posible crash in heapq with custom comparison
+ operators (GH-18118)
+
+* bpo-39421: Fix posible crash in heapq with custom comparison operators
+
+* fixup! bpo-39421: Fix posible crash in heapq with custom comparison operators
+
+* fixup! fixup! bpo-39421: Fix posible crash in heapq with custom comparison operators
+---
+ Lib/test/test_heapq.py                        | 31 ++++++++++++++++
+ .../2020-01-22-15-53-37.bpo-39421.O3nG7u.rst  |  2 ++
+ Modules/_heapqmodule.c                        | 35 ++++++++++++++-----
+ 3 files changed, 59 insertions(+), 9 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-01-22-15-53-37.bpo-39421.O3nG7u.rst
+
+Backport:
+ * Drop Misc/NEWS.d
+ * test_heapq.py:
+   + Update hunk context
+   + list.clear() -> del list[:]
+ * _heapqmodule.c: Port the patch with significant changes
+   + PyObject_RichCompareBool -> cmp_lt
+   + X[Y] -> PyList_GET_ITEM(X, Y)
+   + 4th hunk: newitem refcount is already incremented, parent refcount extended
+
+diff --git a/Lib/test/test_heapq.py b/Lib/test/test_heapq.py
+index 861ba7540d..6902573e8f 100644
+--- a/Lib/test/test_heapq.py
++++ b/Lib/test/test_heapq.py
+@@ -432,6 +432,37 @@ def test_heappop_mutating_heap(self):
+         with self.assertRaises((IndexError, RuntimeError)):
+             self.module.heappop(heap)
+ 
++    def test_comparison_operator_modifiying_heap(self):
++        # See bpo-39421: Strong references need to be taken
++        # when comparing objects as they can alter the heap
++        class EvilClass(int):
++            def __lt__(self, o):
++                del heap[:]
++                return NotImplemented
++
++        heap = []
++        self.module.heappush(heap, EvilClass(0))
++        self.assertRaises(IndexError, self.module.heappushpop, heap, 1)
++
++    def test_comparison_operator_modifiying_heap_two_heaps(self):
++
++        class h(int):
++            def __lt__(self, o):
++                del list2[:]
++                return NotImplemented
++
++        class g(int):
++            def __lt__(self, o):
++                del list1[:]
++                return NotImplemented
++
++        list1, list2 = [], []
++
++        self.module.heappush(list1, h(0))
++        self.module.heappush(list2, g(0))
++
++        self.assertRaises((IndexError, RuntimeError), self.module.heappush, list1, g(1))
++        self.assertRaises((IndexError, RuntimeError), self.module.heappush, list2, h(1))
+ 
+ class TestErrorHandlingPython(TestErrorHandling):
+     module = py_heapq
+diff --git a/Modules/_heapqmodule.c b/Modules/_heapqmodule.c
+index a84cade3aa..6bc18b5f82 100644
+--- a/Modules/_heapqmodule.c
++++ b/Modules/_heapqmodule.c
+@@ -36,7 +36,11 @@ siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
+     while (pos > startpos) {
+         parentpos = (pos - 1) >> 1;
+         parent = PyList_GET_ITEM(heap, parentpos);
++        Py_INCREF(newitem);
++        Py_INCREF(parent);
+         cmp = cmp_lt(newitem, parent);
++        Py_DECREF(parent);
++        Py_DECREF(newitem);
+         if (cmp == -1)
+             return -1;
+         if (size != PyList_GET_SIZE(heap)) {
+@@ -78,9 +82,13 @@ siftup(PyListObject *heap, Py_ssize_t pos)
+         childpos = 2*pos + 1;    /* leftmost child position  */
+         rightpos = childpos + 1;
+         if (rightpos < endpos) {
+-            cmp = cmp_lt(
+-                PyList_GET_ITEM(heap, childpos),
+-                PyList_GET_ITEM(heap, rightpos));
++            PyObject* a = PyList_GET_ITEM(heap, childpos);
++            PyObject* b = PyList_GET_ITEM(heap, rightpos);
++            Py_INCREF(a);
++            Py_INCREF(b);
++            cmp = cmp_lt(a, b);
++            Py_DECREF(a);
++            Py_DECREF(b);
+             if (cmp == -1)
+                 return -1;
+             if (cmp == 0)
+@@ -264,7 +271,10 @@ _heapq_heappushpop_impl(PyObject *module, PyObject *heap, PyObject *item)
+         return item;
+     }
+ 
+-    cmp = cmp_lt(PyList_GET_ITEM(heap, 0), item);
++    PyObject* top = PyList_GET_ITEM(heap, 0);
++    Py_INCREF(top);
++    cmp = cmp_lt(top, item);
++    Py_DECREF(top);
+     if (cmp == -1)
+         return NULL;
+     if (cmp == 0) {
+@@ -420,14 +430,17 @@ siftdown_max(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
+     while (pos > startpos){
+         parentpos = (pos - 1) >> 1;
+         parent = PyList_GET_ITEM(heap, parentpos);
++        Py_INCREF(parent);
+         cmp = cmp_lt(parent, newitem);
+         if (cmp == -1) {
++            Py_DECREF(parent);
+             Py_DECREF(newitem);
+             return -1;
+         }
+-        if (cmp == 0)
++        if (cmp == 0) {
++            Py_DECREF(parent);
+             break;
++        }
+-        Py_INCREF(parent);
+         Py_DECREF(PyList_GET_ITEM(heap, pos));
+         PyList_SET_ITEM(heap, pos, parent);
+         pos = parentpos;
+@@ -462,9 +476,13 @@ siftup_max(PyListObject *heap, Py_ssize_t pos)
+         childpos = 2*pos + 1;    /* leftmost child position  */
+         rightpos = childpos + 1;
+         if (rightpos < endpos) {
+-            cmp = cmp_lt(
+-                PyList_GET_ITEM(heap, rightpos),
+-                PyList_GET_ITEM(heap, childpos));
++            PyObject* a = PyList_GET_ITEM(heap, rightpos);
++            PyObject* b = PyList_GET_ITEM(heap, childpos);
++            Py_INCREF(a);
++            Py_INCREF(b);
++            cmp = cmp_lt(a, b);
++            Py_DECREF(a);
++            Py_DECREF(b);
+             if (cmp == -1) {
+                 Py_DECREF(newitem);
+                 return -1;
+-- 
+2.40.1
+
diff --git a/CVE-2022-48565.patch b/CVE-2022-48565.patch
new file mode 100644
index 0000000..c2bc05a
--- /dev/null
+++ b/CVE-2022-48565.patch
@@ -0,0 +1,91 @@
+From dd9ccc8454250bb4c2e2fe517edbbbbe7d759e12 Mon Sep 17 00:00:00 2001
+From: "Miss Skeleton (bot)" <31488909+miss-islington at users.noreply.github.com>
+Date: Mon, 19 Oct 2020 21:38:30 -0700
+Subject: [PATCH 04/36] bpo-42051: Reject XML entity declarations in plist
+ files (GH-22760) (GH-22801) (GH-22804)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: Ronald Oussoren <ronaldoussoren at mac.com>
+(cherry picked from commit e512bc799e3864fe3b1351757261762d63471efc)
+
+Co-authored-by: Ned Deily <nad at python.org>
+
+Rebased for Python 2.7 by Michał Górny <mgorny at gentoo.org>
+---
+ Lib/plistlib.py                                |  7 +++++++
+ Lib/test/test_plistlib.py                      | 18 ++++++++++++++++++
+ .../2020-10-19-10-56-27.bpo-42051.EU_B7u.rst   |  3 +++
+ 3 files changed, 28 insertions(+)
+ create mode 100644 Misc/NEWS.d/next/Security/2020-10-19-10-56-27.bpo-42051.EU_B7u.rst
+
+diff --git a/Lib/plistlib.py b/Lib/plistlib.py
+index 42897b8da8..2c2b7fb635 100644
+--- a/Lib/plistlib.py
++++ b/Lib/plistlib.py
+@@ -403,9 +403,16 @@ class PlistParser:
+         parser.StartElementHandler = self.handleBeginElement
+         parser.EndElementHandler = self.handleEndElement
+         parser.CharacterDataHandler = self.handleData
++        parser.EntityDeclHandler = self.handleEntityDecl
+         parser.ParseFile(fileobj)
+         return self.root
+ 
++    def handleEntityDecl(self, entity_name, is_parameter_entity, value, base, system_id, public_id, notation_name):
++        # Reject plist files with entity declarations to avoid XML vulnerabilies in expat.
++        # Regular plist files don't contain those declerations, and Apple's plutil tool does not
++        # accept them either.
++        raise ValueError("XML entity declarations are not supported in plist files")
++
+     def handleBeginElement(self, element, attrs):
+         self.data = []
+         handler = getattr(self, "begin_" + element, None)
+diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
+index 7859ad0572..612a1d2d6e 100644
+--- a/Lib/test/test_plistlib.py
++++ b/Lib/test/test_plistlib.py
+@@ -86,6 +86,19 @@ TESTDATA = """<?xml version="1.0" encoding="UTF-8"?>
+ </plist>
+ """.replace(" " * 8, "\t")  # Apple as well as plistlib.py output hard tabs
+ 
++XML_PLIST_WITH_ENTITY=b'''\
++<?xml version="1.0" encoding="UTF-8"?>
++<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" [
++   <!ENTITY entity "replacement text">
++  ]>
++<plist version="1.0">
++  <dict>
++    <key>A</key>
++    <string>&entity;</string>
++  </dict>
++</plist>
++'''
++
+ 
+ class TestPlistlib(unittest.TestCase):
+ 
+@@ -195,6 +208,11 @@ class TestPlistlib(unittest.TestCase):
+         self.assertEqual(test1, result1)
+         self.assertEqual(test2, result2)
+ 
++    def test_xml_plist_with_entity_decl(self):
++        with self.assertRaisesRegexp(ValueError,
++                                     "XML entity declarations are not supported"):
++            plistlib.readPlistFromString(XML_PLIST_WITH_ENTITY)
++
+ 
+ def test_main():
+     test_support.run_unittest(TestPlistlib)
+diff --git a/Misc/NEWS.d/next/Security/2020-10-19-10-56-27.bpo-42051.EU_B7u.rst b/Misc/NEWS.d/next/Security/2020-10-19-10-56-27.bpo-42051.EU_B7u.rst
+new file mode 100644
+index 0000000000..e865ed12a0
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2020-10-19-10-56-27.bpo-42051.EU_B7u.rst
+@@ -0,0 +1,3 @@
++The :mod:`plistlib` module no longer accepts entity declarations in XML
++plist files to avoid XML vulnerabilities. This should not affect users as
++entity declarations are not used in regular plist files.
+-- 
+2.38.1
+
diff --git a/CVE-2022-48566.patch b/CVE-2022-48566.patch
new file mode 100644
index 0000000..657af20
--- /dev/null
+++ b/CVE-2022-48566.patch
@@ -0,0 +1,48 @@
+From bfc498a6c971c7393d37c25bdcf5f892afb16ed2 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Sun, 22 Nov 2020 09:33:09 -0800
+Subject: [PATCH 06/36] bpo-40791: Make compare_digest more constant-time.
+ (GH-23438)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The existing volatile `left`/`right` pointers guarantee that the reads will all occur, but does not guarantee that they will be _used_. So a compiler can still short-circuit the loop, saving e.g. the overhead of doing the xors and especially the overhead of the data dependency between `result` and the reads. That would change performance depending on where the first unequal byte occurs. This change removes that optimization.
+
+(This is change GH-1 from https://bugs.python.org/issue40791 .)
+(cherry picked from commit 31729366e2bc09632e78f3896dbce0ae64914f28)
+
+Co-authored-by: Devin Jeanpierre <jeanpierreda at google.com>
+
+Rebased for Python 2.7 by Michał Górny <mgorny at gentoo.org>
+---
+ .../next/Security/2020-05-28-06-06-47.bpo-40791.QGZClX.rst      | 1 +
+ Modules/operator.c                                              | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2020-05-28-06-06-47.bpo-40791.QGZClX.rst
+
+diff --git a/Misc/NEWS.d/next/Security/2020-05-28-06-06-47.bpo-40791.QGZClX.rst b/Misc/NEWS.d/next/Security/2020-05-28-06-06-47.bpo-40791.QGZClX.rst
+new file mode 100644
+index 0000000000..69b9de1bea
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2020-05-28-06-06-47.bpo-40791.QGZClX.rst
+@@ -0,0 +1 @@
++Add ``volatile`` to the accumulator variable in ``hmac.compare_digest``, making constant-time-defeating optimizations less likely.
+\ No newline at end of file
+diff --git a/Modules/operator.c b/Modules/operator.c
+index 7ddd123f40..67011a6a82 100644
+--- a/Modules/operator.c
++++ b/Modules/operator.c
+@@ -259,7 +259,7 @@ _tscmp(const unsigned char *a, const unsigned char *b,
+     volatile const unsigned char *left;
+     volatile const unsigned char *right;
+     Py_ssize_t i;
+-    unsigned char result;
++    volatile unsigned char result;
+ 
+     /* loop count depends on length of b */
+     length = len_b;
+-- 
+2.38.1
+
diff --git a/CVE-2023-24329.patch b/CVE-2023-24329.patch
new file mode 100644
index 0000000..2bd0ba4
--- /dev/null
+++ b/CVE-2023-24329.patch
@@ -0,0 +1,264 @@
+From 439b9cfaf43080e91c4ad69f312f21fa098befc7 Mon Sep 17 00:00:00 2001
+From: Ben Kallus <49924171+kenballus at users.noreply.github.com>
+Date: Sun, 13 Nov 2022 18:25:55 +0000
+Subject: [PATCH] gh-99418: Make urllib.parse.urlparse enforce that a scheme
+ must begin with an alphabetical ASCII character. (#99421)
+
+Prevent urllib.parse.urlparse from accepting schemes that don't begin with an alphabetical ASCII character.
+
+RFC 3986 defines a scheme like this: `scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`
+RFC 2234 defines an ALPHA like this: `ALPHA = %x41-5A / %x61-7A`
+
+The WHATWG URL spec defines a scheme like this:
+`"A URL-scheme string must be one ASCII alpha, followed by zero or more of ASCII alphanumeric, U+002B (+), U+002D (-), and U+002E (.)."`
+---
+ Lib/test/test_urlparse.py                      | 18 ++++++++++++++++++
+ Lib/urllib/parse.py                            |  2 +-
+ ...22-11-12-15-45-51.gh-issue-99418.FxfAXS.rst |  2 ++
+ 3 files changed, 21 insertions(+), 1 deletion(-)
+ create mode 100644 Misc/NEWS.d/next/Library/2022-11-12-15-45-51.gh-issue-99418.FxfAXS.rst
+
+From 2f630e1ce18ad2e07428296532a68b11dc66ad10 Mon Sep 17 00:00:00 2001
+From: Illia Volochii <illia.volochii at gmail.com>
+Date: Wed, 17 May 2023 11:49:20 +0300
+Subject: [PATCH] gh-102153: Start stripping C0 control and space chars in
+ `urlsplit` (#102508)
+
+`urllib.parse.urlsplit` has already been respecting the WHATWG spec a bit #25595.
+
+This adds more sanitizing to respect the "Remove any leading C0 control or space from input" [rule](https://url.spec.whatwg.org/#url-parsing:~:text=Remove%20any%20leading%20and%20trailing%20C0%20control%20or%20space%20from%20input.) in response to [CVE-2023-24329](https://nvd.nist.gov/vuln/detail/CVE-2023-24329).
+
+---------
+
+Co-authored-by: Gregory P. Smith [Google] <greg at krypto.org>
+---
+ Doc/library/urllib.parse.rst                  | 46 +++++++++++++-
+ Lib/test/test_urlparse.py                     | 61 ++++++++++++++++++-
+ Lib/urllib/parse.py                           | 12 ++++
+ ...-03-07-20-59-17.gh-issue-102153.14CLSZ.rst |  3 +
+ 4 files changed, 119 insertions(+), 3 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2023-03-07-20-59-17.gh-issue-102153.14CLSZ.rst
+
+Backport:
+ * Drop Misc/NEWS.d
+ * urllib.parse -> urlparse
+ * Update hunk context
+ * Implement str.isascii
+ * test_urlparse.py:
+   * Various str vs bytes issues
+   * Drop hunk in test_attributes_bad_port
+   * Avoid using TestCase.subTest
+   * Don't use non-ascii in source
+
+diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
+--- a/Lib/test/test_urlparse.py
++++ b/Lib/test/test_urlparse.py
+@@ -654,6 +654,65 @@ def test_urlsplit_remove_unsafe_bytes(self):
+             self.assertEqual(p.scheme, "http")
+             self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
+
++    def test_urlsplit_strip_url(self):
++        noise = "".join(map(chr, range(0, 0x20 + 1)))
++        base_url = "http://User:Pass@www.python.org:080/doc/?query=yes#frag"
++
++        url = (noise + base_url).decode("utf8")
++        p = urlparse.urlsplit(url)
++        self.assertEqual(p.scheme, u"http")
++        self.assertEqual(p.netloc, u"User:Pass at www.python.org:080")
++        self.assertEqual(p.path, u"/doc/")
++        self.assertEqual(p.query, u"query=yes")
++        self.assertEqual(p.fragment, u"frag")
++        self.assertEqual(p.username, u"User")
++        self.assertEqual(p.password, u"Pass")
++        self.assertEqual(p.hostname, u"www.python.org")
++        self.assertEqual(p.port, 80)
++        self.assertEqual(p.geturl(), base_url.decode("utf8"))
++
++        url = noise + base_url
++        p = urlparse.urlsplit(url)
++        self.assertEqual(p.scheme, b"http")
++        self.assertEqual(p.netloc, b"User:Pass at www.python.org:080")
++        self.assertEqual(p.path, b"/doc/")
++        self.assertEqual(p.query, b"query=yes")
++        self.assertEqual(p.fragment, b"frag")
++        self.assertEqual(p.username, b"User")
++        self.assertEqual(p.password, b"Pass")
++        self.assertEqual(p.hostname, b"www.python.org")
++        self.assertEqual(p.port, 80)
++        self.assertEqual(p.geturl(), base_url)
++
++        # Test that trailing space is preserved as some applications rely on
++        # this within query strings.
++        query_spaces_url = "https://www.python.org:88/doc/?query=    "
++        p = urlparse.urlsplit(noise + query_spaces_url)
++        self.assertEqual(p.scheme, "https")
++        self.assertEqual(p.netloc, "www.python.org:88")
++        self.assertEqual(p.path, "/doc/")
++        self.assertEqual(p.query, "query=    ")
++        self.assertEqual(p.port, 88)
++        self.assertEqual(p.geturl(), query_spaces_url)
++
++        p = urlparse.urlsplit("www.pypi.org ")
++        # That "hostname" gets considered a "path" due to the
++        # trailing space and our existing logic...  YUCK...
++        # and re-assembles via geturl aka unurlsplit into the original.
++        # django.core.validators.URLValidator (at least through v3.2) relies on
++        # this, for better or worse, to catch it in a ValidationError via its
++        # regular expressions.
++        # Here we test the basic round trip concept of such a trailing space.
++        self.assertEqual(urlparse.urlunsplit(p), "www.pypi.org ")
++
++        # with scheme as cache-key
++        url = "//www.python.org/"
++        scheme = noise + "https" + noise
++        for _ in range(2):
++            p = urlparse.urlsplit(url, scheme=scheme)
++            self.assertEqual(p.scheme, "https")
++            self.assertEqual(p.geturl(), "https://www.python.org/")
++
+     def test_attributes_bad_port(self):
+         """Check handling of non-integer ports."""
+         p = urlparse.urlsplit("http://www.example.net:foo")
+@@ -668,6 +668,23 @@ def test_attributes_bad_port(self):
+         self.assertEqual(p.netloc, "www.example.net:foo")
+         self.assertRaises(ValueError, lambda: p.port)
+
++    def test_attributes_bad_scheme(self):
++        """Check handling of invalid schemes."""
++        for bytes in (False, True):
++            for parse in (urlparse.urlsplit, urlparse.urlparse):
++                for scheme in (u".", u"+", u"-", u"0", u"http&", u"\xe0http"):
++                    url = scheme + u"://www.example.net"
++                    if bytes:
++                        if all(ord(c) < 128 for c in url):
++                            url = url.encode("ascii")
++                        else:
++                            continue
++                    p = parse(url)
++                    if bytes:
++                        self.assertEqual(p.scheme, b"")
++                    else:
++                        self.assertEqual(p.scheme, u"")
++
+     def test_attributes_without_netloc(self):
+         # This example is straight from RFC 3261.  It looks like it
+         # should allow the username, hostname, and port to be filled
+diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
+index 9a3102afd6..4f6867accb 100644
+--- a/Lib/urlparse.py
++++ b/Lib/urlparse.py
+@@ -25,6 +25,10 @@
+ scenarios for parsing, and for backward compatibility purposes, some
+ parsing quirks from older RFCs are retained. The testcases in
+ test_urlparse.py provides a good indicator of parsing behavior.
++
++The WHATWG URL Parser spec should also be considered.  We are not compliant with
++it either due to existing user code API behavior expectations (Hyrum's Law).
++It serves as a useful guide when making changes.
+
+ """
+ 
+@@ -80,6 +84,10 @@
+                 '0123456789'
+                 '+-.')
+ 
++# Leading and trailing C0 control and space to be stripped per WHATWG spec.
++# == "".join([chr(i) for i in range(0, 0x20 + 1)])
++_WHATWG_C0_CONTROL_OR_SPACE = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f '
++
+ # Unsafe bytes to be removed per WHATWG spec
+ _UNSAFE_URL_BYTES_TO_REMOVE = ['\t', '\r', '\n']
+ 
+@@ -464,9 +472,13 @@ def urlsplit(url, scheme='', allow_fragments=True):
+         return cached
+     if len(_parse_cache) >= MAX_CACHE_SIZE: # avoid runaway growth
+         clear_cache()
++    # Only lstrip url as some applications rely on preserving trailing space.
++    # (https://url.spec.whatwg.org/#concept-basic-url-parser would strip both)
++    url = url.lstrip(_WHATWG_C0_CONTROL_OR_SPACE)
++    scheme = scheme.strip(_WHATWG_C0_CONTROL_OR_SPACE)
+     netloc = query = fragment = ''
+     i = url.find(':')
+-    if i > 0:
++    if i > 0 and ord(url[0]) < 128 and url[0].isalpha():
+         if url[:i] == 'http': # optimize the common case
+             scheme = url[:i].lower()
+             url = url[i+1:]
+diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
+index 96b3965107..5a9a53f83d 100644
+--- a/Doc/library/urlparse.rst
++++ b/Doc/library/urlparse.rst
+@@ -159,6 +159,11 @@ or on combining URL components into a URL string.
+    decomposed before parsing, or is not a Unicode string, no error will be
+    raised.
+ 
++   .. warning::
++
++      :func:`urlparse` does not perform validation.  See :ref:`URL parsing
++      security <url-parsing-security>` for details.
++
+    .. versionchanged:: 2.5
+       Added attributes to return value.
+
+@@ -324,6 +328,15 @@ or on combining URL components into a URL string.
+    Following the `WHATWG spec`_ that updates RFC 3986, ASCII newline
+    ``\n``, ``\r`` and tab ``\t`` characters are stripped from the URL.
+
++   Following some of the `WHATWG spec`_ that updates RFC 3986, leading C0
++   control and space characters are stripped from the URL. ``\n``,
++   ``\r`` and tab ``\t`` characters are removed from the URL at any position.
++
++   .. warning::
++
++      :func:`urlsplit` does not perform validation.  See :ref:`URL parsing
++      security <url-parsing-security>` for details.
++
+    .. versionadded:: 2.2
+
+    .. versionchanged:: 2.5
+@@ -338,6 +348,9 @@ or on combining URL components into a URL string.
+    .. versionchanged:: 2.7 security update
+       ASCII newline and tab characters are stripped from the URL.
+
++   .. versionchanged:: 2.7 security update
++      Leading WHATWG C0 control and space characters are stripped from the URL.
++
+ .. _WHATWG spec: https://url.spec.whatwg.org/#concept-basic-url-parser
+ 
+ .. function:: urlunsplit(parts)
+@@ -414,6 +427,35 @@ or on combining URL components into a URL string.
+ .. _WHATWG: https://url.spec.whatwg.org/
+
+
++.. _url-parsing-security:
++
++URL parsing security
++--------------------
++
++The :func:`urlsplit` and :func:`urlparse` APIs do not perform **validation** of
++inputs.  They may not raise errors on inputs that other applications consider
++invalid.  They may also succeed on some inputs that might not be considered
++URLs elsewhere.  Their purpose is for practical functionality rather than
++purity.
++
++Instead of raising an exception on unusual input, they may instead return some
++component parts as empty strings. Or components may contain more than perhaps
++they should.
++
++We recommend that users of these APIs where the values may be used anywhere
++with security implications code defensively. Do some verification within your
++code before trusting a returned component part.  Does that ``scheme`` make
++sense?  Is that a sensible ``path``?  Is there anything strange about that
++``hostname``?  etc.
++
++What constitutes a URL is not universally well defined.  Different applications
++have different needs and desired constraints.  For instance the living `WHATWG
++spec`_ describes what user facing web clients such as a web browser require.
++While :rfc:`3986` is more general.  These functions incorporate some aspects of
++both, but cannot be claimed compliant with either.  The APIs and existing user
++code with expectations on specific behaviors predate both standards leading us
++to be very cautious about making API behavior changes.
++
+ .. _urlparse-result-object:
+
+ Results of :func:`urlparse` and :func:`urlsplit`
diff --git a/CVE-2023-40217.patch b/CVE-2023-40217.patch
new file mode 100644
index 0000000..dd780be
--- /dev/null
+++ b/CVE-2023-40217.patch
@@ -0,0 +1,321 @@
+From 0cb0c238d520a8718e313b52cffc356a5a7561bf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz at langa.pl>
+Date: Tue, 22 Aug 2023 19:53:15 +0200
+Subject: [PATCH] gh-108310: Fix CVE-2023-40217: Check for & avoid the ssl
+ pre-close flaw (#108315)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Instances of `ssl.SSLSocket` were vulnerable to a bypass of the TLS handshake
+and included protections (like certificate verification) and treating sent
+unencrypted data as if it were post-handshake TLS encrypted data.
+
+The vulnerability is caused when a socket is connected, data is sent by the
+malicious peer and stored in a buffer, and then the malicious peer closes the
+socket within a small timing window before the other peers’ TLS handshake can
+begin. After this sequence of events the closed socket will not immediately
+attempt a TLS handshake due to not being connected but will also allow the
+buffered data to be read as if a successful TLS handshake had occurred.
+
+Co-authored-by: Gregory P. Smith [Google LLC] <greg at krypto.org>
+---
+ Lib/ssl.py                                    |  31 ++-
+ Lib/test/test_ssl.py                          | 211 ++++++++++++++++++
+ ...-08-22-17-39-12.gh-issue-108310.fVM3sg.rst |   7 +
+ 3 files changed, 248 insertions(+), 1 deletion(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2023-08-22-17-39-12.gh-issue-108310.fVM3sg.rst
+
+From 64f99350351bc46e016b2286f36ba7cd669b79e3 Mon Sep 17 00:00:00 2001
+From: Victor Stinner <vstinner at python.org>
+Date: Wed, 23 Aug 2023 07:26:01 +0200
+Subject: [PATCH] gh-108342: Break ref cycle in SSLSocket._create() exc
+ (#108344)
+
+Explicitly break a reference cycle when SSLSocket._create() raises an
+exception. Clear the variable storing the exception, since the
+exception traceback contains the variables and so creates a reference
+cycle.
+
+This test leak was introduced by the test added for the fix of #108310.
+---
+ Lib/ssl.py | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+Backport:
+ * ssl.py:
+   * There is no socket.getblocking. In the connected case, we do not retain the
+     timeout, because #8524 was never fixed on Python 2.7. In the disconnected
+     case, we have to modify the timeout and therefore restore it.
+   * Cannot use self.recv in c'tor, because it would access self._sslobj
+   * socket.socket raises socket.error rather than OSError
+ * test_ssl.py:
+   * Merge imports
+   * Inline socket_helper.bind_port
+   * Delete non_linux_skip_if_other_okay_error: This backport is Linux-only
+   * socket.socket isn't a ContextManager yet
+   * http.client -> httplib
+   * Implement assertRaises
+   * Remove use of keyword-only arguments
+   * Do not use f"strings"
+ * Drop Misc/NEWS.d
+
+diff --git a/Lib/ssl.py b/Lib/ssl.py
+index 1d58737264..ff363c75e7 100644
+--- a/Lib/ssl.py
++++ b/Lib/ssl.py
+@@ -994,6 +994,38 @@ def _create(cls, sock, server_side=False, do_handshake_on_connect=True,
+             if e.errno != errno.ENOTCONN:
+                 raise
+             connected = False
++            sock_timeout = self.gettimeout()
++            self.settimeout(0)
++            try:
++                # We are not connected so this is not supposed to block, but
++                # testing revealed otherwise on macOS and Windows so we do
++                # the non-blocking dance regardless. Our raise when any data
++                # is found means consuming the data is harmless.
++                notconn_pre_handshake_data = self._sock.recv(1)
++            except socket_error as e:
++                # EINVAL occurs for recv(1) on non-connected on unix sockets.
++                if e.errno not in (errno.ENOTCONN, errno.EINVAL):
++                    raise
++                notconn_pre_handshake_data = b''
++            self.settimeout(sock_timeout)
++            if notconn_pre_handshake_data:
++                # This prevents pending data sent to the socket before it was
++                # closed from escaping to the caller who could otherwise
++                # presume it came through a successful TLS connection.
++                reason = "Closed before TLS handshake with data in recv buffer."
++                notconn_pre_handshake_data_error = SSLError(e.errno, reason)
++                # Add the SSLError attributes that _ssl.c always adds.
++                notconn_pre_handshake_data_error.reason = reason
++                notconn_pre_handshake_data_error.library = None
++                try:
++                    self.close()
++                except socket_error:
++                    pass
++                try:
++                    raise notconn_pre_handshake_data_error
++                finally:
++                    # Explicitly break the reference cycle.
++                    notconn_pre_handshake_data_error = None
+         else:
+             connected = True
+ 
+diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
+index 6117ca3fdb..ad5377ec05 100644
+--- a/Lib/test/test_ssl.py
++++ b/Lib/test/test_ssl.py
+@@ -10,9 +10,11 @@
+ import asyncore
+ import socket
+ import select
++import struct
+ import time
+ import datetime
+ import gc
++import httplib
+ import os
+ import errno
+ import pprint
+@@ -4659,5 +4662,196 @@ def sni_cb(sock, servername, ctx):
+         if _have_threads:
+             support.threading_cleanup(*thread_info)
+ 
++def set_socket_so_linger_on_with_zero_timeout(sock):
++    sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
++
++
++class TestPreHandshakeClose(unittest.TestCase):
++    """Verify behavior of close sockets with received data before to the handshake.
++    """
++
++    class SingleConnectionTestServerThread(threading.Thread):
++
++        def __init__(self, name, call_after_accept):
++            self.call_after_accept = call_after_accept
++            self.received_data = b''  # set by .run()
++            self.wrap_error = None  # set by .run()
++            self.listener = None  # set by .start()
++            self.port = None  # set by .start()
++            super().__init__(name=name)
++
++        def __enter__(self):
++            self.start()
++            return self
++
++        def __exit__(self, *args):
++            try:
++                if self.listener:
++                    self.listener.close()
++            except OSError:
++                pass
++            self.join()
++            self.wrap_error = None  # avoid dangling references
++
++        def start(self):
++            self.ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
++            self.ssl_ctx.verify_mode = ssl.CERT_REQUIRED
++            self.ssl_ctx.load_verify_locations(cafile=ONLYCERT)
++            self.ssl_ctx.load_cert_chain(certfile=ONLYCERT, keyfile=ONLYKEY)
++            self.listener = socket.socket()
++            self.listener.bind((None, 0))
++            self.port = self.listener.getsockname()[1]
++            self.listener.settimeout(2.0)
++            self.listener.listen(1)
++            super().start()
++
++        def run(self):
++            conn, address = self.listener.accept()
++            self.listener.close()
++            with conn:
++                if self.call_after_accept(conn):
++                    return
++                try:
++                    tls_socket = self.ssl_ctx.wrap_socket(conn, server_side=True)
++                except OSError as err:  # ssl.SSLError inherits from OSError
++                    self.wrap_error = err
++                else:
++                    try:
++                        self.received_data = tls_socket.recv(400)
++                    except OSError:
++                        pass  # closed, protocol error, etc.
++
++    def test_preauth_data_to_tls_server(self):
++        server_accept_called = threading.Event()
++        ready_for_server_wrap_socket = threading.Event()
++
++        def call_after_accept(unused):
++            server_accept_called.set()
++            if not ready_for_server_wrap_socket.wait(2.0):
++                raise RuntimeError("wrap_socket event never set, test may fail.")
++            return False  # Tell the server thread to continue.
++
++        server = self.SingleConnectionTestServerThread(
++                call_after_accept=call_after_accept,
++                name="preauth_data_to_tls_server")
++        self.enterContext(server)  # starts it & unittest.TestCase stops it.
++
++        with closing(socket.socket()) as client:
++            client.connect(server.listener.getsockname())
++            # This forces an immediate connection close via RST on .close().
++            set_socket_so_linger_on_with_zero_timeout(client)
++            client.setblocking(False)
++
++            server_accept_called.wait()
++            client.send(b"DELETE /data HTTP/1.0\r\n\r\n")
++            client.close()  # RST
++
++        ready_for_server_wrap_socket.set()
++        server.join()
++        wrap_error = server.wrap_error
++        self.assertEqual(b"", server.received_data)
++        self.assertIsInstance(wrap_error, OSError)  # All platforms.
++        self.assertIsInstance(wrap_error, ssl.SSLError)
++        self.assertIn("before TLS handshake with data", wrap_error.args[1])
++        self.assertIn("before TLS handshake with data", wrap_error.reason)
++        self.assertNotEqual(0, wrap_error.args[0])
++        self.assertIsNone(wrap_error.library, msg="attr must exist")
++
++    def test_preauth_data_to_tls_client(self):
++        client_can_continue_with_wrap_socket = threading.Event()
++
++        def call_after_accept(conn_to_client):
++            # This forces an immediate connection close via RST on .close().
++            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
++            conn_to_client.send(
++                    b"HTTP/1.0 307 Temporary Redirect\r\n"
++                    b"Location: https://example.com/someone-elses-server\r\n"
++                    b"\r\n")
++            conn_to_client.close()  # RST
++            client_can_continue_with_wrap_socket.set()
++            return True  # Tell the server to stop.
++
++        server = self.SingleConnectionTestServerThread(
++                call_after_accept=call_after_accept,
++                name="preauth_data_to_tls_client")
++        self.enterContext(server)  # starts it & unittest.TestCase stops it.
++        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
++        set_socket_so_linger_on_with_zero_timeout(server.listener)
++
++        with closing(socket.socket()) as client:
++            client.connect(server.listener.getsockname())
++            if not client_can_continue_with_wrap_socket.wait(2.0):
++                self.fail("test server took too long.")
++            ssl_ctx = ssl.create_default_context()
++            try:
++                tls_client = ssl_ctx.wrap_socket(
++                        client, server_hostname="localhost")
++            except OSError as err:  # SSLError inherits from OSError
++                wrap_error = err
++                received_data = b""
++            else:
++                wrap_error = None
++                received_data = tls_client.recv(400)
++                tls_client.close()
++
++        server.join()
++        self.assertEqual(b"", received_data)
++        self.assertIsInstance(wrap_error, OSError)  # All platforms.
++        self.assertIsInstance(wrap_error, ssl.SSLError)
++        self.assertIn("before TLS handshake with data", wrap_error.args[1])
++        self.assertIn("before TLS handshake with data", wrap_error.reason)
++        self.assertNotEqual(0, wrap_error.args[0])
++        self.assertIsNone(wrap_error.library, msg="attr must exist")
++
++    def test_https_client_non_tls_response_ignored(self):
++
++        server_responding = threading.Event()
++
++        class SynchronizedHTTPSConnection(httplib.HTTPSConnection):
++            def connect(self):
++                httplib.HTTPConnection.connect(self)
++                # Wait for our fault injection server to have done its thing.
++                if not server_responding.wait(1.0) and support.verbose:
++                    sys.stdout.write("server_responding event never set.")
++                self.sock = self._context.wrap_socket(
++                        self.sock, server_hostname=self.host)
++
++        def call_after_accept(conn_to_client):
++            # This forces an immediate connection close via RST on .close().
++            set_socket_so_linger_on_with_zero_timeout(conn_to_client)
++            conn_to_client.send(
++                    b"HTTP/1.0 402 Payment Required\r\n"
++                    b"\r\n")
++            conn_to_client.close()  # RST
++            server_responding.set()
++            return True  # Tell the server to stop.
++
++        server = self.SingleConnectionTestServerThread(
++                call_after_accept=call_after_accept,
++                name="non_tls_http_RST_responder")
++        self.enterContext(server)  # starts it & unittest.TestCase stops it.
++        # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
++        set_socket_so_linger_on_with_zero_timeout(server.listener)
++
++        connection = SynchronizedHTTPSConnection(
++                "localhost",
++                port=server.port,
++                context=ssl.create_default_context(),
++                timeout=2.0,
++        )
++        # There are lots of reasons this raises as desired, long before this
++        # test was added. Sending the request requires a successful TLS wrapped
++        # socket; that fails if the connection is broken. It may seem pointless
++        # to test this. It serves as an illustration of something that we never
++        # want to happen... properly not happening.
++        try:
++            connection.request("HEAD", "/test", headers={"Host": "localhost"})
++            response = connection.getresponse()
++        except OSError:
++            pass
++        else:
++            self.fail("http request did not raise OSError")
++
++
+ if __name__ == "__main__":
+     test_main()
+-- 
+2.40.1
+
diff --git a/python-bpo-42278-pydoc-mktemp.patch b/python-bpo-42278-pydoc-mktemp.patch
new file mode 100644
index 0000000..938020b
--- /dev/null
+++ b/python-bpo-42278-pydoc-mktemp.patch
@@ -0,0 +1,59 @@
+From e5507ee1da8bb7a484311cf58238dd3daaae0aed Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Sun, 29 Aug 2021 05:57:05 -0700
+Subject: [PATCH 15/36] bpo-42278: Use tempfile.TemporaryDirectory rather than
+ tempfile.mktemp in pydoc (GH-23200) (GH-28026)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: Łukasz Langa <lukasz at langa.pl>
+(cherry picked from commit c9227df5a9d8e958a2324cf0deba8524d1ded26a)
+
+Co-authored-by: E-Paine <63801254+E-Paine at users.noreply.github.com>
+
+(updated for Python 2.7 by Michał Górny)
+---
+ Lib/pydoc.py                                          | 11 ++++++-----
+ .../Security/2021-08-29-12-39-44.bpo-42278.jvmQz_.rst |  2 ++
+ 2 files changed, 8 insertions(+), 5 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-08-29-12-39-44.bpo-42278.jvmQz_.rst
+
+diff --git a/Lib/pydoc.py b/Lib/pydoc.py
+index 62cc262ccb..c2f2d31026 100755
+--- a/Lib/pydoc.py
++++ b/Lib/pydoc.py
+@@ -1423,15 +1423,16 @@ def pipepager(text, cmd):
+ 
+ def tempfilepager(text, cmd):
+     """Page through text by invoking a program on a temporary file."""
++    import shutil
+     import tempfile
+-    filename = tempfile.mktemp()
+-    file = open(filename, 'w')
+-    file.write(_encode(text))
+-    file.close()
++    tempdir = tempfile.mkdtemp()
+     try:
++        filename = os.path.join(tempdir, 'pydoc.out')
++        with open(filename, 'w') as file:
++            file.write(_encode(text))
+         os.system(cmd + ' "' + filename + '"')
+     finally:
+-        os.unlink(filename)
++        shutil.rmtree(tempdir)
+ 
+ def ttypager(text):
+     """Page through text on a text terminal."""
+diff --git a/Misc/NEWS.d/next/Security/2021-08-29-12-39-44.bpo-42278.jvmQz_.rst b/Misc/NEWS.d/next/Security/2021-08-29-12-39-44.bpo-42278.jvmQz_.rst
+new file mode 100644
+index 0000000000..621c317670
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2021-08-29-12-39-44.bpo-42278.jvmQz_.rst
+@@ -0,0 +1,2 @@
++Replaced usage of :func:`tempfile.mktemp` with :func:`tempfile.mkdtemp`
++to avoid a potential race condition.
+-- 
+2.38.1
+
diff --git a/python-bpo-43124-smtplib-crlf.patch b/python-bpo-43124-smtplib-crlf.patch
new file mode 100644
index 0000000..d924b2d
--- /dev/null
+++ b/python-bpo-43124-smtplib-crlf.patch
@@ -0,0 +1,180 @@
+From 11afbbe796db6d50e5f31d12d32c0b9e1c79439b Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington at users.noreply.github.com>
+Date: Mon, 30 Aug 2021 12:21:57 -0700
+Subject: [PATCH 14/36] bpo-43124: Fix smtplib multiple CRLF injection
+ (GH-25987) (GH-28038)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Co-authored-by: Miguel Brito <5544985+miguendes at users.noreply.github.com>
+
+Co-authored-by: Łukasz Langa <lukasz at langa.pl>
+(cherry picked from commit 0897253f426068ea6a6fbe0ada01689af9ef1019)
+
+(updated for 2.7 by Michał Górny)
+---
+ Lib/smtplib.py                                | 12 +++-
+ Lib/test/test_smtplib.py                      | 65 +++++++++++++++++++
+ .../2021-05-08-11-50-46.bpo-43124.2CTM6M.rst  |  2 +
+ 3 files changed, 76 insertions(+), 3 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
+
+diff --git a/Lib/smtplib.py b/Lib/smtplib.py
+index 0c61065432..b6c7285d8b 100755
+--- a/Lib/smtplib.py
++++ b/Lib/smtplib.py
+@@ -336,10 +336,16 @@ class SMTP:
+     def putcmd(self, cmd, args=""):
+         """Send a command to the server."""
+         if args == "":
+-            str = '%s%s' % (cmd, CRLF)
++            s = cmd
+         else:
+-            str = '%s %s%s' % (cmd, args, CRLF)
+-        self.send(str)
++            s = '%s %s' % (cmd, args)
++        if '\r' in s or '\n' in s:
++            s = s.replace('\n', '\\n').replace('\r', '\\r')
++            raise ValueError(
++                'command and arguments contain prohibited newline characters: %s'
++                % (s,)
++            )
++        self.send(s + CRLF)
+ 
+     def getreply(self):
+         """Get a reply from the server.
+diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
+index 703b631c17..2224524ebd 100644
+--- a/Lib/test/test_smtplib.py
++++ b/Lib/test/test_smtplib.py
+@@ -155,6 +155,9 @@ class DebuggingServerTests(unittest.TestCase):
+         self._threads = test_support.threading_setup()
+         self.serv_evt = threading.Event()
+         self.client_evt = threading.Event()
++        # Capture SMTPChannel debug output
++        self.old_DEBUGSTREAM = smtpd.DEBUGSTREAM
++        smtpd.DEBUGSTREAM = StringIO.StringIO()
+         # Pick a random unused port by passing 0 for the port number
+         self.serv = smtpd.DebuggingServer((HOST, 0), ('nowhere', -1))
+         # Keep a note of what port was assigned
+@@ -176,6 +179,9 @@ class DebuggingServerTests(unittest.TestCase):
+         test_support.threading_cleanup(*self._threads)
+         # restore sys.stdout
+         sys.stdout = self.old_stdout
++        # restore DEBUGSTREAM
++        smtpd.DEBUGSTREAM.close()
++        smtpd.DEBUGSTREAM = self.old_DEBUGSTREAM
+ 
+     def testBasic(self):
+         # connect
+@@ -201,6 +207,17 @@ class DebuggingServerTests(unittest.TestCase):
+         self.assertEqual(smtp.ehlo(), expected)
+         smtp.quit()
+ 
++
++    def test_issue43124_putcmd_escapes_newline(self):
++        # see: https://bugs.python.org/issue43124
++        smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
++                            timeout=10)  # support.LOOPBACK_TIMEOUT in newer Pythons
++        self.addCleanup(smtp.close)
++        with self.assertRaises(ValueError) as exc:
++            smtp.putcmd('helo\nX-INJECTED')
++        self.assertIn("prohibited newline characters", str(exc.exception))
++        smtp.quit()
++
+     def testVRFY(self):
+         # VRFY isn't implemented in DebuggingServer
+         smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
+@@ -240,6 +257,54 @@ class DebuggingServerTests(unittest.TestCase):
+         mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
+         self.assertEqual(self.output.getvalue(), mexpect)
+ 
++    def test_issue43124_escape_localhostname(self):
++        # see: https://bugs.python.org/issue43124
++        # connect and send mail
++        m = 'wazzuuup\nlinetwo'
++        smtp = smtplib.SMTP(HOST, self.port, local_hostname='hi\nX-INJECTED',
++                            timeout=10)  # support.LOOPBACK_TIMEOUT in newer Pythons
++        self.addCleanup(smtp.close)
++        with self.assertRaises(ValueError) as exc:
++            smtp.sendmail("hi at me.com", "you at me.com", m)
++        self.assertIn(
++            "prohibited newline characters: ehlo hi\\nX-INJECTED",
++            str(exc.exception),
++        )
++        # XXX (see comment in testSend)
++        time.sleep(0.01)
++        smtp.quit()
++
++        debugout = smtpd.DEBUGSTREAM.getvalue()
++        self.assertNotIn("X-INJECTED", debugout)
++
++    def test_issue43124_escape_options(self):
++        # see: https://bugs.python.org/issue43124
++        # connect and send mail
++        m = 'wazzuuup\nlinetwo'
++        smtp = smtplib.SMTP(
++            HOST, self.port, local_hostname='localhost',
++            timeout=10)  # support.LOOPBACK_TIMEOUT in newer Pythons
++
++        self.addCleanup(smtp.close)
++#        smtp.sendmail("hi at me.com", "you at me.com", m)
++        # NB: gross hack but still cleaner than backporting whole ESMTP
++        # support to DebuggingServer
++        smtp.does_esmtp = 1
++        with self.assertRaises(ValueError) as exc:
++            smtp.mail("hi at me.com", ["X-OPTION\nX-INJECTED-1", "X-OPTION2\nX-INJECTED-2"])
++        msg = str(exc.exception)
++        self.assertIn("prohibited newline characters", msg)
++        self.assertIn("X-OPTION\\nX-INJECTED-1 X-OPTION2\\nX-INJECTED-2", msg)
++        # XXX (see comment in testSend)
++        time.sleep(0.01)
++        smtp.quit()
++
++        debugout = smtpd.DEBUGSTREAM.getvalue()
++        self.assertNotIn("X-OPTION", debugout)
++        self.assertNotIn("X-OPTION2", debugout)
++        self.assertNotIn("X-INJECTED-1", debugout)
++        self.assertNotIn("X-INJECTED-2", debugout)
++
+ 
+ class NonConnectingTests(unittest.TestCase):
+ 
+diff --git a/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst b/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
+new file mode 100644
+index 0000000000..e897d6cd36
+--- /dev/null
++++ b/Misc/NEWS.d/next/Security/2021-05-08-11-50-46.bpo-43124.2CTM6M.rst
+@@ -0,0 +1,2 @@
++Made the internal ``putcmd`` function in :mod:`smtplib` sanitize input for
++presence of ``\r`` and ``\n`` characters to avoid (unlikely) command injection.
+-- 
+2.38.1
+
+From 4e43d055206ca57794bdcaad2dac8978fd960663 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Tue, 31 Aug 2021 10:50:53 +0200
+Subject: [PATCH 16/36] Fix accidentally leaving one sub-test commented out
+
+---
+ Lib/test/test_smtplib.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py
+index 2224524ebd..b6fa293228 100644
+--- a/Lib/test/test_smtplib.py
++++ b/Lib/test/test_smtplib.py
+@@ -286,7 +286,7 @@ class DebuggingServerTests(unittest.TestCase):
+             timeout=10)  # support.LOOPBACK_TIMEOUT in newer Pythons
+ 
+         self.addCleanup(smtp.close)
+-#        smtp.sendmail("hi at me.com", "you at me.com", m)
++        smtp.sendmail("hi at me.com", "you at me.com", m)
+         # NB: gross hack but still cleaner than backporting whole ESMTP
+         # support to DebuggingServer
+         smtp.does_esmtp = 1
+-- 
+2.38.1
+
diff --git a/python-bpo-46756-urllib-auth.patch b/python-bpo-46756-urllib-auth.patch
new file mode 100644
index 0000000..133e1f3
--- /dev/null
+++ b/python-bpo-46756-urllib-auth.patch
@@ -0,0 +1,77 @@
+From e972fd923fd5172aa04c8f9d8775f1590ebbe394 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= <mgorny at gentoo.org>
+Date: Fri, 18 Mar 2022 12:26:44 +0100
+Subject: [PATCH 18/36] bpo-46756: Fix authorization check in urllib.request
+ (GH-31353)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fix a bug in urllib.request.HTTPPasswordMgr.find_user_password() and
+urllib.request.HTTPPasswordMgrWithPriorAuth.is_authenticated() which
+allowed to bypass authorization. For example, access to URI "example.org/foobar"
+was allowed if the user was authorized for URI "example.org/foo".
+
+(rebased for 2.7 by Michał Górny)
+---
+ Lib/test/test_urllib2.py | 17 +++++++++++++++++
+ Lib/urllib2.py           |  8 ++++----
+ 2 files changed, 21 insertions(+), 4 deletions(-)
+
+diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
+index 0adbb13c43..6acab89678 100644
+--- a/Lib/test/test_urllib2.py
++++ b/Lib/test/test_urllib2.py
+@@ -137,6 +137,7 @@ def test_password_manager(self):
+     >>> add("Some Realm", "http://example.com/ni", "ni", "ni")
+     >>> add("c", "http://example.com/foo", "foo", "ni")
+     >>> add("c", "http://example.com/bar", "bar", "nini")
++    >>> add("c", "http://example.com/foo/bar", "foobar", "nibar")
+     >>> add("b", "http://example.com/", "first", "blah")
+     >>> add("b", "http://example.com/", "second", "spam")
+     >>> add("a", "http://example.com", "1", "a")
+@@ -158,6 +159,22 @@ def test_password_manager(self):
+     ('foo', 'ni')
+     >>> mgr.find_user_password("c", "http://example.com/bar")
+     ('bar', 'nini')
++    >>> mgr.find_user_password("c", "http://example.com/foo/")
++    ('foo', 'ni')
++    >>> mgr.find_user_password("c", "http://example.com/foo/bar")
++    ('foo', 'ni')
++    >>> mgr.find_user_password("c", "http://example.com/foo/baz")
++    ('foo', 'ni')
++    >>> mgr.find_user_password("c", "http://example.com/foobar")
++    (None, None)
++
++    >>> add("c", "http://example.com/baz/", "baz", "ninini")
++    >>> mgr.find_user_password("c", "http://example.com/baz")
++    (None, None)
++    >>> mgr.find_user_password("c", "http://example.com/baz/")
++    ('baz', 'ninini')
++    >>> mgr.find_user_password("c", "http://example.com/baz/bar")
++    ('baz', 'ninini')
+ 
+     Actually, this is really undefined ATM
+ ##     Currently, we use the highest-level path where more than one match:
+diff --git a/Lib/urllib2.py b/Lib/urllib2.py
+index c92fb6afb8..f45ef57ee3 100644
+--- a/Lib/urllib2.py
++++ b/Lib/urllib2.py
+@@ -833,10 +833,10 @@ class HTTPPasswordMgr:
+             return True
+         if base[0] != test[0]:
+             return False
+-        common = posixpath.commonprefix((base[1], test[1]))
+-        if len(common) == len(base[1]):
+-            return True
+-        return False
++        prefix = base[1]
++        if prefix[-1:] != '/':
++            prefix += '/'
++        return test[1].startswith(prefix)
+ 
+ 
+ class HTTPPasswordMgrWithDefaultRealm(HTTPPasswordMgr):
+-- 
+2.38.1
+
diff --git a/python-configure-c99.patch b/python-configure-c99.patch
new file mode 100644
index 0000000..828be31
--- /dev/null
+++ b/python-configure-c99.patch
@@ -0,0 +1,52 @@
+From 351f5e0a4b805f78c8bb7e650ec695d7041d5f39 Mon Sep 17 00:00:00 2001
+From: Sam James <sam at gentoo.org>
+Date: Fri, 4 Nov 2022 03:38:19 +0000
+Subject: [PATCH 36/36] Fix -Wimplicit-int, -Wimplicit-function-declaration
+
+These warnings became fatal by default in Clang 16.
+
+Most of this has since been fixed upstream but
+https://github.com/python/cpython/pull/99085 is pending.
+
+Signed-off-by: Sam James <sam at gentoo.org>
+---
+ configure.ac | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index bf3b4e4d7e..0852fbd36a 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -2801,11 +2801,12 @@ if test "$posix_threads" = "yes"; then
+       AC_CACHE_VAL(ac_cv_pthread_system_supported,
+       [AC_RUN_IFELSE([AC_LANG_SOURCE([[
+       #include <stdio.h>
++      #include <stdlib.h>
+       #include <pthread.h>
+       void *foo(void *parm) {
+         return NULL;
+       }
+-      main() {
++      int main() {
+         pthread_attr_t attr;
+         pthread_t id;
+         if (pthread_attr_init(&attr)) exit(-1);
+@@ -4164,6 +4165,7 @@ then
+   AC_MSG_CHECKING(whether wchar_t is signed)
+   AC_CACHE_VAL(ac_cv_wchar_t_signed, [
+   AC_RUN_IFELSE([AC_LANG_SOURCE([[
++  #include <stdlib.h>
+   #include <wchar.h>
+   int main()
+   {
+@@ -4253,6 +4255,7 @@ AC_C_BIGENDIAN
+ AC_MSG_CHECKING(whether right shift extends the sign bit)
+ AC_CACHE_VAL(ac_cv_rshift_extends_sign, [
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[
++#include <stdlib.h>
+ int main()
+ {
+ 	exit(((-1)>>3 == -1) ? 0 : 1);
+-- 
+2.38.1
+
diff --git a/python-expat-2.4.5-tests.patch b/python-expat-2.4.5-tests.patch
new file mode 100644
index 0000000..973f7e7
--- /dev/null
+++ b/python-expat-2.4.5-tests.patch
@@ -0,0 +1,86 @@
+From 9f9f95976ac248d49685c39b355e0e1ce9001bca Mon Sep 17 00:00:00 2001
+From: Sebastian Pipping <sebastian at pipping.org>
+Date: Mon, 21 Feb 2022 15:48:32 +0100
+Subject: [PATCH 17/36] bpo-46811: Make test suite support Expat >=2.4.5
+ (GH-31453)
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Curly brackets were never allowed in namespace URIs
+according to RFC 3986, and so-called namespace-validating
+XML parsers have the right to reject them a invalid URIs.
+
+libexpat >=2.4.5 has become strcter in that regard due to
+related security issues; with ET.XML instantiating a
+namespace-aware parser under the hood, this test has no
+future in CPython.
+
+References:
+- https://datatracker.ietf.org/doc/html/rfc3968
+- https://www.w3.org/TR/xml-names/
+
+Also, test_minidom.py: Support Expat >=2.4.5
+
+(rebased for 2.7.18 by Michał Górny)
+---
+ Lib/test/test_minidom.py   | 12 ++++++++++--
+ Lib/test/test_xml_etree.py |  6 ------
+ 2 files changed, 10 insertions(+), 8 deletions(-)
+
+diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
+index 2eb642395b..3aa524c365 100644
+--- a/Lib/test/test_minidom.py
++++ b/Lib/test/test_minidom.py
+@@ -6,12 +6,14 @@ from StringIO import StringIO
+ from test import support
+ import unittest
+ 
++import pyexpat
+ import xml.dom
+ import xml.dom.minidom
+ import xml.parsers.expat
+ 
+ from xml.dom.minidom import parse, Node, Document, parseString
+ from xml.dom.minidom import getDOMImplementation
++from xml.parsers.expat import ExpatError
+ 
+ 
+ tstfile = support.findfile("test.xml", subdir="xmltestdata")
+@@ -1051,8 +1053,14 @@ class MinidomTest(unittest.TestCase):
+ 
+         # Verify that character decoding errors raise exceptions instead
+         # of crashing
+-        self.assertRaises(UnicodeDecodeError, parseString,
+-                '<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
++        if pyexpat.version_info >= (2, 4, 5):
++            self.assertRaises(ExpatError, parseString,
++                    b'<fran\xe7ais></fran\xe7ais>')
++            self.assertRaises(ExpatError, parseString,
++                    b'<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
++        else:
++            self.assertRaises(UnicodeDecodeError, parseString,
++                b'<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
+ 
+         doc.unlink()
+ 
+diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
+index c75d55f05c..0855bc07ca 100644
+--- a/Lib/test/test_xml_etree.py
++++ b/Lib/test/test_xml_etree.py
+@@ -1482,12 +1482,6 @@ class BugsTest(unittest.TestCase):
+                 b"<?xml version='1.0' encoding='ascii'?>\n"
+                 b'<body>tãg</body>')
+ 
+-    def test_issue3151(self):
+-        e = ET.XML('<prefix:localname xmlns:prefix="${stuff}"/>')
+-        self.assertEqual(e.tag, '{${stuff}}localname')
+-        t = ET.ElementTree(e)
+-        self.assertEqual(ET.tostring(e), b'<ns0:localname xmlns:ns0="${stuff}" />')
+-
+     def test_issue6565(self):
+         elem = ET.XML("<body><tag/></body>")
+         self.assertEqual(summarize_list(elem), ['tag'])
+-- 
+2.38.1
+
diff --git a/python-test-support-unlink-errno.patch b/python-test-support-unlink-errno.patch
new file mode 100644
index 0000000..aeca9d2
--- /dev/null
+++ b/python-test-support-unlink-errno.patch
@@ -0,0 +1,21 @@
+From: Mike Gilbert <floppym at gentoo.org>
+Subject: test.support.unlink: ignore EACCES and EROFS
+
+test.support.forget() removes a module's .pyc/.pyo from every sys.path entry,
+including ones the build environment mounts read-only (/usr/local/lib*) - there
+can be no stale .pyc there, so the failure to remove one is not a test failure.
+
+Based on Gentoo's https://bugs.gentoo.org/679628 (EACCES); PLD's builder mounts
+the tree read-only rather than denying the write, so EROFS is needed too.
+
+--- a/Lib/test/support/__init__.py
++++ b/Lib/test/support/__init__.py
+@@ -291,7 +291,7 @@ def unlink(filename):
+     try:
+         _unlink(filename)
+     except OSError as exc:
+-        if exc.errno not in (errno.ENOENT, errno.ENOTDIR):
++        if exc.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EACCES, errno.EROFS):
+             raise
+ 
+ def rmdir(dirname):
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/python.git/commitdiff/d265a4e262afa1319ff48749f6dd0d4a06fdfb2e



More information about the pld-cvs-commit mailing list