[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1052222: bullseye-pu: package python2.7/2.7.18-8+deb11u1



Package: release.debian.org
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: python2.7@packages.debian.org,debian-security@lists.debian.org,doko@debian.org
Control: affects -1 + src:python2.7

Hi release team, security team and Matthias (python maintainer),

I know that officially, we do not consider Python 2.7 covered by
security support. In bullseye, it has merely been kept to support a
small minority of applications that would otherwise have been removed.
Freexian SARL has an interest in updating it anyway. I am therefore
proposing a PU that fixes know security issues in Python 2.7. Do you
think we can accept this into bullseye? I recognize that such an update
could be seen as a promise of support. Therefore, I've Cc'ed the
security team to have them veto if desired. In effect, Freexian
currently makes this promise to customers and will continue to update
security issues in Python 2.7 as it enters LTS. So we might as well do
it now already.

[ Reason ]

The reason to update this package is two-fold. For one thing, I fix
autopkgtests and for another, I fix seven known vulnerabilities with
CVEs.

[ Impact ]

Users should not be using Python 2.7 in relevant contexts. In reality,
they do this anyway and thus remain vulnerable unless this is applied.
Much of what is being fixed here affects URL parsing or network
services.

[ Tests ]

I have fixed autopkgtests in the process of preparing the update. When
backporting patches for CVEs, I have also backported relevant patches.
Those are being run as part of autopkgtests.

[ Risks ]

The biggest risk of this change arises from the URL handling changes.
The API of functions is extended to accept new arguments for customizing
behaviour. The semantics of URL parsing changes a bit and might break
abnormal uses (which are the ones that are potentially vulnerable).
However, these changes have been applied to Python 3.x already.

For the heapq change, I initially observed Segmentation faults on
naively applying the patches. All tests pass now and I have carefully
reviewed the reference count changes and their effects in the
surrounding code.

The remaining changes bear a lower risk of negatively affecting users.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]

  * Add testsuite-fix-with-expat.diff: Fix autopkgtests with updated
    expat.
  * Fix issue9189.diff: Update test suite to match behaviour change.
  * Add CVE-2021-23336.diff: Only use '&' as query string separator
  * Add CVE-2022-0391.diff: Make urlsplit robust against newlines.
  * Add CVE-2022-48560.diff: Fix use-after-free in heapq module.
  * Add CVE-2022-48565.diff: Reject entities declarations while parsing
    XML plists.
  * Add CVE-2022-48566.diff: Make constant time comparison more
    constant-time.
  * Add CVE-2023-24329.diff: More WHATWG-compatible URL parsing
  * Add CVE-2023-40217.diff: Prevent reading unauthenticated data on a
    SSLSocket.

[ Other info ]

I am also applying a similar update for buster (and Freexian's jessie
and stretch suites).

Helmut
diff -u python2.7-2.7.18/debian/changelog python2.7-2.7.18/debian/changelog
--- python2.7-2.7.18/debian/changelog
+++ python2.7-2.7.18/debian/changelog
@@ -1,3 +1,20 @@
+python2.7 (2.7.18-8+deb11u1) bullseye; urgency=medium
+
+  * Non-maintainer upload by the LTS Team.
+  * Add testsuite-fix-with-expat.diff: Fix autopkgtests with updated expat.
+  * Fix issue9189.diff: Update test suite to match behaviour change.
+  * Add CVE-2021-23336.diff: Only use '&' as query string separator
+  * Add CVE-2022-0391.diff: Make urlsplit robust against newlines
+  * Add CVE-2022-48560.diff: Fix use-after-free in heapq module.
+  * Add CVE-2022-48565.diff: Reject entities declarations while parsing XML
+    plists.
+  * Add CVE-2022-48566.diff: Make constant time comparison more constant-time.
+  * Add CVE-2023-24329.diff: More WHATWG-compatible URL parsing
+  * Add CVE-2023-40217.diff: Prevent reading unauthenticated data on a
+    SSLSocket
+
+ -- Helmut Grohne <helmut@subdivi.de>  Tue, 19 Sep 2023 09:10:59 +0200
+
 python2.7 (2.7.18-8) unstable; urgency=medium
 
   [ Andreas Beckmann ]
diff -u python2.7-2.7.18/debian/patches/issue9189.diff python2.7-2.7.18/debian/patches/issue9189.diff
--- python2.7-2.7.18/debian/patches/issue9189.diff
+++ python2.7-2.7.18/debian/patches/issue9189.diff
@@ -338,3 +338,23 @@
  
      # strip spurious spaces
      for k, v in done.items():
+--- a/Lib/distutils/tests/test_sysconfig.py
++++ b/Lib/distutils/tests/test_sysconfig.py
+@@ -60,7 +60,7 @@ def test_parse_makefile_base(self):
+             fd.close()
+         d = sysconfig.parse_makefile(self.makefile)
+         self.assertEqual(d, {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'",
+-                             'OTHER': 'foo'})
++                             'OTHER': 'foo', 'VAR': '$OTHER'})
+
+     def test_parse_makefile_literal_dollar(self):
+         self.makefile = test.test_support.TESTFN
+@@ -72,7 +72,7 @@ def test_parse_makefile_literal_dollar(self):
+             fd.close()
+         d = sysconfig.parse_makefile(self.makefile)
+         self.assertEqual(d, {'CONFIG_ARGS': r"'--arg1=optarg1' 'ENV=\$LIB'",
+-                             'OTHER': 'foo'})
++                             'OTHER': 'foo', 'VAR': '$OTHER'})
+
+
+     def test_sysconfig_module(self):
diff -u python2.7-2.7.18/debian/patches/series.in python2.7-2.7.18/debian/patches/series.in
--- python2.7-2.7.18/debian/patches/series.in
+++ python2.7-2.7.18/debian/patches/series.in
@@ -75,3 +75,12 @@
 CVE-2019-20907.diff
 CVE-2020-8492.diff
 CVE-2021-3177.diff
+
+testsuite-fix-with-expat.diff
+CVE-2021-23336.diff
+CVE-2022-0391.diff
+CVE-2022-48560.diff
+CVE-2022-48565.diff
+CVE-2022-48566.diff
+CVE-2023-24329.diff
+CVE-2023-40217.diff
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2021-23336.diff
+++ python2.7-2.7.18/debian/patches/CVE-2021-23336.diff
@@ -0,0 +1,472 @@
+From fcbe0cb04d35189401c0c880ebfb4311e952d776 Mon Sep 17 00:00:00 2001
+From: Adam Goldschmidt <adamgold7@gmail.com>
+Date: Mon, 15 Feb 2021 00:41:57 +0200
+Subject: [PATCH] bpo-42967: only use '&' as a query string separator (#24297)
+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@netwok.org>
+Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
+Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
+Co-authored-by: Éric Araujo <merwok@netwok.org>
+---
+ Doc/library/cgi.rst                           |  9 ++-
+ Doc/library/urllib.parse.rst                  | 16 ++++-
+ Doc/whatsnew/3.10.rst                         | 13 ++++
+ Doc/whatsnew/3.6.rst                          | 13 ++++
+ Doc/whatsnew/3.7.rst                          | 13 ++++
+ Doc/whatsnew/3.8.rst                          | 13 ++++
+ Doc/whatsnew/3.9.rst                          | 15 +++-
+ Lib/cgi.py                                    | 23 ++++---
+ Lib/test/test_cgi.py                          | 29 ++++++--
+ Lib/test/test_urlparse.py                     | 68 +++++++++++++------
+ Lib/urllib/parse.py                           | 20 ++++--
+ .../2021-02-14-15-59-16.bpo-42967.YApqDS.rst  |  1 +
+ 12 files changed, 186 insertions(+), 47 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Security/2021-02-14-15-59-16.bpo-42967.YApqDS.rst
+
+Backport:
+ * Drop Doc/whatsnew and Misc/NEWS.d
+ * urllib.parse.urlencode -> urllib.urlencode
+ * urllib.parse -> urlparse
+ * Significant refactoring due to differences in arguments
+ * Avoid using TestCase.subTest
+
+diff --git a/Doc/library/cgi.rst b/Doc/library/cgi.rst
+index 4048592e73..05d9cdf424 100644
+--- a/Doc/library/cgi.rst
++++ b/Doc/library/cgi.rst
+@@ -277,24 +277,25 @@ 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.
+ 
+ 
+-.. 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]]]])
+ 
+    This function is deprecated in this module. Use :func:`urlparse.parse_qs`
+    instead. It is maintained here only for backward compatibility.
+ 
+-.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]])
++.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields[, separator]]]])
+ 
+    This function is deprecated in this module. Use :func:`urlparse.parse_qsl`
+    instead. It is maintained here only for backward compatibility.
+ 
+-.. function:: parse_multipart(fp, pdict)
++.. function:: parse_multipart(fp, pdict, separator="&")
+ 
+    Parse input of type :mimetype:`multipart/form-data` (for  file uploads).
+    Arguments are *fp* for the input file and *pdict* for a dictionary containing
+@@ -303,6 +303,9 @@ algorithms implemented in this module in other circumstances.
+    Note that this does not parse nested multipart parts --- use
+    :class:`FieldStorage` for that.
+ 
++   .. versionchanged:: 2.7 security update
++      Added the *separator* parameter.
++
+ 
+ .. function:: parse_header(string)
+ 
+diff --git a/Doc/library/urllib.parse.rst b/Doc/library/urllib.parse.rst
+index f9c8ba7398..1a79078239 100644
+--- a/Doc/library/urlparse.rst
++++ b/Doc/library/urlparse.rst
+@@ -165,7 +165,7 @@ or on combining URL components into a URL string.
+       Added IPv6 URL parsing capabilities.
+ 
+ 
+-.. 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
+@@ -190,6 +190,8 @@ or on combining URL components into a URL string.
+    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.
+
+@@ -201,7 +203,12 @@ or on combining URL components into a URL string.
+    .. versionchanged:: 2.7.16
+       Added *max_num_fields* parameter.
+ 
+-.. function:: parse_qsl(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]])
++   .. versionchanged:: 2.7 security update
++      Added *separator* parameter with the default value of `&`. Python versions earlier than Python 3.10 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
+@@ -226,6 +232,8 @@ or on combining URL components into a URL string.
+    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.
+ 
+@@ -235,6 +243,11 @@ or on combining URL components into a URL string.
+    .. versionchanged:: 2.7.16
+       Added *max_num_fields* parameter.
+ 
++   .. versionchanged:: 2.7 security update
++      Added *separator* parameter with the default value of `&`. Python versions earlier than Python 3.10 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 6018c36086..6c72507c20 100755
+--- a/Lib/cgi.py
++++ b/Lib/cgi.py
+@@ -115,7 +115,8 @@ def closelog():
+ # 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:
+@@ -134,6 +135,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
+@@ -154,7 +158,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
+     if environ['REQUEST_METHOD'] == 'POST':
+         ctype, pdict = parse_header(environ['CONTENT_TYPE'])
+         if ctype == 'multipart/form-data':
+-            return parse_multipart(fp, pdict)
++            return parse_multipart(fp, pdict, separator=separator)
+         elif ctype == 'application/x-www-form-urlencoded':
+             clength = int(environ['CONTENT_LENGTH'])
+             if maxlen and clength > maxlen:
+@@ -178,27 +182,30 @@ 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,
+ # this is done in order to maintain backward compatibility.
+ 
+-def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
++def parse_qs(qs, keep_blank_values=0, strict_parsing=0, separator='&'):
+     """Parse a query given as a string argument."""
+     warn("cgi.parse_qs is deprecated, use urlparse.parse_qs instead",
+          PendingDeprecationWarning, 2)
+-    return urlparse.parse_qs(qs, keep_blank_values, strict_parsing)
++    return urlparse.parse_qs(qs, keep_blank_values, strict_parsing,
++                             separator=separator)
+
+
+-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."""
+     warn("cgi.parse_qsl is deprecated, use urlparse.parse_qsl instead",
+          PendingDeprecationWarning, 2)
+     return urlparse.parse_qsl(qs, keep_blank_values, strict_parsing,
+-                              max_num_fields)
++                              max_num_fields, separator=separator)
+
+-def parse_multipart(fp, pdict):
++def parse_multipart(fp, pdict, separator='&'):
+     """Parse multipart input.
+ 
+     Arguments:
+@@ -315,7 +319,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:
+@@ -363,6 +367,7 @@ def __init__(self, fp=None, headers=None, outerboundary=b'',
+         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
+@@ -589,7 +594,8 @@ def read_urlencoded(self):
+         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()
+ 
+@@ -605,7 +610,8 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
+             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 +654,7 @@ def read_multi(self, environ, keep_blank_values, strict_parsing):
+         klass = self.FieldStorageClass or self.__class__
+         part = klass(self.fp, {}, ib,
+                      environ, keep_blank_values, strict_parsing,
+-                     max_num_fields)
++                     max_num_fields, separator=self.separator)
+
+         # Throw first part away
+         while not part.done:
+diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py
+index 6b29759da4..239d97589c 100644
+--- a/Lib/test/test_cgi.py
++++ b/Lib/test/test_cgi.py
+@@ -53,12 +53,9 @@ def do_test(buf, method):
+     ("", 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: ''")),
+@@ -73,8 +70,6 @@ def do_test(buf, method):
+     ("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'],
+@@ -201,6 +196,30 @@ def test_strict(self):
+             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 762500789f..3b1c360625 100644
+--- a/Lib/test/test_urlparse.py
++++ b/Lib/test/test_urlparse.py
+@@ -32,16 +32,10 @@
+     ("&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')]),
++    (";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 = [
+@@ -68,16 +62,10 @@
+     (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):
+@@ -886,6 +874,42 @@ def test_parse_qsl_encoding(self):
+         self.assertEqual(urlparse.urlparse("http://www.python.org:80";),
+                 ('http','www.python.org:80','','','',''))
+ 
++    def test_parse_qs_separator(self):
++        parse_qs_semicolon_cases = [
++            (";", {}),
++            (";;", {}),
++            (";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']}),
++        ]
++        for orig, expect in parse_qs_semicolon_cases:
++            result = urlparse.parse_qs(orig, separator=';')
++            self.assertEqual(result, expect, "Error parsing %r" % orig)
++
++
++    def test_parse_qsl_separator(self):
++        parse_qsl_semicolon_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')]),
++            (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')]),
++        ]
++        for orig, expect in parse_qsl_semicolon_cases:
++            result = urlparse.parse_qsl(orig, separator=';')
++            self.assertEqual(result, expect, "Error parsing %r" % orig)
++
++
+ def test_main():
+     test_support.run_unittest(UrlParseTestCase)
+ 
+diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
+index ea897c3032..5bd067895b 100644
+--- a/Lib/urlparse.py
++++ b/Lib/urlparse.py
+@@ -662,7 +662,8 @@ def unquote(string, encoding='utf-8', errors='replace'):
+             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:
+@@ -686,10 +686,13 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
+
+         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:
+@@ -701,7 +704,8 @@ def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
+             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:
+@@ -724,17 +727,23 @@ def parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
+     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):
++        raise ValueError("Separator must be of type str.")
++
+     # 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:
+-- 
+2.40.1
+
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2022-0391.diff
+++ python2.7-2.7.18/debian/patches/CVE-2022-0391.diff
@@ -0,0 +1,151 @@
+From 76cd81d60310d65d01f9d7b48a8985d8ab89c8b4 Mon Sep 17 00:00:00 2001
+From: Senthil Kumaran <senthil@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@krypto.org>
+Co-authored-by: Serhiy Storchaka <storchaka@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@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)
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2022-48560.diff
+++ python2.7-2.7.18/debian/patches/CVE-2022-48560.diff
@@ -0,0 +1,155 @@
+From 79f89e6e5a659846d1068e8b1bd8e491ccdef861 Mon Sep 17 00:00:00 2001
+From: Pablo Galindo <Pablogsal@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
+
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2022-48565.diff
+++ python2.7-2.7.18/debian/patches/CVE-2022-48565.diff
@@ -0,0 +1,90 @@
+From 05ee790f4d1cd8725a90b54268fc1dfe5b4d1fa2 Mon Sep 17 00:00:00 2001
+From: Ronald Oussoren <ronaldoussoren@mac.com>
+Date: Mon, 19 Oct 2020 20:13:49 +0200
+Subject: [PATCH] bpo-42051: Reject XML entity declarations in plist files
+ (#22760)
+
+---
+ 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
+
+Backport:
+ * Drop Misc/NEWS.d
+ * plistlib.py:
+   * Handle cAmElCaSe vs under_scores
+   * self.parser vs parser
+   * Degrade InvalidFileException to ValueError
+ * test_plistlib.py:
+   * Update hunk context
+   * Drop b"" prefix
+   * Degrade InvalidFileException to ValueError
+   * Adapt parser invocation to older API
+   * Reimplement assertRaisesRegexp
+
+diff --git a/Lib/plistlib.py b/Lib/plistlib.py
+index aff5fe36ca..ba7ac19364 100644
+--- a/Lib/plistlib.py
++++ b/Lib/plistlib.py
+@@ -173,9 +173,16 @@ def parse(self, fileobj):
+         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 e5c9b5b6b2..cb071da1f3 100644
+--- a/Lib/test/test_plistlib.py
++++ b/Lib/test/test_plistlib.py
+@@ -106,6 +106,19 @@
+ </plist>
+ """.replace(" " * 8, "\t")  # Apple as well as plistlib.py output hard tabs
+ 
++XML_PLIST_WITH_ENTITY='''\
++<?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):
+ 
+@@ -524,6 +537,15 @@ def test_modified_uid_huge(self):
+         self.assertEqual(test1, result1)
+         self.assertEqual(test2, result2)
+ 
++    def test_xml_plist_with_entity_decl(self):
++        try:
++            plistlib.readPlistFromString(XML_PLIST_WITH_ENTITY)
++        except ValueError as e:
++            self.assertIn("XML entity declarations are not supported",
++                          e.message)
++        else:
++            self.fail("expected ValueError to be raised")
++
+ 
+ def test_main():
+     test_support.run_unittest(TestPlistlib)
+-- 
+2.40.1
+
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2022-48566.diff
+++ python2.7-2.7.18/debian/patches/CVE-2022-48566.diff
@@ -0,0 +1,40 @@
+From c1bbca5b004b3f74d240ef8a76ff445cc1a27efb Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <31488909+miss-islington@users.noreply.github.com>
+Date: Sat, 21 Nov 2020 01:18:41 -0800
+Subject: [PATCH] bpo-40791: Make compare_digest more constant-time. (GH-20444)
+
+* bpo-40791: Make compare_digest more constant-time.
+
+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@google.com>
+---
+ .../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
+
+Backport:
+ * Drop Misc/NEWS.d
+ * _operator.c -> operator.c
+
+diff --git a/Modules/_operator.c b/Modules/_operator.c
+index 8a54829e5b..6f8f68f459 100644
+--- a/Modules/operator.c
++++ b/Modules/operator.c
+@@ -735,7 +735,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.40.1
+
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2023-24329.diff
+++ python2.7-2.7.18/debian/patches/CVE-2023-24329.diff
@@ -0,0 +1,264 @@
+From 439b9cfaf43080e91c4ad69f312f21fa098befc7 Mon Sep 17 00:00:00 2001
+From: Ben Kallus <49924171+kenballus@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@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@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@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@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`
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/CVE-2023-40217.diff
+++ python2.7-2.7.18/debian/patches/CVE-2023-40217.diff
@@ -0,0 +1,321 @@
+From 0cb0c238d520a8718e313b52cffc356a5a7561bf Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?=C5=81ukasz=20Langa?= <lukasz@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@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@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
+
only in patch2:
unchanged:
--- python2.7-2.7.18.orig/debian/patches/testsuite-fix-with-expat.diff
+++ python2.7-2.7.18/debian/patches/testsuite-fix-with-expat.diff
@@ -0,0 +1,84 @@
+From 2cae93832f46b245847bdc252456ddf7742ef45e Mon Sep 17 00:00:00 2001
+From: Sebastian Pipping <sebastian@pipping.org>
+Date: Mon, 21 Feb 2022 15:48:32 +0100
+Subject: [PATCH] bpo-46811: Make test suite support Expat >=2.4.5 (GH-31453)
+
+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
+
+Backport:
+ * Drop Misc/NEWS.d
+ * test_minidom.py: Update import context
+ * test_minidom.py: Drop byte string prefixes
+ * test_minidom.py: Remove expat version check as the expat changes have been
+   backported in Debian.
+ * test_minidom.py: Drop testExceptionOnSpacesInXMLNSValue hunk
+
+---
+ Lib/test/test_minidom.py                        | 17 +++++++++++++++--
+ Lib/test/test_xml_etree.py                      |  6 ------
+ .../2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst    |  1 +
+ 3 files changed, 16 insertions(+), 8 deletions(-)
+ create mode 100644 Misc/NEWS.d/next/Library/2022-02-20-21-03-31.bpo-46811.8BxgdQ.rst
+
+diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
+index 1663b1f114..97620258d8 100644
+--- a/Lib/test/test_minidom.py
++++ b/Lib/test/test_minidom.py
+@@ -6,12 +6,14 @@
+ 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")
+@@ -1147,8 +1149,10 @@ def testEncodings(self):
+ 
+         # Verify that character decoding errors raise exceptions instead
+         # of crashing
+-        self.assertRaises(UnicodeDecodeError, parseString,
+-                '<fran\xe7ais>Comment \xe7a va ? Tr\xe8s bien ?</fran\xe7ais>')
++        self.assertRaises(ExpatError, parseString,
++                '<fran\xe7ais></fran\xe7ais>')
++        self.assertRaises(ExpatError, parseString,
++                '<franais>Comment \xe7a va ? Tr\xe8s bien ?</franais>')
+
+         doc.unlink()
+
+diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
+index a25f536134..c5292b5e9e 100644
+--- a/Lib/test/test_xml_etree.py
++++ b/Lib/test/test_xml_etree.py
+@@ -2192,12 +2192,6 @@ def test_issue6233(self):
+                 b"<?xml version='1.0' encoding='ascii'?>\n"
+                 b'<body>t&#227;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'])

Reply to: