Skip to content

Commit 1988bdb

Browse files
committed
Merge remote-tracking branch 'upstream/3.10' into security-310-wrap-bio
2 parents 998f731 + c2bfbcd commit 1988bdb

46 files changed

Lines changed: 1474 additions & 657 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Doc/library/ssl.rst‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,12 @@ to speed up repeated connections from the same clients.
17501750
:class:`SSLContext` representing a certificate chain that matches the server
17511751
name.
17521752

1753+
If the callback assigns a new context to :attr:`SSLSocket.context`, any
1754+
further ClientHello message on the same connection (for example after a
1755+
TLS 1.3 HelloRetryRequest) is dispatched to the new context's
1756+
*sni_callback*, if it has one; the original callback is not called again
1757+
for that connection.
1758+
17531759
Due to the early negotiation phase of the TLS connection, only limited
17541760
methods and attributes are usable like
17551761
:meth:`SSLSocket.selected_alpn_protocol` and :attr:`SSLSocket.context`.
@@ -1773,6 +1779,11 @@ to speed up repeated connections from the same clients.
17731779

17741780
.. versionadded:: 3.7
17751781

1782+
.. versionchanged:: next
1783+
After the callback assigns a new :attr:`SSLSocket.context`, later
1784+
ClientHello messages on the connection are dispatched to the new
1785+
context's *sni_callback*.
1786+
17761787
.. attribute:: SSLContext.set_servername_callback(server_name_callback)
17771788

17781789
This is a legacy API retained for backwards compatibility. When possible,

‎Doc/library/urllib.request.rst‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,14 @@ These methods are available on :class:`HTTPPasswordMgr` and
922922

923923
*uri* can be either a single URI, or a sequence of URIs. *realm*, *user* and
924924
*passwd* must be strings. This causes ``(user, passwd)`` to be used as
925-
authentication tokens when authentication for *realm* and a super-URI of any of
926-
the given URIs is given.
925+
authentication tokens when authentication for *realm* and a super-URI of any
926+
of the given URIs is given. If a URI includes a scheme, its credentials only
927+
match authentication URIs with the same scheme or no scheme. A URI without a
928+
scheme matches authentication URIs with any scheme.
929+
930+
.. versionchanged:: next
931+
Authentication credentials for URIs with a scheme are now scoped by
932+
that scheme.
927933

928934

929935
.. method:: HTTPPasswordMgr.find_user_password(realm, authuri)

‎Lib/configparser.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,8 @@ class RawConfigParser(MutableMapping):
570570
_OPT_TMPL = r"""
571571
(?P<option> # very permissive!
572572
(?:(?!{delim})\S)* # non-delimiter non-whitespace
573-
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
573+
(?:(?:(?!{delim})\s)+ # optionally more
574+
(?:(?!{delim})\S)+)*) # space-separated words
574575
\s*(?P<vi>{delim})\s* # any number of space/tab,
575576
# followed by any of the
576577
# allowed delimiters,
@@ -580,7 +581,8 @@ class RawConfigParser(MutableMapping):
580581
_OPT_NV_TMPL = r"""
581582
(?P<option> # very permissive!
582583
(?:(?!{delim})\S)* # non-delimiter non-whitespace
583-
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
584+
(?:(?:(?!{delim})\s)+ # optionally more
585+
(?:(?!{delim})\S)+)*) # space-separated words
584586
\s*(?: # any number of space/tab,
585587
(?P<vi>{delim})\s* # optionally followed by
586588
# any of the allowed

‎Lib/stringprep.py‎

Lines changed: 315 additions & 148 deletions
Large diffs are not rendered by default.

‎Lib/tarfile.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2674,9 +2674,11 @@ def makelink_with_filter(self, tarinfo, targetpath,
26742674
"makelink_with_filter: if filter_function is not None, "
26752675
+ "extraction_root must also not be None")
26762676
try:
2677-
filter_function(
2677+
filtered = filter_function(
26782678
unfiltered.replace(name=tarinfo.name, deep=False),
26792679
extraction_root)
2680+
if filtered is None:
2681+
return
26802682
filtered = filter_function(unfiltered, extraction_root)
26812683
except _FILTER_ERRORS as cause:
26822684
raise LinkFallbackError(tarinfo, unfiltered.name) from cause

‎Lib/test/test_codecs.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,15 @@ def test_builtin_encode(self):
15471547
self.assertEqual("pyth\xf6n.org".encode("idna"), b"xn--pythn-mua.org")
15481548
self.assertEqual("pyth\xf6n.org.".encode("idna"), b"xn--pythn-mua.org.")
15491549

1550+
@support.subTests(['unicode', 'encoded'], [
1551+
('\N{CHEROKEE LETTER A}\N{CHEROKEE LETTER A}', b"xn--58da"),
1552+
('\N{GEORGIAN CAPITAL LETTER AN}.', b"xn--7md."),
1553+
('\N{CYRILLIC LETTER PALOCHKA}.example', b"xn--d5a.example"),
1554+
('\N{ROMAN NUMERAL REVERSED ONE HUNDRED}.example.', b"xn--q5g.example."),
1555+
])
1556+
def test_new_unicode_case_folding(self, unicode, encoded):
1557+
self.assertEqual(unicode.encode("idna"), encoded)
1558+
15501559
def test_builtin_decode_length_limit(self):
15511560
with self.assertRaisesRegex(UnicodeError, "too long"):
15521561
(b"xn--016c"+b"a"*1100).decode("idna")

‎Lib/test/test_configparser.py‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class CfgParserTestCaseClass:
4545
default_section = configparser.DEFAULTSECT
4646
interpolation = configparser._UNSET
4747

48-
def newconfig(self, defaults=None):
48+
def newconfig(self, defaults=None, **kwargs):
4949
arguments = dict(
5050
defaults=defaults,
5151
allow_no_value=self.allow_no_value,
@@ -58,6 +58,7 @@ def newconfig(self, defaults=None):
5858
default_section=self.default_section,
5959
interpolation=self.interpolation,
6060
)
61+
arguments.update(kwargs)
6162
instance = self.config_class(**arguments)
6263
return instance
6364

@@ -360,6 +361,32 @@ def test_basic(self):
360361
the larch {0[1]} 1
361362
""".format(self.delimiters)))
362363

364+
@support.subTests('data', [
365+
'foo bar=baz',
366+
'foo bar=baz',
367+
'foo=bar=baz',
368+
'foo = bar=baz',
369+
'foo\t \t=\t \tbar=baz',
370+
])
371+
def test_space_delimiter(self, data):
372+
# gh-156353: Space should be accepted as a delimiter
373+
cf = self.newconfig(delimiters=(' ', '='))
374+
cf.read_string(f"[all]\n{data}")
375+
self.assertEqual(cf.options('all'), ['foo'])
376+
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')
377+
378+
@support.subTests('delimiter', ' =:;#x\t\0\N{RS}\N{CEDILLA}\N{CAT}')
379+
@support.subTests('space_before', ['', ' ', '\t', ' \t'])
380+
@support.subTests('space_after', ['', ' ', '\t', ' \t'])
381+
def test_any_delimiter(self, delimiter, space_before, space_after):
382+
cf = self.newconfig(
383+
delimiters=(delimiter,),
384+
inline_comment_prefixes=None,
385+
)
386+
cf.read_string(f"[all]\nfoo{space_before}{delimiter}{space_after}bar=baz")
387+
self.assertEqual(cf.options('all'), ['foo'])
388+
self.assertEqual(cf.get('all', 'foo'), 'bar=baz')
389+
363390
def test_basic_from_dict(self):
364391
config = {
365392
"Foo Bar": {
@@ -1956,8 +1983,8 @@ class ConvertersTestCase(BasicTestCase, unittest.TestCase):
19561983

19571984
config_class = configparser.ConfigParser
19581985

1959-
def newconfig(self, defaults=None):
1960-
instance = super().newconfig(defaults=defaults)
1986+
def newconfig(self, defaults=None, **kwargs):
1987+
instance = super().newconfig(defaults=defaults, **kwargs)
19611988
instance.converters['list'] = lambda v: [e.strip() for e in v.split()
19621989
if e.strip()]
19631990
return instance

‎Lib/test/test_format.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,27 @@ def test_precision_c_limits(self):
488488
with self.assertRaises(ValueError) as cm:
489489
format(c, ".%sf" % (INT_MAX + 1))
490490

491+
@support.cpython_only
492+
def test_precision_near_int_max(self):
493+
# gh-158446: Precisions just below INT_MAX are rejected before any
494+
# output buffer size is computed from them.
495+
from _testcapi import INT_MAX
496+
497+
f = 1e300
498+
c = complex(f)
499+
for prec in (INT_MAX, INT_MAX - 1023):
500+
for code in "feg":
501+
spec = ".%d%s" % (prec, code)
502+
with self.subTest(spec=spec):
503+
with self.assertRaises(ValueError):
504+
format(f, spec)
505+
with self.assertRaises(ValueError):
506+
format(c, spec)
507+
with self.assertRaises(ValueError):
508+
("%" + spec) % f
509+
with self.assertRaises(ValueError):
510+
("%" + spec).encode() % f
511+
491512
def test_g_format_has_no_trailing_zeros(self):
492513
# regression test for bugs.python.org/issue40780
493514
self.assertEqual("%.3g" % 1505.0, "1.5e+03")

‎Lib/test/test_ssl.py‎

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,86 @@ def test_unwrap(self):
21032103
c_in.write(s_out.read())
21042104
client.unwrap()
21052105

2106+
def test_sni_callback_context_released_and_callback_raises(self):
2107+
# Variant of the test below without a HelloRetryRequest: the callback
2108+
# switches the connection to another context, drops the last
2109+
# references to the context that carries it, and raises. The C
2110+
# callback must not touch that context after the Python callback
2111+
# returned.
2112+
client_ctx, server_ctx, hostname = testing_context()
2113+
leaf_ctx = server_ctx
2114+
2115+
def sni_cb(sslobj, server_name, ctx):
2116+
sslobj.context = leaf_ctx
2117+
del ctx
2118+
raise LookupError("no certificate for " + repr(server_name))
2119+
2120+
def make_server():
2121+
dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
2122+
dispatch_ctx.load_cert_chain(SIGNED_CERTFILE)
2123+
dispatch_ctx.sni_callback = sni_cb
2124+
s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO()
2125+
server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True)
2126+
return server, s_in, s_out
2127+
2128+
server, s_in, s_out = make_server()
2129+
c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO()
2130+
client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname)
2131+
with self.assertRaises(ssl.SSLWantReadError):
2132+
client.do_handshake()
2133+
s_in.write(c_out.read())
2134+
with support.catch_unraisable_exception() as cm:
2135+
with self.assertRaises(ssl.SSLError):
2136+
server.do_handshake()
2137+
self.assertIsInstance(cm.unraisable.exc_value, LookupError)
2138+
self.assertIs(server.context, leaf_ctx)
2139+
2140+
def test_sni_callback_context_released_before_second_client_hello(self):
2141+
# The SSLContext carrying sni_callback may be released by the
2142+
# application once the callback has switched the connection over to
2143+
# another context. If the server then sends a HelloRetryRequest, the
2144+
# second ClientHello makes OpenSSL consult the original SSL_CTX's
2145+
# servername callback again; that must not use the deallocated
2146+
# SSLContext object.
2147+
client_ctx, leaf_ctx, hostname = testing_context()
2148+
calls = []
2149+
2150+
def make_server():
2151+
dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
2152+
dispatch_ctx.load_cert_chain(SIGNED_CERTFILE)
2153+
# Force a HelloRetryRequest: the client offers an X25519 key
2154+
# share first, the server only accepts P-384.
2155+
dispatch_ctx.set_ecdh_curve("secp384r1")
2156+
def sni_cb(sslobj, server_name, ctx):
2157+
calls.append(server_name)
2158+
sslobj.context = leaf_ctx
2159+
dispatch_ctx.sni_callback = sni_cb
2160+
s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO()
2161+
server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True)
2162+
return server, s_in, s_out, weakref.ref(dispatch_ctx)
2163+
2164+
# After this only the C-level SSL object references dispatch_ctx.
2165+
server, s_in, s_out, dispatch_ref = make_server()
2166+
c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO()
2167+
client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname)
2168+
for _ in range(10):
2169+
for obj, out, peer_in in ((client, c_out, s_in),
2170+
(server, s_out, c_in)):
2171+
try:
2172+
obj.do_handshake()
2173+
except ssl.SSLWantReadError:
2174+
pass
2175+
if out.pending:
2176+
peer_in.write(out.read())
2177+
client.do_handshake()
2178+
server.do_handshake()
2179+
support.gc_collect()
2180+
self.assertIsNone(dispatch_ref())
2181+
self.assertGreaterEqual(len(calls), 1)
2182+
self.assertEqual(calls[0], hostname)
2183+
self.assertIs(server.context, leaf_ctx)
2184+
self.assertIsNotNone(client.cipher())
2185+
21062186
class SimpleBackgroundTests(unittest.TestCase):
21072187
"""Tests that connect to a simple server running in the background"""
21082188

@@ -5082,15 +5162,27 @@ def non_linux_skip_if_other_okay_error(self, err):
50825162
return # Expect the full test setup to always work on Linux.
50835163
if (isinstance(err, ConnectionResetError) or
50845164
(isinstance(err, OSError) and err.errno == errno.EINVAL) or
5085-
re.search('wrong.version.number', getattr(err, "reason", ""), re.I)):
5165+
re.search(
5166+
# Matches the following error messages:
5167+
# '[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)'
5168+
# '[SSL: RECORD_LAYER_FAILURE] record layer failure (_ssl.c:1109)'
5169+
# '[SSL: HTTP_REQUEST] http request (_ssl.c:1143)'
5170+
r'wrong.version.number|record.layer.failure|http.request',
5171+
str(getattr(err, "reason", "")),
5172+
re.IGNORECASE,
5173+
)
5174+
):
50865175
# On Windows the TCP RST leads to a ConnectionResetError
50875176
# (ECONNRESET) which Linux doesn't appear to surface to userspace.
50885177
# If wrap_socket() winds up on the "if connected:" path and doing
5089-
# the actual wrapping... we get an SSLError from OpenSSL. Typically
5090-
# WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
5091-
# we're specifically trying to test. The way this test is written
5092-
# is known to work on Linux. We'll skip it anywhere else that it
5093-
# does not present as doing so.
5178+
# the actual wrapping... we get an SSLError from OpenSSL. This is
5179+
# typically WRONG_VERSION_NUMBER. The same happens on iOS, but
5180+
# RECORD_LAYER_FAILURE or HTTP_REQUEST is the error.
5181+
#
5182+
# While appropriate, these scenarios aren't what we're specifically
5183+
# trying to test. The way this test is written is known to work on
5184+
# Linux. We'll skip it anywhere else that it does not present as
5185+
# doing so.
50945186
try:
50955187
self.skipTest(f"Could not recreate conditions on {sys.platform}:"
50965188
f" {err=}")

‎Lib/test/test_tarfile.py‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3977,9 +3977,15 @@ def test_sneaky_hardlink_fallback(self):
39773977
for filter in 'tar', 'fully_trusted':
39783978
with self.subTest(filter), self.check_context(arc.open(), filter):
39793979
if not os_helper.can_symlink():
3980-
self.expect_file("a/t/dummy")
3981-
self.expect_file("b/")
3982-
self.expect_file("c/")
3980+
if filter == 'fully_trusted' or sys.platform == "win32":
3981+
self.expect_file("a/t/dummy")
3982+
self.expect_file("b/")
3983+
self.expect_file("c/")
3984+
else:
3985+
self.expect_exception(
3986+
tarfile.LinkFallbackError,
3987+
"link 'boom' would be extracted as a copy of "
3988+
+ "'c/escape', which was rejected")
39833989
else:
39843990
self.expect_file("a/t/dummy")
39853991
self.expect_file("b/")
@@ -4170,6 +4176,24 @@ def testing_filter(member, path):
41704176
if sys.platform != 'win32':
41714177
self.assertFalse(path.stat().st_mode & stat.S_IWUSR)
41724178

4179+
def test_extract_filters_target_none(self):
4180+
# Test that when extract() falls back to extracting (rather than
4181+
# linking) a hardlink target, the member is skipped if the filter
4182+
# returns None.
4183+
with ArchiveMaker() as arc:
4184+
arc.add('a/b/s', symlink_to='../escape')
4185+
arc.add('q', hardlink_to='a/b/s')
4186+
def filter_unsafe_members(member, path):
4187+
try:
4188+
return tarfile.data_filter(member, path)
4189+
except tarfile.FilterError as error:
4190+
return None
4191+
with self.check_context(arc.open(), filter_unsafe_members):
4192+
if os_helper.can_symlink():
4193+
self.expect_file('a/b/s', symlink_to='../escape')
4194+
else:
4195+
self.expect_file('a/b/') # symlink is not extracted
4196+
41734197
def test_link_fallback_normalizes(self):
41744198
# Make sure hardlink fallbacks work for non-normalized paths for all
41754199
# filters

0 commit comments

Comments
 (0)