+gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)
+* gh-114572: Fix locking in cert_store_stats and get_ca_certs
+cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with
+X509_STORE_get0_objects, but reading the result requires a lock. See
+https://github.com/openssl/openssl/pull/23224 for details.
+Instead, use X509_STORE_get1_objects, newly added in that PR.
+X509_STORE_get1_objects does not exist in current OpenSSLs, but we can
+polyfill it with X509_STORE_lock and X509_STORE_unlock.
default:
+- / Ignore X509_LU_FAIL, X509_LU_RETRY, X509_LU_PKEY.
+- As far as I can tell they are internal states and never
+- stored in a cert store /
++ / Ignore unrecognized types. /
+--- python3.11-3.11.2.orig/Doc/library/ipaddress.rst
++++ python3.11-3.11.2/Doc/library/ipaddress.rst
+@@ -188,18 +188,53 @@ write code that handles both IP versions
.. attribute:: is_private
+- True if the address is allocated for private networks. See
++ True if the address is defined as not globally reachable by
iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry
+- (for IPv6).
++ (for IPv6) with the following exceptions:
++
++ is_private is False for the shared address space (100.64.0.0/10)
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
++
++ address.is_private == address.ipv4_mapped.is_private
++
++ is_private has value opposite to :attr:is_global, except for the shared address space
++ (100.64.0.0/10 range) where they are both False.
++
++ .. versionchanged:: 3.11.10
++
++ Fixed some false positives and false negatives.
++
++ 192.0.0.0/24 is considered private with the exception of 192.0.0.9/32 and
++ 192.0.0.10/32 (previously: only the 192.0.0.0/29 sub-range was considered private).
++ 64:ff9b:1::/48 is considered private.
++ 2002::/16 is considered private.
++ There are exceptions within 2001::/23 (otherwise considered private): 2001:1::1/128,
++ 2001:1::2/128, 2001:3::/32, 2001:4:112::/48, 2001:20::/28, 2001:30::/28.
++ The exceptions are not considered private.
.. attribute:: is_global
+- True if the address is allocated for public networks. See
++ True if the address is defined as globally reachable by
iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry
+- (for IPv6).
++ (for IPv6) with the following exception:
++
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
++
++ address.is_global == address.ipv4_mapped.is_global
++
++ is_global has value opposite to :attr:is_private, except for the shared address space
++ (100.64.0.0/10 range) where they are both False.
.. versionadded:: 3.4
++ .. versionchanged:: 3.11.10
++
++ Fixed some false positives and false negatives, see :attr:is_private for details.
++
.. attribute:: is_unspecified
True if the address is unspecified. See :RFC:5735 (for IPv4)
+--- python3.11-3.11.2.orig/Lib/ipaddress.py
++++ python3.11-3.11.2/Lib/ipaddress.py
+@@ -1086,7 +1086,11 @@ class _BaseNetwork(_IPAddressBase):
"""
return any(self.network_address in priv_network and
self.broadcast_address in priv_network
+- for priv_network in self._constants._private_networks)
++ for priv_network in self._constants._private_networks) and all(
++ self.network_address not in network and
++ self.broadcast_address not in network
++ for network in self._constants._private_networks_exceptions
++ )
@property
def is_global(self):
+@@ -1333,18 +1337,41 @@ class IPv4Address(_BaseV4, _BaseAddress)
@property
@functools.lru_cache()
def is_private(self):
+- """Test if this address is allocated for private networks.
+-
+- Returns:
+- A boolean, True if the address is reserved per
+- iana-ipv4-special-registry.
+-
+- """
+- return any(self in net for net in self._constants._privatenetworks)
++ """True if the address is defined as not globally reachable by
++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_
++ (for IPv6) with the following exceptions:
++
++ is_private is False for 100.64.0.0/10
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
++
++ address.is_private == address.ipv4_mapped.is_private
++
++ is_private has value opposite to :attr:is_global, except for the 100.64.0.0/10
++ IPv4 range where they are both False.
++ """
++ return (
++ any(self in net for net in self._constants._private_networks)
++ and all(self not in net for net in self._constants._private_networks_exceptions)
++ )
@property
@functools.lru_cache()
def isglobal(self):
++ """True if the address is defined as globally reachable by
++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_
++ (for IPv6) with the following exception:
++
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
++
++ address.is_global == address.ipv4_mapped.is_global
++
++ is_global has value opposite to :attr:is_private, except for the 100.64.0.0/10
++ IPv4 range where they are both False.
++ """
return self not in self._constants._public_network and not self.is_private
@property
+@@ -1548,13 +1575,15 @@ class _IPv4Constants:
_unspecified_address = IPv4Address('0.0.0.0')
+@@ -2007,27 +2041,42 @@ class IPv6Address(_BaseV6, _BaseAddress)
@property
@functools.lru_cache()
def isprivate(self):
+- """Test if this address is allocated for private networks.
++ """True if the address is defined as not globally reachable by
++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_
++ (for IPv6) with the following exceptions:
++
++ is_private is False for 100.64.0.0/10
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
+- Returns:
+- A boolean, True if the address is reserved per
+- iana-ipv6-special-registry, or is ipv4_mapped and is
+- reserved in the iana-ipv4-special-registry.
++ address.is_private == address.ipv4_mapped.is_private
++ is_private has value opposite to :attr:is_global, except for the 100.64.0.0/10
++ IPv4 range where they are both False.
"""
ipv4_mapped = self.ipv4_mapped
if ipv4_mapped is not None:
return ipv4_mapped.is_private
+- return any(self in net for net in self._constants._private_networks)
++ return (
++ any(self in net for net in self._constants._private_networks)
++ and all(self not in net for net in self._constants._private_networks_exceptions)
++ )
@property
def isglobal(self):
+- """Test if this address is allocated for public networks.
++ """True if the address is defined as globally reachable by
++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_
++ (for IPv6) with the following exception:
++
++ For IPv4-mapped IPv6-addresses the is_private value is determined by the
++ semantics of the underlying IPv4 addresses and the following condition holds
++ (see :attr:IPv6Address.ipv4_mapped)::
+- Returns:
+- A boolean, true if the address is not reserved per
+- iana-ipv6-special-registry.
++ address.is_global == address.ipv4_mapped.is_global
++ is_global has value opposite to :attr:is_private, except for the 100.64.0.0/10
++ IPv4 range where they are both False.
IPv6Network('2001:db8::/32'),
+- IPv6Network('2001:10::/28'),
++ # IANA says N/A, let's consider it not globally reachable to be safe
++ IPv6Network('2002::/16'),
"Version in base suite: 3.11.2-6+deb12u2
Base version: python3.11_3.11.2-6+deb12u2 Target version: python3.11_3.11.2-6+deb12u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/python3.11/python3.11_3.11.2-6+deb12u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/python3.11/python3.11_3.11.2-6+deb12u3.dsc
changelog | 8 + patches/CVE-2024-0397.diff | 136 ++++++++++++++++++++ patches/CVE-2024-4032.diff | 299 +++++++++++++++++++++++++++++++++++++++++++++ patches/CVE-2024-8088.diff | 122 ++++++++++++++++++ patches/series | 3 5 files changed, 568 insertions(+)
diff -Nru python3.11-3.11.2/debian/changelog python3.11-3.11.2/debian/changelog --- python3.11-3.11.2/debian/changelog 2024-05-02 11:59:08.000000000 +0000 +++ python3.11-3.11.2/debian/changelog 2024-08-26 07:20:54.000000000 +0000 @@ -1,3 +1,11 @@ +python3.11 (3.11.2-6+deb12u3) bookworm-security; urgency=medium +
python3.11 (3.11.2-6+deb12u2) bookworm; urgency=medium
[ Steve McIntyre ] diff -Nru python3.11-3.11.2/debian/patches/CVE-2024-0397.diff python3.11-3.11.2/debian/patches/CVE-2024-0397.diff --- python3.11-3.11.2/debian/patches/CVE-2024-0397.diff 1970-01-01 00:00:00.000000000 +0000 +++ python3.11-3.11.2/debian/patches/CVE-2024-0397.diff 2024-08-26 07:17:39.000000000 +0000 @@ -0,0 +1,136 @@ +From 01c37f1d0714f5822d34063ca7180b595abf589d Mon Sep 17 00:00:00 2001 +From: "Miss Islington (bot)"
+gh-114572: Fix locking in cert_store_stats and get_ca_certs (GH-114573)
+* gh-114572: Fix locking in cert_store_stats and get_ca_certs
+cert_store_stats and get_ca_certs query the SSLContext's X509_STORE with +X509_STORE_get0_objects, but reading the result requires a lock. See +https://github.com/openssl/openssl/pull/23224 for details.
+Instead, use X509_STORE_get1_objects, newly added in that PR. +X509_STORE_get1_objects does not exist in current OpenSSLs, but we can +polyfill it with X509_STORE_lock and X509_STORE_unlock.
+* Work around const-correctness problem
+* Add missing X509_STORE_get1_objects failure check
+* Add blurb +(cherry picked from commit bce693111bff906ccf9281c22371331aaff766ab)
+Co-authored-by: David Benjamin davidben@google.com
+--- python3.11-3.11.2.orig/Modules/_ssl.c ++++ python3.11-3.11.2/Modules/_ssl.c +@@ -4493,6 +4493,50 @@ set_sni_callback(PySSLContext *self, PyO
++#if OPENSSL_VERSION_NUMBER < 0x30300000L ++static X509_OBJECT x509_object_dup(const X509_OBJECT obj) ++{ ++ int ok; ++ X509_OBJECT ret = X509_OBJECT_new(); ++ if (ret == NULL) { ++ return NULL; ++ } ++ switch (X509_OBJECT_get_type(obj)) { ++ case X509_LU_X509: ++ ok = X509_OBJECT_set1_X509(ret, X509_OBJECT_get0_X509(obj)); ++ break; ++ case X509_LU_CRL: ++ / X509_OBJECT_get0_X509_CRL was not const-correct prior to 3.0./ ++ ok = X509_OBJECT_set1_X509_CRL( ++ ret, X509_OBJECT_get0_X509_CRL((X509_OBJECT )obj)); ++ break; ++ default: ++ / We cannot duplicate unrecognized types in a polyfill, but it is ++ safe to leave an empty object. The caller will ignore it. / ++ ok = 1; ++ break; ++ } ++ if (!ok) { ++ X509_OBJECT_free(ret); ++ return NULL; ++ } ++ return ret; ++} ++ ++static STACK_OF(X509_OBJECT) ++X509_STORE_get1_objects(X509_STORE store) ++{ ++ STACK_OF(X509_OBJECT) ret; ++ if (!X509_STORE_lock(store)) { ++ return NULL; ++ } ++ ret = sk_X509_OBJECT_deep_copy(X509_STORE_get0_objects(store), ++ x509_object_dup, X509_OBJECT_free); ++ X509_STORE_unlock(store); ++ return ret; ++} ++#endif ++
+--- python3.11-3.11.2.orig/Doc/library/ipaddress.rst ++++ python3.11-3.11.2/Doc/library/ipaddress.rst +@@ -188,18 +188,53 @@ write code that handles both IP versions
+-
True
if the address is allocated for private networks. See ++True
if the address is defined as not globally reachable byis_private
isFalse
for the shared address space (100.64.0.0/10
) ++ For IPv4-mapped IPv6-addresses theis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
):: ++ ++ address.is_private == address.ipv4_mapped.is_private ++ ++is_private
has value opposite to :attr:is_global
, except for the shared address space ++ (100.64.0.0/10
range) where they are bothFalse
. ++ ++ .. versionchanged:: 3.11.10 ++ ++ Fixed some false positives and false negatives. ++ ++192.0.0.0/24
is considered private with the exception of192.0.0.9/32
and ++192.0.0.10/32
(previously: only the192.0.0.0/29
sub-range was considered private). ++64:ff9b:1::/48
is considered private. ++2002::/16
is considered private. ++ There are exceptions within2001::/23
(otherwise considered private):2001:1::1/128
, ++2001:1::2/128
,2001:3::/32
,2001:4:112::/48
,2001:20::/28
,2001:30::/28
. ++ The exceptions are not considered private.+-
True
if the address is allocated for public networks. See ++True
if the address is defined as globally reachable byis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
):: ++ ++ address.is_global == address.ipv4_mapped.is_global ++ ++is_global
has value opposite to :attr:is_private
, except for the shared address space ++ (100.64.0.0/10
range) where they are bothFalse
.++ .. versionchanged:: 3.11.10 ++ ++ Fixed some false positives and false negatives, see :attr:
is_private
for details. ++True
if the address is unspecified. See :RFC:5735
(for IPv4) +--- python3.11-3.11.2.orig/Lib/ipaddress.py ++++ python3.11-3.11.2/Lib/ipaddress.py +@@ -1086,7 +1086,11 @@ class _BaseNetwork(_IPAddressBase):True
if the address is defined as not globally reachable by ++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_ ++ (for IPv6) with the following exceptions: ++ ++is_private
isFalse
for100.64.0.0/10
++ For IPv4-mapped IPv6-addresses theis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
):: ++ ++ address.is_private == address.ipv4_mapped.is_private ++ ++is_private
has value opposite to :attr:is_global
, except for the100.64.0.0/10
++ IPv4 range where they are bothFalse
. ++ """ ++ return ( ++ any(self in net for net in self._constants._private_networks) ++ and all(self not in net for net in self._constants._private_networks_exceptions) ++ )True
if the address is defined as globally reachable by ++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_ ++ (for IPv6) with the following exception: ++ ++ For IPv4-mapped IPv6-addresses theis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
):: ++ ++ address.is_global == address.ipv4_mapped.is_global ++ ++is_global
has value opposite to :attr:is_private
, except for the100.64.0.0/10
++ IPv4 range where they are bothFalse
. ++ """++ # Not globally reachable address blocks listed on ++ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
++ _private_networks_exceptions = [ ++ IPv4Network('192.0.0.9/32'), ++ IPv4Network('192.0.0.10/32'), ++ ] ++
True
if the address is defined as not globally reachable by ++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_ ++ (for IPv6) with the following exceptions: ++ ++is_private
isFalse
for100.64.0.0/10
++ For IPv4-mapped IPv6-addresses theis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
)::+- Returns: +- A boolean, True if the address is reserved per +- iana-ipv6-special-registry, or is ipv4_mapped and is +- reserved in the iana-ipv4-special-registry. ++ address.is_private == address.ipv4_mapped.is_private
++
is_private
has value opposite to :attr:is_global
, except for the100.64.0.0/10
++ IPv4 range where they are bothFalse
.True
if the address is defined as globally reachable by ++ iana-ipv4-special-registry (for IPv4) or iana-ipv6-special-registry_ ++ (for IPv6) with the following exception: ++ ++ For IPv4-mapped IPv6-addresses theis_private
value is determined by the ++ semantics of the underlying IPv4 addresses and the following condition holds ++ (see :attr:IPv6Address.ipv4_mapped
)::+- Returns: +- A boolean, true if the address is not reserved per +- iana-ipv6-special-registry. ++ address.is_global == address.ipv4_mapped.is_global
++
is_global
has value opposite to :attr:is_private
, except for the100.64.0.0/10
++ IPv4 range where they are bothFalse
.+@@ -2268,19 +2317,31 @@ class _IPv6Constants:
++ # Not globally reachable address blocks listed on ++ # https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
++ _private_networks_exceptions = [ ++ IPv6Network('2001:1::1/128'), ++ IPv6Network('2001:1::2/128'), ++ IPv6Network('2001:3::/32'), ++ IPv6Network('2001:4:112::/48'), ++ IPv6Network('2001:20::/28'), ++ IPv6Network('2001:30::/28'), ++ ] ++
++ self.assertFalse(ipaddress.ip_address('64:ff9b:1::').is_global) ++ self.assertFalse(ipaddress.ip_address('2001::').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:1::1').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:1::2').is_global) ++ self.assertFalse(ipaddress.ip_address('2001:2::').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:3::').is_global) ++ self.assertFalse(ipaddress.ip_address('2001:4::').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:4:112::').is_global) ++ self.assertFalse(ipaddress.ip_address('2001:10::').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:20::').is_global) ++ self.assertTrue(ipaddress.ip_address('2001:30::').is_global) ++ self.assertFalse(ipaddress.ip_address('2001:40::').is_global) ++ self.assertFalse(ipaddress.ip_address('2002::').is_global) ++
some generic IETF reserved addresses
+* gh-122905: Sanitize names in zipfile.Path. (#122906)
+Ported from zipp 3.19.1; ref jaraco/zipp#119.
+(cherry picked from commit 9cd03263100ddb1657826cc4a71470786cab3932)
+* [3.11] gh-122905: Sanitize names in zipfile.Path. (GH-122906)
+Ported from zipp 3.19.1; ref jaraco/zippGH-119. +(cherry picked from commit 9cd03263100ddb1657826cc4a71470786cab3932)
+Co-authored-by: Jason R. Coombs jaraco@jaraco.com
+--- python3.11-3.11.2.orig/Lib/test/test_zipfile.py ++++ python3.11-3.11.2/Lib/test/test_zipfile.py +@@ -3544,6 +3544,23 @@ class EncodedMetadataTests(unittest.Test
++ def test_malformed_paths(self): ++ """ ++ Path should handle malformed paths. ++ """ ++ data = io.BytesIO() ++ zf = zipfile.ZipFile(data, "w") ++ zf.writestr("/one-slash.txt", b"content") ++ zf.writestr("//two-slash.txt", b"content") ++ zf.writestr("../parent.txt", b"content") ++ zf.filename = '' ++ root = zipfile.Path(zf) ++ assert list(map(str, root.iterdir())) == [ ++ 'one-slash.txt', ++ 'two-slash.txt', ++ 'parent.txt', ++ ] ++
+-class CompleteDirs(ZipFile): ++class SanitizedNames: ++ """ ++ ZipFile mix-in to ensure names are sanitized. ++ """ ++ ++ def namelist(self): ++ return list(map(self._sanitize, super().namelist())) ++ ++ @staticmethod ++ def _sanitize(name): ++ r""" ++ Ensure a relative path with posix separators and no dot names. ++ Modeled after ++ https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813 ++ but provides consistent cross-platform behavior. ++ >>> san = SanitizedNames._sanitize ++ >>> san('/foo/bar') ++ 'foo/bar' ++ >>> san('//foo.txt') ++ 'foo.txt' ++ >>> san('foo/.././bar.txt') ++ 'foo/bar.txt' ++ >>> san('foo../.bar.txt') ++ 'foo../.bar.txt' ++ >>> san('\foo\bar.txt') ++ 'foo/bar.txt' ++ >>> san('D:\foo.txt') ++ 'D/foo.txt' ++ >>> san('\\server\share\file.txt') ++ 'server/share/file.txt' ++ >>> san('\\?\GLOBALROOT\Volume3') ++ '?/GLOBALROOT/Volume3' ++ >>> san('\\.\PhysicalDrive1\root') ++ 'PhysicalDrive1/root' ++ Retain any trailing slash. ++ >>> san('abc/') ++ 'abc/' ++ Raises a ValueError if the result is empty. ++ >>> san('../..') ++ Traceback (most recent call last): ++ ... ++ ValueError: Empty filename ++ """ ++ ++ def allowed(part): ++ return part and part not in {'..', '.'} ++ ++ # Remove the drive letter. ++ # Don't use ntpath.splitdrive, because that also strips UNC paths ++ bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE) ++ clean = bare.replace('\', '/') ++ parts = clean.split('/') ++ joined = '/'.join(filter(allowed, parts)) ++ if not joined: ++ raise ValueError("Empty filename") ++ return joined + '/' * name.endswith('/') ++ ++ ++class CompleteDirs(SanitizedNames, ZipFile):