Compare commits
10 Commits
b36af39639
...
f2daa992e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2daa992e9 | ||
|
|
566cb3e5c1 | ||
|
|
ed4eb3373d | ||
|
|
89007601cc | ||
|
|
dded90a5bc | ||
|
|
5bc71843bd | ||
|
|
3a962a5013 | ||
|
|
6386f10883 | ||
|
|
131ee7867c | ||
|
|
039b4734e6 |
24
0001-expected_algs-list-to-include-TLS_SM4.patch
Normal file
24
0001-expected_algs-list-to-include-TLS_SM4.patch
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
From 273a81e603e97af08186d697023182723aa2d69d Mon Sep 17 00:00:00 2001
|
||||||
|
From: wangshuo <wangshuo@kylinos.cn>
|
||||||
|
Date: Wed, 11 Dec 2024 01:15:08 +0800
|
||||||
|
Subject: [PATCH] expected_algs list to include TLS_SM4
|
||||||
|
|
||||||
|
---
|
||||||
|
Lib/test/test_ssl.py | 1 +
|
||||||
|
1 file changed, 1 insertion(+)
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
|
||||||
|
index d95f0ef..775fc80 100644
|
||||||
|
--- a/Lib/test/test_ssl.py
|
||||||
|
+++ b/Lib/test/test_ssl.py
|
||||||
|
@@ -4305,6 +4305,7 @@ class ThreadedTests(unittest.TestCase):
|
||||||
|
"AES256", "AES-256",
|
||||||
|
# TLS 1.3 ciphers are always enabled
|
||||||
|
"TLS_CHACHA20", "TLS_AES",
|
||||||
|
+ "TLS_SM4",
|
||||||
|
]
|
||||||
|
|
||||||
|
stats = server_params_test(client_context, server_context,
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@ -0,0 +1,302 @@
|
|||||||
|
From 42deeab5b2efc2930d4eb73416e1dde9cf790dd2 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Petr Viktorin <encukou@gmail.com>
|
||||||
|
Date: Tue, 22 Aug 2023 20:28:10 +0200
|
||||||
|
Subject: [PATCH] [3.9] gh-107845: Fix symlink handling for tarfile.data_filter
|
||||||
|
(GH-107846) (#108274)
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
(cherry picked from commit acbd3f9c5c5f23e95267714e41236140d84fe962)
|
||||||
|
|
||||||
|
Co-authored-by: Petr Viktorin <encukou@gmail.com>
|
||||||
|
Co-authored-by: Victor Stinner <vstinner@python.org>
|
||||||
|
Co-authored-by: Lumír 'Frenzy' Balhar <frenzy.madness@gmail.com>
|
||||||
|
---
|
||||||
|
Doc/library/tarfile.rst | 5 +
|
||||||
|
Lib/tarfile.py | 11 +-
|
||||||
|
Lib/test/test_tarfile.py | 144 +++++++++++++++++-
|
||||||
|
...-08-10-17-36-22.gh-issue-107845.dABiMJ.rst | 3 +
|
||||||
|
4 files changed, 154 insertions(+), 9 deletions(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2023-08-10-17-36-22.gh-issue-107845.dABiMJ.rst
|
||||||
|
|
||||||
|
diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst
|
||||||
|
index f1d9bd34536..165088529a8 100644
|
||||||
|
--- a/Doc/library/tarfile.rst
|
||||||
|
+++ b/Doc/library/tarfile.rst
|
||||||
|
@@ -732,6 +732,11 @@ A ``TarInfo`` object has the following public data attributes:
|
||||||
|
Name of the target file name, which is only present in :class:`TarInfo` objects
|
||||||
|
of type :const:`LNKTYPE` and :const:`SYMTYPE`.
|
||||||
|
|
||||||
|
+ For symbolic links (``SYMTYPE``), the *linkname* is relative to the directory
|
||||||
|
+ that contains the link.
|
||||||
|
+ For hard links (``LNKTYPE``), the *linkname* is relative to the root of
|
||||||
|
+ the archive.
|
||||||
|
+
|
||||||
|
|
||||||
|
.. attribute:: TarInfo.uid
|
||||||
|
:type: int
|
||||||
|
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
|
||||||
|
index b6ad7dbe2a4..7a6158c2eb9 100755
|
||||||
|
--- a/Lib/tarfile.py
|
||||||
|
+++ b/Lib/tarfile.py
|
||||||
|
@@ -740,7 +740,7 @@ class SpecialFileError(FilterError):
|
||||||
|
class AbsoluteLinkError(FilterError):
|
||||||
|
def __init__(self, tarinfo):
|
||||||
|
self.tarinfo = tarinfo
|
||||||
|
- super().__init__(f'{tarinfo.name!r} is a symlink to an absolute path')
|
||||||
|
+ super().__init__(f'{tarinfo.name!r} is a link to an absolute path')
|
||||||
|
|
||||||
|
class LinkOutsideDestinationError(FilterError):
|
||||||
|
def __init__(self, tarinfo, path):
|
||||||
|
@@ -800,7 +800,14 @@ def _get_filtered_attrs(member, dest_path, for_data=True):
|
||||||
|
if member.islnk() or member.issym():
|
||||||
|
if os.path.isabs(member.linkname):
|
||||||
|
raise AbsoluteLinkError(member)
|
||||||
|
- target_path = os.path.realpath(os.path.join(dest_path, member.linkname))
|
||||||
|
+ if member.issym():
|
||||||
|
+ target_path = os.path.join(dest_path,
|
||||||
|
+ os.path.dirname(name),
|
||||||
|
+ member.linkname)
|
||||||
|
+ else:
|
||||||
|
+ target_path = os.path.join(dest_path,
|
||||||
|
+ member.linkname)
|
||||||
|
+ target_path = os.path.realpath(target_path)
|
||||||
|
if os.path.commonpath([target_path, dest_path]) != dest_path:
|
||||||
|
raise LinkOutsideDestinationError(member, target_path)
|
||||||
|
return new_attrs
|
||||||
|
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
|
||||||
|
index a66f7efd2d6..3df64c78032 100644
|
||||||
|
--- a/Lib/test/test_tarfile.py
|
||||||
|
+++ b/Lib/test/test_tarfile.py
|
||||||
|
@@ -3169,10 +3169,12 @@ class ArchiveMaker:
|
||||||
|
self.bio = None
|
||||||
|
|
||||||
|
def add(self, name, *, type=None, symlink_to=None, hardlink_to=None,
|
||||||
|
- mode=None, **kwargs):
|
||||||
|
+ mode=None, size=None, **kwargs):
|
||||||
|
"""Add a member to the test archive. Call within `with`."""
|
||||||
|
name = str(name)
|
||||||
|
tarinfo = tarfile.TarInfo(name).replace(**kwargs)
|
||||||
|
+ if size is not None:
|
||||||
|
+ tarinfo.size = size
|
||||||
|
if mode:
|
||||||
|
tarinfo.mode = _filemode_to_int(mode)
|
||||||
|
if symlink_to is not None:
|
||||||
|
@@ -3236,7 +3238,8 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
raise self.raised_exception
|
||||||
|
self.assertEqual(self.expected_paths, set())
|
||||||
|
|
||||||
|
- def expect_file(self, name, type=None, symlink_to=None, mode=None):
|
||||||
|
+ def expect_file(self, name, type=None, symlink_to=None, mode=None,
|
||||||
|
+ size=None):
|
||||||
|
"""Check a single file. See check_context."""
|
||||||
|
if self.raised_exception:
|
||||||
|
raise self.raised_exception
|
||||||
|
@@ -3270,6 +3273,8 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
self.assertTrue(path.is_fifo())
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(type)
|
||||||
|
+ if size is not None:
|
||||||
|
+ self.assertEqual(path.stat().st_size, size)
|
||||||
|
for parent in path.parents:
|
||||||
|
self.expected_paths.discard(parent)
|
||||||
|
|
||||||
|
@@ -3315,8 +3320,15 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
# Test interplaying symlinks
|
||||||
|
# Inspired by 'dirsymlink2a' in jwilk/traversal-archives
|
||||||
|
with ArchiveMaker() as arc:
|
||||||
|
+
|
||||||
|
+ # `current` links to `.` which is both:
|
||||||
|
+ # - the destination directory
|
||||||
|
+ # - `current` itself
|
||||||
|
arc.add('current', symlink_to='.')
|
||||||
|
+
|
||||||
|
+ # effectively points to ./../
|
||||||
|
arc.add('parent', symlink_to='current/..')
|
||||||
|
+
|
||||||
|
arc.add('parent/evil')
|
||||||
|
|
||||||
|
if support.can_symlink():
|
||||||
|
@@ -3357,9 +3369,46 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
def test_parent_symlink2(self):
|
||||||
|
# Test interplaying symlinks
|
||||||
|
# Inspired by 'dirsymlink2b' in jwilk/traversal-archives
|
||||||
|
+
|
||||||
|
+ # Posix and Windows have different pathname resolution:
|
||||||
|
+ # either symlink or a '..' component resolve first.
|
||||||
|
+ # Let's see which we are on.
|
||||||
|
+ if support.can_symlink():
|
||||||
|
+ testpath = os.path.join(TEMPDIR, 'resolution_test')
|
||||||
|
+ os.mkdir(testpath)
|
||||||
|
+
|
||||||
|
+ # testpath/current links to `.` which is all of:
|
||||||
|
+ # - `testpath`
|
||||||
|
+ # - `testpath/current`
|
||||||
|
+ # - `testpath/current/current`
|
||||||
|
+ # - etc.
|
||||||
|
+ os.symlink('.', os.path.join(testpath, 'current'))
|
||||||
|
+
|
||||||
|
+ # we'll test where `testpath/current/../file` ends up
|
||||||
|
+ with open(os.path.join(testpath, 'current', '..', 'file'), 'w'):
|
||||||
|
+ pass
|
||||||
|
+
|
||||||
|
+ if os.path.exists(os.path.join(testpath, 'file')):
|
||||||
|
+ # Windows collapses 'current\..' to '.' first, leaving
|
||||||
|
+ # 'testpath\file'
|
||||||
|
+ dotdot_resolves_early = True
|
||||||
|
+ elif os.path.exists(os.path.join(testpath, '..', 'file')):
|
||||||
|
+ # Posix resolves 'current' to '.' first, leaving
|
||||||
|
+ # 'testpath/../file'
|
||||||
|
+ dotdot_resolves_early = False
|
||||||
|
+ else:
|
||||||
|
+ raise AssertionError('Could not determine link resolution')
|
||||||
|
+
|
||||||
|
with ArchiveMaker() as arc:
|
||||||
|
+
|
||||||
|
+ # `current` links to `.` which is both the destination directory
|
||||||
|
+ # and `current` itself
|
||||||
|
arc.add('current', symlink_to='.')
|
||||||
|
+
|
||||||
|
+ # `current/parent` is also available as `./parent`,
|
||||||
|
+ # and effectively points to `./../`
|
||||||
|
arc.add('current/parent', symlink_to='..')
|
||||||
|
+
|
||||||
|
arc.add('parent/evil')
|
||||||
|
|
||||||
|
with self.check_context(arc.open(), 'fully_trusted'):
|
||||||
|
@@ -3373,6 +3422,7 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
|
||||||
|
with self.check_context(arc.open(), 'tar'):
|
||||||
|
if support.can_symlink():
|
||||||
|
+ # Fail when extracting a file outside destination
|
||||||
|
self.expect_exception(
|
||||||
|
tarfile.OutsideDestinationError,
|
||||||
|
"'parent/evil' would be extracted to "
|
||||||
|
@@ -3383,10 +3433,24 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
self.expect_file('parent/evil')
|
||||||
|
|
||||||
|
with self.check_context(arc.open(), 'data'):
|
||||||
|
- self.expect_exception(
|
||||||
|
- tarfile.LinkOutsideDestinationError,
|
||||||
|
- """'current/parent' would link to ['"].*['"], """
|
||||||
|
- + "which is outside the destination")
|
||||||
|
+ if support.can_symlink():
|
||||||
|
+ if dotdot_resolves_early:
|
||||||
|
+ # Fail when extracting a file outside destination
|
||||||
|
+ self.expect_exception(
|
||||||
|
+ tarfile.OutsideDestinationError,
|
||||||
|
+ "'parent/evil' would be extracted to "
|
||||||
|
+ + """['"].*evil['"], which is outside """
|
||||||
|
+ + "the destination")
|
||||||
|
+ else:
|
||||||
|
+ # Fail as soon as we have a symlink outside the destination
|
||||||
|
+ self.expect_exception(
|
||||||
|
+ tarfile.LinkOutsideDestinationError,
|
||||||
|
+ "'current/parent' would link to "
|
||||||
|
+ + """['"].*outerdir['"], which is outside """
|
||||||
|
+ + "the destination")
|
||||||
|
+ else:
|
||||||
|
+ self.expect_file('current/')
|
||||||
|
+ self.expect_file('parent/evil')
|
||||||
|
|
||||||
|
def test_absolute_symlink(self):
|
||||||
|
# Test symlink to an absolute path
|
||||||
|
@@ -3415,11 +3479,29 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
with self.check_context(arc.open(), 'data'):
|
||||||
|
self.expect_exception(
|
||||||
|
tarfile.AbsoluteLinkError,
|
||||||
|
- "'parent' is a symlink to an absolute path")
|
||||||
|
+ "'parent' is a link to an absolute path")
|
||||||
|
+
|
||||||
|
+ def test_absolute_hardlink(self):
|
||||||
|
+ # Test hardlink to an absolute path
|
||||||
|
+ # Inspired by 'dirsymlink' in https://github.com/jwilk/traversal-archives
|
||||||
|
+ with ArchiveMaker() as arc:
|
||||||
|
+ arc.add('parent', hardlink_to=self.outerdir / 'foo')
|
||||||
|
+
|
||||||
|
+ with self.check_context(arc.open(), 'fully_trusted'):
|
||||||
|
+ self.expect_exception(KeyError, ".*foo. not found")
|
||||||
|
+
|
||||||
|
+ with self.check_context(arc.open(), 'tar'):
|
||||||
|
+ self.expect_exception(KeyError, ".*foo. not found")
|
||||||
|
+
|
||||||
|
+ with self.check_context(arc.open(), 'data'):
|
||||||
|
+ self.expect_exception(
|
||||||
|
+ tarfile.AbsoluteLinkError,
|
||||||
|
+ "'parent' is a link to an absolute path")
|
||||||
|
|
||||||
|
def test_sly_relative0(self):
|
||||||
|
# Inspired by 'relative0' in jwilk/traversal-archives
|
||||||
|
with ArchiveMaker() as arc:
|
||||||
|
+ # points to `../../tmp/moo`
|
||||||
|
arc.add('../moo', symlink_to='..//tmp/moo')
|
||||||
|
|
||||||
|
try:
|
||||||
|
@@ -3469,6 +3551,54 @@ class TestExtractionFilters(unittest.TestCase):
|
||||||
|
+ """['"].*moo['"], which is outside the """
|
||||||
|
+ "destination")
|
||||||
|
|
||||||
|
+ def test_deep_symlink(self):
|
||||||
|
+ # Test that symlinks and hardlinks inside a directory
|
||||||
|
+ # point to the correct file (`target` of size 3).
|
||||||
|
+ # If links aren't supported we get a copy of the file.
|
||||||
|
+ with ArchiveMaker() as arc:
|
||||||
|
+ arc.add('targetdir/target', size=3)
|
||||||
|
+ # a hardlink's linkname is relative to the archive
|
||||||
|
+ arc.add('linkdir/hardlink', hardlink_to=os.path.join(
|
||||||
|
+ 'targetdir', 'target'))
|
||||||
|
+ # a symlink's linkname is relative to the link's directory
|
||||||
|
+ arc.add('linkdir/symlink', symlink_to=os.path.join(
|
||||||
|
+ '..', 'targetdir', 'target'))
|
||||||
|
+
|
||||||
|
+ for filter in 'tar', 'data', 'fully_trusted':
|
||||||
|
+ with self.check_context(arc.open(), filter):
|
||||||
|
+ self.expect_file('targetdir/target', size=3)
|
||||||
|
+ self.expect_file('linkdir/hardlink', size=3)
|
||||||
|
+ if support.can_symlink():
|
||||||
|
+ self.expect_file('linkdir/symlink', size=3,
|
||||||
|
+ symlink_to='../targetdir/target')
|
||||||
|
+ else:
|
||||||
|
+ self.expect_file('linkdir/symlink', size=3)
|
||||||
|
+
|
||||||
|
+ def test_chains(self):
|
||||||
|
+ # Test chaining of symlinks/hardlinks.
|
||||||
|
+ # Symlinks are created before the files they point to.
|
||||||
|
+ with ArchiveMaker() as arc:
|
||||||
|
+ arc.add('linkdir/symlink', symlink_to='hardlink')
|
||||||
|
+ arc.add('symlink2', symlink_to=os.path.join(
|
||||||
|
+ 'linkdir', 'hardlink2'))
|
||||||
|
+ arc.add('targetdir/target', size=3)
|
||||||
|
+ arc.add('linkdir/hardlink', hardlink_to='targetdir/target')
|
||||||
|
+ arc.add('linkdir/hardlink2', hardlink_to='linkdir/symlink')
|
||||||
|
+
|
||||||
|
+ for filter in 'tar', 'data', 'fully_trusted':
|
||||||
|
+ with self.check_context(arc.open(), filter):
|
||||||
|
+ self.expect_file('targetdir/target', size=3)
|
||||||
|
+ self.expect_file('linkdir/hardlink', size=3)
|
||||||
|
+ self.expect_file('linkdir/hardlink2', size=3)
|
||||||
|
+ if support.can_symlink():
|
||||||
|
+ self.expect_file('linkdir/symlink', size=3,
|
||||||
|
+ symlink_to='hardlink')
|
||||||
|
+ self.expect_file('symlink2', size=3,
|
||||||
|
+ symlink_to='linkdir/hardlink2')
|
||||||
|
+ else:
|
||||||
|
+ self.expect_file('linkdir/symlink', size=3)
|
||||||
|
+ self.expect_file('symlink2', size=3)
|
||||||
|
+
|
||||||
|
def test_modes(self):
|
||||||
|
# Test how file modes are extracted
|
||||||
|
# (Note that the modes are ignored on platforms without working chmod)
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2023-08-10-17-36-22.gh-issue-107845.dABiMJ.rst b/Misc/NEWS.d/next/Library/2023-08-10-17-36-22.gh-issue-107845.dABiMJ.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..32c1fb93f4a
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2023-08-10-17-36-22.gh-issue-107845.dABiMJ.rst
|
||||||
|
@@ -0,0 +1,3 @@
|
||||||
|
+:func:`tarfile.data_filter` now takes the location of symlinks into account
|
||||||
|
+when determining their target, so it will no longer reject some valid
|
||||||
|
+tarballs with ``LinkOutsideDestinationError``.
|
||||||
|
--
|
||||||
|
2.25.1
|
||||||
|
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
From ddca2953191c67a12b1f19d6bca41016c6ae7132 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Victor Stinner <vstinner@python.org>
|
||||||
|
Date: Mon, 2 Dec 2024 13:36:46 +0100
|
||||||
|
Subject: [PATCH] [3.9] gh-103848: Adds checks to ensure that bracketed hosts
|
||||||
|
found by urlsplit are of IPv6 or IPvFuture format (#103849) (#126976)
|
||||||
|
|
||||||
|
Co-authored-by: Gregory P. Smith <greg@krypto.org>
|
||||||
|
(cherry picked from commit 29f348e232e82938ba2165843c448c2b291504c5)
|
||||||
|
|
||||||
|
Co-authored-by: JohnJamesUtley <81572567+JohnJamesUtley@users.noreply.github.com>
|
||||||
|
---
|
||||||
|
Lib/test/test_urlparse.py | 26 +++++++++++++++++++
|
||||||
|
Lib/urllib/parse.py | 16 +++++++++++-
|
||||||
|
...-04-26-09-54-25.gh-issue-103848.aDSnpR.rst | 2 ++
|
||||||
|
3 files changed, 43 insertions(+), 1 deletion(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
|
||||||
|
index b862711e414..6f7d40c2125 100644
|
||||||
|
--- a/Lib/test/test_urlparse.py
|
||||||
|
+++ b/Lib/test/test_urlparse.py
|
||||||
|
@@ -1135,6 +1135,32 @@ class UrlParseTestCase(unittest.TestCase):
|
||||||
|
self.assertEqual(p2.scheme, 'tel')
|
||||||
|
self.assertEqual(p2.path, '+31641044153')
|
||||||
|
|
||||||
|
+ def test_invalid_bracketed_hosts(self):
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[192.0.2.146]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[important.com:8000]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123r.IP]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v12ae]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v.IP]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v123.]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[v]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
|
||||||
|
+
|
||||||
|
+ def test_splitting_bracketed_hosts(self):
|
||||||
|
+ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
|
||||||
|
+ self.assertEqual(p1.hostname, 'v6a.ip')
|
||||||
|
+ self.assertEqual(p1.username, 'user')
|
||||||
|
+ self.assertEqual(p1.path, '/path')
|
||||||
|
+ p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
|
||||||
|
+ self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
|
||||||
|
+ self.assertEqual(p2.username, 'user')
|
||||||
|
+ self.assertEqual(p2.path, '/path')
|
||||||
|
+ p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
|
||||||
|
+ self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
|
||||||
|
+ self.assertEqual(p3.username, 'user')
|
||||||
|
+ self.assertEqual(p3.path, '/path')
|
||||||
|
+
|
||||||
|
def test_port_casting_failure_message(self):
|
||||||
|
message = "Port could not be cast to integer value as 'oracle'"
|
||||||
|
p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')
|
||||||
|
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
|
||||||
|
index 4a24f7eb5e4..9d37dcaa904 100644
|
||||||
|
--- a/Lib/urllib/parse.py
|
||||||
|
+++ b/Lib/urllib/parse.py
|
||||||
|
@@ -36,6 +36,7 @@ import sys
|
||||||
|
import types
|
||||||
|
import collections
|
||||||
|
import warnings
|
||||||
|
+import ipaddress
|
||||||
|
|
||||||
|
__all__ = ["urlparse", "urlunparse", "urljoin", "urldefrag",
|
||||||
|
"urlsplit", "urlunsplit", "urlencode", "parse_qs",
|
||||||
|
@@ -442,6 +443,17 @@ def _checknetloc(netloc):
|
||||||
|
raise ValueError("netloc '" + netloc + "' contains invalid " +
|
||||||
|
"characters under NFKC normalization")
|
||||||
|
|
||||||
|
+# Valid bracketed hosts are defined in
|
||||||
|
+# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
|
||||||
|
+def _check_bracketed_host(hostname):
|
||||||
|
+ if hostname.startswith('v'):
|
||||||
|
+ if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", hostname):
|
||||||
|
+ raise ValueError(f"IPvFuture address is invalid")
|
||||||
|
+ else:
|
||||||
|
+ ip = ipaddress.ip_address(hostname) # Throws Value Error if not IPv6 or IPv4
|
||||||
|
+ if isinstance(ip, ipaddress.IPv4Address):
|
||||||
|
+ raise ValueError(f"An IPv4 address cannot be in brackets")
|
||||||
|
+
|
||||||
|
def urlsplit(url, scheme='', allow_fragments=True):
|
||||||
|
"""Parse a URL into 5 components:
|
||||||
|
<scheme>://<netloc>/<path>?<query>#<fragment>
|
||||||
|
@@ -488,12 +500,14 @@ def urlsplit(url, scheme='', allow_fragments=True):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
scheme, url = url[:i].lower(), url[i+1:]
|
||||||
|
-
|
||||||
|
if url[:2] == '//':
|
||||||
|
netloc, url = _splitnetloc(url, 2)
|
||||||
|
if (('[' in netloc and ']' not in netloc) or
|
||||||
|
(']' in netloc and '[' not in netloc)):
|
||||||
|
raise ValueError("Invalid IPv6 URL")
|
||||||
|
+ if '[' in netloc and ']' in netloc:
|
||||||
|
+ bracketed_host = netloc.partition('[')[2].partition(']')[0]
|
||||||
|
+ _check_bracketed_host(bracketed_host)
|
||||||
|
if allow_fragments and '#' in url:
|
||||||
|
url, fragment = url.split('#', 1)
|
||||||
|
if '?' in url:
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000..81e5904aa6c
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2023-04-26-09-54-25.gh-issue-103848.aDSnpR.rst
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+Add checks to ensure that ``[`` bracketed ``]`` hosts found by
|
||||||
|
+:func:`urllib.parse.urlsplit` are of IPv6 or IPvFuture format.
|
||||||
|
--
|
||||||
|
2.33.0
|
||||||
|
|
||||||
297
backport-CVE-2024-9287.patch
Normal file
297
backport-CVE-2024-9287.patch
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
From 633555735a023d3e4d92ba31da35b1205f9ecbd7 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Victor Stinner <vstinner@python.org>
|
||||||
|
Date: Mon, 4 Nov 2024 16:16:35 +0100
|
||||||
|
Subject: [PATCH] [3.9] gh-124651: Quote template strings in `venv` activation
|
||||||
|
scripts (GH-124712) (GH-126185) (GH-126269) (GH-126301)
|
||||||
|
|
||||||
|
(cherry picked from commit ae961ae94bf19c8f8c7fbea3d1c25cc55ce8ae97)
|
||||||
|
---
|
||||||
|
Lib/test/test_venv.py | 81 +++++++++++++++++++
|
||||||
|
Lib/venv/__init__.py | 42 ++++++++--
|
||||||
|
Lib/venv/scripts/common/activate | 6 +-
|
||||||
|
Lib/venv/scripts/nt/activate.bat | 4 +-
|
||||||
|
Lib/venv/scripts/posix/activate.csh | 6 +-
|
||||||
|
Lib/venv/scripts/posix/activate.fish | 6 +-
|
||||||
|
...-09-28-02-03-04.gh-issue-124651.bLBGtH.rst | 1 +
|
||||||
|
7 files changed, 130 insertions(+), 16 deletions(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
|
||||||
|
index 480cb29f35a6a4..871b8314b90b05 100644
|
||||||
|
--- a/Lib/test/test_venv.py
|
||||||
|
+++ b/Lib/test/test_venv.py
|
||||||
|
@@ -14,6 +14,7 @@
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
+import shlex
|
||||||
|
from test.support import (captured_stdout, captured_stderr, requires_zlib,
|
||||||
|
can_symlink, EnvironmentVarGuard, rmtree,
|
||||||
|
import_module,
|
||||||
|
@@ -85,6 +86,10 @@ def get_text_file_contents(self, *args, encoding='utf-8'):
|
||||||
|
result = f.read()
|
||||||
|
return result
|
||||||
|
|
||||||
|
+ def assertEndsWith(self, string, tail):
|
||||||
|
+ if not string.endswith(tail):
|
||||||
|
+ self.fail(f"String {string!r} does not end with {tail!r}")
|
||||||
|
+
|
||||||
|
class BasicTest(BaseTest):
|
||||||
|
"""Test venv module functionality."""
|
||||||
|
|
||||||
|
@@ -342,6 +347,82 @@ def test_executable_symlinks(self):
|
||||||
|
'import sys; print(sys.executable)'])
|
||||||
|
self.assertEqual(out.strip(), envpy.encode())
|
||||||
|
|
||||||
|
+ # gh-124651: test quoted strings
|
||||||
|
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||||
|
+ def test_special_chars_bash(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly (bash)
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ bash = shutil.which('bash')
|
||||||
|
+ if bash is None:
|
||||||
|
+ self.skipTest('bash required for this test')
|
||||||
|
+ env_name = '"\';&&$e|\'"'
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate')
|
||||||
|
+ test_script = os.path.join(self.env_dir, 'test_special_chars.sh')
|
||||||
|
+ with open(test_script, "w") as f:
|
||||||
|
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||||
|
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||||
|
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||||
|
+ 'deactivate\n')
|
||||||
|
+ out, err = check_output([bash, test_script])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
+ # gh-124651: test quoted strings
|
||||||
|
+ @unittest.skipIf(os.name == 'nt', 'contains invalid characters on Windows')
|
||||||
|
+ def test_special_chars_csh(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly (csh)
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ csh = shutil.which('tcsh') or shutil.which('csh')
|
||||||
|
+ if csh is None:
|
||||||
|
+ self.skipTest('csh required for this test')
|
||||||
|
+ env_name = '"\';&&$e|\'"'
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate.csh')
|
||||||
|
+ test_script = os.path.join(self.env_dir, 'test_special_chars.csh')
|
||||||
|
+ with open(test_script, "w") as f:
|
||||||
|
+ f.write(f'source {shlex.quote(activate)}\n'
|
||||||
|
+ 'python -c \'import sys; print(sys.executable)\'\n'
|
||||||
|
+ 'python -c \'import os; print(os.environ["VIRTUAL_ENV"])\'\n'
|
||||||
|
+ 'deactivate\n')
|
||||||
|
+ out, err = check_output([csh, test_script])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
+ # gh-124651: test quoted strings on Windows
|
||||||
|
+ @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||||
|
+ def test_special_chars_windows(self):
|
||||||
|
+ """
|
||||||
|
+ Test that the template strings are quoted properly on Windows
|
||||||
|
+ """
|
||||||
|
+ rmtree(self.env_dir)
|
||||||
|
+ env_name = "'&&^$e"
|
||||||
|
+ env_dir = os.path.join(os.path.realpath(self.env_dir), env_name)
|
||||||
|
+ builder = venv.EnvBuilder(clear=True)
|
||||||
|
+ builder.create(env_dir)
|
||||||
|
+ activate = os.path.join(env_dir, self.bindir, 'activate.bat')
|
||||||
|
+ test_batch = os.path.join(self.env_dir, 'test_special_chars.bat')
|
||||||
|
+ with open(test_batch, "w") as f:
|
||||||
|
+ f.write('@echo off\n'
|
||||||
|
+ f'"{activate}" & '
|
||||||
|
+ f'{self.exe} -c "import sys; print(sys.executable)" & '
|
||||||
|
+ f'{self.exe} -c "import os; print(os.environ[\'VIRTUAL_ENV\'])" & '
|
||||||
|
+ 'deactivate')
|
||||||
|
+ out, err = check_output([test_batch])
|
||||||
|
+ lines = out.splitlines()
|
||||||
|
+ self.assertTrue(env_name.encode() in lines[0])
|
||||||
|
+ self.assertEndsWith(lines[1], env_name.encode())
|
||||||
|
+
|
||||||
|
@unittest.skipUnless(os.name == 'nt', 'only relevant on Windows')
|
||||||
|
def test_unicode_in_batch_file(self):
|
||||||
|
"""
|
||||||
|
diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py
|
||||||
|
index 6f1af294ae63e3..299633117e6fbe 100644
|
||||||
|
--- a/Lib/venv/__init__.py
|
||||||
|
+++ b/Lib/venv/__init__.py
|
||||||
|
@@ -11,6 +11,7 @@
|
||||||
|
import sys
|
||||||
|
import sysconfig
|
||||||
|
import types
|
||||||
|
+import shlex
|
||||||
|
|
||||||
|
|
||||||
|
CORE_VENV_DEPS = ('pip', 'setuptools')
|
||||||
|
@@ -348,11 +349,41 @@ def replace_variables(self, text, context):
|
||||||
|
:param context: The information for the environment creation request
|
||||||
|
being processed.
|
||||||
|
"""
|
||||||
|
- text = text.replace('__VENV_DIR__', context.env_dir)
|
||||||
|
- text = text.replace('__VENV_NAME__', context.env_name)
|
||||||
|
- text = text.replace('__VENV_PROMPT__', context.prompt)
|
||||||
|
- text = text.replace('__VENV_BIN_NAME__', context.bin_name)
|
||||||
|
- text = text.replace('__VENV_PYTHON__', context.env_exe)
|
||||||
|
+ replacements = {
|
||||||
|
+ '__VENV_DIR__': context.env_dir,
|
||||||
|
+ '__VENV_NAME__': context.env_name,
|
||||||
|
+ '__VENV_PROMPT__': context.prompt,
|
||||||
|
+ '__VENV_BIN_NAME__': context.bin_name,
|
||||||
|
+ '__VENV_PYTHON__': context.env_exe,
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ def quote_ps1(s):
|
||||||
|
+ """
|
||||||
|
+ This should satisfy PowerShell quoting rules [1], unless the quoted
|
||||||
|
+ string is passed directly to Windows native commands [2].
|
||||||
|
+ [1]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules
|
||||||
|
+ [2]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing#passing-arguments-that-contain-quote-characters
|
||||||
|
+ """
|
||||||
|
+ s = s.replace("'", "''")
|
||||||
|
+ return f"'{s}'"
|
||||||
|
+
|
||||||
|
+ def quote_bat(s):
|
||||||
|
+ return s
|
||||||
|
+
|
||||||
|
+ # gh-124651: need to quote the template strings properly
|
||||||
|
+ quote = shlex.quote
|
||||||
|
+ script_path = context.script_path
|
||||||
|
+ if script_path.endswith('.ps1'):
|
||||||
|
+ quote = quote_ps1
|
||||||
|
+ elif script_path.endswith('.bat'):
|
||||||
|
+ quote = quote_bat
|
||||||
|
+ else:
|
||||||
|
+ # fallbacks to POSIX shell compliant quote
|
||||||
|
+ quote = shlex.quote
|
||||||
|
+
|
||||||
|
+ replacements = {key: quote(s) for key, s in replacements.items()}
|
||||||
|
+ for key, quoted in replacements.items():
|
||||||
|
+ text = text.replace(key, quoted)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def install_scripts(self, context, path):
|
||||||
|
@@ -392,6 +423,7 @@ def install_scripts(self, context, path):
|
||||||
|
with open(srcfile, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
if not srcfile.endswith(('.exe', '.pdb')):
|
||||||
|
+ context.script_path = srcfile
|
||||||
|
try:
|
||||||
|
data = data.decode('utf-8')
|
||||||
|
data = self.replace_variables(data, context)
|
||||||
|
diff --git a/Lib/venv/scripts/common/activate b/Lib/venv/scripts/common/activate
|
||||||
|
index 45af3536aa191d..1d116ca6eda4ed 100644
|
||||||
|
--- a/Lib/venv/scripts/common/activate
|
||||||
|
+++ b/Lib/venv/scripts/common/activate
|
||||||
|
@@ -37,11 +37,11 @@ deactivate () {
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-VIRTUAL_ENV="__VENV_DIR__"
|
||||||
|
+VIRTUAL_ENV=__VENV_DIR__
|
||||||
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
-PATH="$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||||
|
+PATH="$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
@@ -54,7 +54,7 @@ fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
- PS1="__VENV_PROMPT__${PS1:-}"
|
||||||
|
+ PS1=__VENV_PROMPT__"${PS1:-}"
|
||||||
|
export PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat
|
||||||
|
index af4c7e0abacb1c..5ca475a6e81879 100644
|
||||||
|
--- a/Lib/venv/scripts/nt/activate.bat
|
||||||
|
+++ b/Lib/venv/scripts/nt/activate.bat
|
||||||
|
@@ -8,7 +8,7 @@
|
||||||
|
"%SystemRoot%\System32\chcp.com" 65001 > nul
|
||||||
|
)
|
||||||
|
|
||||||
|
-set VIRTUAL_ENV=__VENV_DIR__
|
||||||
|
+set "VIRTUAL_ENV=__VENV_DIR__"
|
||||||
|
|
||||||
|
if not defined PROMPT set PROMPT=$P$G
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@
|
||||||
|
if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
|
||||||
|
if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%
|
||||||
|
|
||||||
|
-set PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%
|
||||||
|
+set "PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%"
|
||||||
|
|
||||||
|
:END
|
||||||
|
if defined _OLD_CODEPAGE (
|
||||||
|
diff --git a/Lib/venv/scripts/posix/activate.csh b/Lib/venv/scripts/posix/activate.csh
|
||||||
|
index 68a0dc74e1a3c7..51301139517f10 100644
|
||||||
|
--- a/Lib/venv/scripts/posix/activate.csh
|
||||||
|
+++ b/Lib/venv/scripts/posix/activate.csh
|
||||||
|
@@ -8,16 +8,16 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-setenv VIRTUAL_ENV "__VENV_DIR__"
|
||||||
|
+setenv VIRTUAL_ENV __VENV_DIR__
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
-setenv PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__:$PATH"
|
||||||
|
+setenv PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__":$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
- set prompt = "__VENV_PROMPT__$prompt"
|
||||||
|
+ set prompt = __VENV_PROMPT__"$prompt"
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
|
||||||
|
index 54b9ea5676b66b..62ab5312d6121b 100644
|
||||||
|
--- a/Lib/venv/scripts/posix/activate.fish
|
||||||
|
+++ b/Lib/venv/scripts/posix/activate.fish
|
||||||
|
@@ -29,10 +29,10 @@ end
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
-set -gx VIRTUAL_ENV "__VENV_DIR__"
|
||||||
|
+set -gx VIRTUAL_ENV __VENV_DIR__
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
-set -gx PATH "$VIRTUAL_ENV/__VENV_BIN_NAME__" $PATH
|
||||||
|
+set -gx PATH "$VIRTUAL_ENV/"__VENV_BIN_NAME__ $PATH
|
||||||
|
|
||||||
|
# Unset PYTHONHOME if set.
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
@@ -52,7 +52,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||||
|
- printf "%s%s%s" (set_color 4B8BBE) "__VENV_PROMPT__" (set_color normal)
|
||||||
|
+ printf "%s%s%s" (set_color 4B8BBE) __VENV_PROMPT__ (set_color normal)
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
diff --git a/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000000..17fc9171390dd9
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Library/2024-09-28-02-03-04.gh-issue-124651.bLBGtH.rst
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+Properly quote template strings in :mod:`venv` activation scripts.
|
||||||
137
backport-CVE-2025-0938.patch
Normal file
137
backport-CVE-2025-0938.patch
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
From a7084f6075c9595ba60119ce8c62f1496f50c568 Mon Sep 17 00:00:00 2001
|
||||||
|
From: "Miss Islington (bot)"
|
||||||
|
<31488909+miss-islington@users.noreply.github.com>
|
||||||
|
Date: Sun, 2 Feb 2025 09:30:28 +0100
|
||||||
|
Subject: [PATCH] [3.12] gh-105704: Disallow square brackets (`[` and `]`) in
|
||||||
|
domain names for parsed URLs (GH-129418) (GH-129527)
|
||||||
|
|
||||||
|
gh-105704: Disallow square brackets (`[` and `]`) in domain names for parsed URLs (GH-129418)
|
||||||
|
|
||||||
|
* gh-105704: Disallow square brackets ( and ) in domain names for parsed URLs
|
||||||
|
|
||||||
|
* Use Sphinx references
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Add mismatched bracket test cases, fix news format
|
||||||
|
|
||||||
|
* Add more test coverage for ports
|
||||||
|
|
||||||
|
---------
|
||||||
|
|
||||||
|
(cherry picked from commit d89a5f6a6e65511a5f6e0618c4c30a7aa5aba56a)
|
||||||
|
|
||||||
|
Co-authored-by: Seth Michael Larson <seth@python.org>
|
||||||
|
Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
|
||||||
|
---
|
||||||
|
Lib/test/test_urlparse.py | 37 ++++++++++++++++++-
|
||||||
|
Lib/urllib/parse.py | 20 +++++++++-
|
||||||
|
...-01-28-14-08-03.gh-issue-105704.EnhHxu.rst | 4 ++
|
||||||
|
3 files changed, 58 insertions(+), 3 deletions(-)
|
||||||
|
create mode 100644 Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
|
||||||
|
|
||||||
|
diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py
|
||||||
|
index 818e7e93dbbe11..5e429b9259fee7 100644
|
||||||
|
--- a/Lib/test/test_urlparse.py
|
||||||
|
+++ b/Lib/test/test_urlparse.py
|
||||||
|
@@ -1273,16 +1273,51 @@ def test_invalid_bracketed_hosts(self):
|
||||||
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af::2309::fae7:1234]/Path?Query')
|
||||||
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@[0439:23af:2309::fae7:1234:2342:438e:192.0.2.146]/Path?Query')
|
||||||
|
self.assertRaises(ValueError, urllib.parse.urlsplit, 'Scheme://user@]v6a.ip[/Path')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]/')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix/')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip]?')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip].suffix?')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]/')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix/')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]?')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix?')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:a1')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:a1')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:1a')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:1a')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[::1].suffix:/')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[::1]:?')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@prefix.[v6a.ip]')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://user@[v6a.ip].suffix')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://[v6a.ip')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip]')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip[')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://]v6a.ip')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix.[v6a.ip')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip].suffix')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip[suffix')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://prefix]v6a.ip')
|
||||||
|
+ self.assertRaises(ValueError, urllib.parse.urlsplit, 'scheme://v6a.ip[suffix')
|
||||||
|
|
||||||
|
def test_splitting_bracketed_hosts(self):
|
||||||
|
- p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]/path?query')
|
||||||
|
+ p1 = urllib.parse.urlsplit('scheme://user@[v6a.ip]:1234/path?query')
|
||||||
|
self.assertEqual(p1.hostname, 'v6a.ip')
|
||||||
|
self.assertEqual(p1.username, 'user')
|
||||||
|
self.assertEqual(p1.path, '/path')
|
||||||
|
+ self.assertEqual(p1.port, 1234)
|
||||||
|
p2 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7%test]/path?query')
|
||||||
|
self.assertEqual(p2.hostname, '0439:23af:2309::fae7%test')
|
||||||
|
self.assertEqual(p2.username, 'user')
|
||||||
|
self.assertEqual(p2.path, '/path')
|
||||||
|
+ self.assertIs(p2.port, None)
|
||||||
|
p3 = urllib.parse.urlsplit('scheme://user@[0439:23af:2309::fae7:1234:192.0.2.146%test]/path?query')
|
||||||
|
self.assertEqual(p3.hostname, '0439:23af:2309::fae7:1234:192.0.2.146%test')
|
||||||
|
self.assertEqual(p3.username, 'user')
|
||||||
|
diff --git a/Lib/urllib/parse.py b/Lib/urllib/parse.py
|
||||||
|
index 24815952037fef..c72138a33ca6d4 100644
|
||||||
|
--- a/Lib/urllib/parse.py
|
||||||
|
+++ b/Lib/urllib/parse.py
|
||||||
|
@@ -436,6 +436,23 @@ def _checknetloc(netloc):
|
||||||
|
raise ValueError("netloc '" + netloc + "' contains invalid " +
|
||||||
|
"characters under NFKC normalization")
|
||||||
|
|
||||||
|
+def _check_bracketed_netloc(netloc):
|
||||||
|
+ # Note that this function must mirror the splitting
|
||||||
|
+ # done in NetlocResultMixins._hostinfo().
|
||||||
|
+ hostname_and_port = netloc.rpartition('@')[2]
|
||||||
|
+ before_bracket, have_open_br, bracketed = hostname_and_port.partition('[')
|
||||||
|
+ if have_open_br:
|
||||||
|
+ # No data is allowed before a bracket.
|
||||||
|
+ if before_bracket:
|
||||||
|
+ raise ValueError("Invalid IPv6 URL")
|
||||||
|
+ hostname, _, port = bracketed.partition(']')
|
||||||
|
+ # No data is allowed after the bracket but before the port delimiter.
|
||||||
|
+ if port and not port.startswith(":"):
|
||||||
|
+ raise ValueError("Invalid IPv6 URL")
|
||||||
|
+ else:
|
||||||
|
+ hostname, _, port = hostname_and_port.partition(':')
|
||||||
|
+ _check_bracketed_host(hostname)
|
||||||
|
+
|
||||||
|
# Valid bracketed hosts are defined in
|
||||||
|
# https://www.rfc-editor.org/rfc/rfc3986#page-49 and https://url.spec.whatwg.org/
|
||||||
|
def _check_bracketed_host(hostname):
|
||||||
|
@@ -496,8 +513,7 @@ def urlsplit(url, scheme='', allow_fragments=True):
|
||||||
|
(']' in netloc and '[' not in netloc)):
|
||||||
|
raise ValueError("Invalid IPv6 URL")
|
||||||
|
if '[' in netloc and ']' in netloc:
|
||||||
|
- bracketed_host = netloc.partition('[')[2].partition(']')[0]
|
||||||
|
- _check_bracketed_host(bracketed_host)
|
||||||
|
+ _check_bracketed_netloc(netloc)
|
||||||
|
if allow_fragments and '#' in url:
|
||||||
|
url, fragment = url.split('#', 1)
|
||||||
|
if '?' in url:
|
||||||
|
diff --git a/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst b/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000000000..bff1bc6b0d609c
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/Misc/NEWS.d/next/Security/2025-01-28-14-08-03.gh-issue-105704.EnhHxu.rst
|
||||||
|
@@ -0,0 +1,4 @@
|
||||||
|
+When using :func:`urllib.parse.urlsplit` and :func:`urllib.parse.urlparse` host
|
||||||
|
+parsing would not reject domain names containing square brackets (``[`` and
|
||||||
|
+``]``). Square brackets are only valid for IPv6 and IPvFuture hosts according to
|
||||||
|
+`RFC 3986 Section 3.2.2 <https://www.rfc-editor.org/rfc/rfc3986#section-3.2.2>`__.
|
||||||
133
python3.spec
133
python3.spec
@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language
|
|||||||
URL: https://www.python.org/
|
URL: https://www.python.org/
|
||||||
|
|
||||||
Version: 3.9.9
|
Version: 3.9.9
|
||||||
Release: 36
|
Release: 41
|
||||||
License: Python-2.0
|
License: Python-2.0
|
||||||
|
|
||||||
%global branchversion 3.9
|
%global branchversion 3.9
|
||||||
@ -130,12 +130,17 @@ Patch6032: backport-CVE-2024-3219-1-gh-122133-Authenticate-socket-connection-for
|
|||||||
Patch6033: backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-to.patch
|
Patch6033: backport-CVE-2024-3219-2-gh-122133-Rework-pure-Python-socketpair-tests-to.patch
|
||||||
Patch6034: backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch
|
Patch6034: backport-CVE-2023-6597-gh-91133-tempfile.TemporaryDirectory-fix-symlink.patch
|
||||||
Patch6035: backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch
|
Patch6035: backport-CVE-2024-0450-gh-109858-Protect-zipfile-from-quoted-overlap-zi.patch
|
||||||
|
Patch6036: backport-3.9-gh-107845-Fix-symlink-handling-for-tarfile.data_.patch
|
||||||
|
Patch6037: backport-CVE-2024-9287.patch
|
||||||
|
Patch6038: backport-CVE-2024-11168-3.9-gh-103848-Adds-checks-to-ensure-that-bracketed-h.patch
|
||||||
|
Patch6039: backport-CVE-2025-0938.patch
|
||||||
|
|
||||||
Patch9000: add-the-sm3-method-for-obtaining-the-salt-value.patch
|
Patch9000: add-the-sm3-method-for-obtaining-the-salt-value.patch
|
||||||
Patch9001: python3-Add-sw64-architecture.patch
|
Patch9001: python3-Add-sw64-architecture.patch
|
||||||
Patch9002: Add-loongarch-support.patch
|
Patch9002: Add-loongarch-support.patch
|
||||||
Patch9003: avoid-usage-of-md5-in-multiprocessing.patch
|
Patch9003: avoid-usage-of-md5-in-multiprocessing.patch
|
||||||
Patch9004: update-openssl-version-for-test-case.patch
|
Patch9004: update-openssl-version-for-test-case.patch
|
||||||
|
Patch9005: 0001-expected_algs-list-to-include-TLS_SM4.patch
|
||||||
|
|
||||||
Provides: python%{branchversion} = %{version}-%{release}
|
Provides: python%{branchversion} = %{version}-%{release}
|
||||||
Provides: python(abi) = %{branchversion}
|
Provides: python(abi) = %{branchversion}
|
||||||
@ -216,51 +221,56 @@ extension modules.
|
|||||||
find -name '*.exe' -print -delete
|
find -name '*.exe' -print -delete
|
||||||
rm -r Modules/expat
|
rm -r Modules/expat
|
||||||
|
|
||||||
%patch1 -p1
|
%patch -P1 -p1
|
||||||
%patch111 -p1
|
%patch -P111 -p1
|
||||||
%patch251 -p1
|
%patch -P251 -p1
|
||||||
%patch6000 -p1
|
%patch -P6000 -p1
|
||||||
%patch6001 -p1
|
%patch -P6001 -p1
|
||||||
%patch6002 -p1
|
%patch -P6002 -p1
|
||||||
%patch6003 -p1
|
%patch -P6003 -p1
|
||||||
%patch6004 -p1
|
%patch -P6004 -p1
|
||||||
%patch6005 -p1
|
%patch -P6005 -p1
|
||||||
%patch6006 -p1
|
%patch -P6006 -p1
|
||||||
%patch6007 -p1
|
%patch -P6007 -p1
|
||||||
%patch6008 -p1
|
%patch -P6008 -p1
|
||||||
%patch6009 -p1
|
%patch -P6009 -p1
|
||||||
%patch6010 -p1
|
%patch -P6010 -p1
|
||||||
%patch6011 -p1
|
%patch -P6011 -p1
|
||||||
%patch6012 -p1
|
%patch -P6012 -p1
|
||||||
%patch6013 -p1
|
%patch -P6013 -p1
|
||||||
%patch6014 -p1
|
%patch -P6014 -p1
|
||||||
%patch6015 -p1
|
%patch -P6015 -p1
|
||||||
%patch6016 -p1
|
%patch -P6016 -p1
|
||||||
%patch6017 -p1
|
%patch -P6017 -p1
|
||||||
%patch6018 -p1
|
%patch -P6018 -p1
|
||||||
%patch6019 -p1
|
%patch -P6019 -p1
|
||||||
%patch6020 -p1
|
%patch -P6020 -p1
|
||||||
%patch6021 -p1
|
%patch -P6021 -p1
|
||||||
%patch6022 -p1
|
%patch -P6022 -p1
|
||||||
%patch6023 -p1
|
%patch -P6023 -p1
|
||||||
%patch6024 -p1
|
%patch -P6024 -p1
|
||||||
%patch6025 -p1
|
%patch -P6025 -p1
|
||||||
%patch6026 -p1
|
%patch -P6026 -p1
|
||||||
%patch6027 -p1
|
%patch -P6027 -p1
|
||||||
%patch6028 -p1
|
%patch -P6028 -p1
|
||||||
%patch6029 -p1
|
%patch -P6029 -p1
|
||||||
%patch6030 -p1
|
%patch -P6030 -p1
|
||||||
%patch6031 -p1
|
%patch -P6031 -p1
|
||||||
%patch6032 -p1
|
%patch -P6032 -p1
|
||||||
%patch6033 -p1
|
%patch -P6033 -p1
|
||||||
%patch6034 -p1
|
%patch -P6034 -p1
|
||||||
%patch6035 -p1
|
%patch -P6035 -p1
|
||||||
|
%patch -P6036 -p1
|
||||||
|
%patch -P6037 -p1
|
||||||
|
%patch -P6038 -p1
|
||||||
|
|
||||||
%patch9000 -p1
|
%patch -P9000 -p1
|
||||||
%patch9001 -p1
|
%patch -P9001 -p1
|
||||||
%patch9002 -p1
|
%patch -P9002 -p1
|
||||||
%patch9003 -p1
|
%patch -P9003 -p1
|
||||||
%patch9004 -p1
|
%patch -P9004 -p1
|
||||||
|
%patch -P9005 -p1
|
||||||
|
%patch -P6039 -p1
|
||||||
|
|
||||||
rm Lib/ensurepip/_bundled/*.whl
|
rm Lib/ensurepip/_bundled/*.whl
|
||||||
rm configure pyconfig.h.in
|
rm configure pyconfig.h.in
|
||||||
@ -883,6 +893,39 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP"
|
|||||||
%{_mandir}/*/*
|
%{_mandir}/*/*
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Feb 11 2025 Funda Wang <fundawang@yeah.net> - 3.9.9-41
|
||||||
|
- Type:CVE
|
||||||
|
- CVE:CVE-2025-0938
|
||||||
|
- SUG:NA
|
||||||
|
- DESC:fix CVE-2025-0938
|
||||||
|
|
||||||
|
* Wed Dec 25 2024 xinsheng <xinsheng3@huawei.com> - 3.9.9-40
|
||||||
|
- Type:CVE
|
||||||
|
- CVE:CVE-2024-11168
|
||||||
|
- SUG:NA
|
||||||
|
- DESC:fix CVE-2024-11168
|
||||||
|
- Adds checks to ensure that bracketed hosts found by urlsplit are of IPv6 or IPvFuture format
|
||||||
|
|
||||||
|
* Wed Dec 11 2024 wangshuo <wangshuo@kylinos.cn> - 3.9.9-39
|
||||||
|
- Type:update
|
||||||
|
- CVE:NA
|
||||||
|
- SUG:NA
|
||||||
|
- DESC:support TLS_SM4
|
||||||
|
|
||||||
|
* Tue Nov 12 2024 Funda Wang <fundawang@yeah.net> - 3.9.9-38
|
||||||
|
- Type:CVE
|
||||||
|
- CVE:CVE-2024-9287
|
||||||
|
- SUG:NA
|
||||||
|
- DESC:fix CVE-2024-9287
|
||||||
|
- Quote template strings in venv activation scripts
|
||||||
|
|
||||||
|
* Tue Oct 29 2024 wangshuo <wangshuo@kylinos.cn> - 3.9.9-37
|
||||||
|
- Type:bugfix
|
||||||
|
- ID:NA
|
||||||
|
- SUG:NA
|
||||||
|
- DESC:backport upstream patch, fix the bug introduced by the fix for CVE-2007-4559
|
||||||
|
- gh-107845: Fix symlink handling for tarfile.data_filter
|
||||||
|
|
||||||
* Tue Sep 24 2024 xinsheng <xinsheng3@huawei.com> - 3.9.9-36
|
* Tue Sep 24 2024 xinsheng <xinsheng3@huawei.com> - 3.9.9-36
|
||||||
- Type:CVE
|
- Type:CVE
|
||||||
- CVE:CVE-2024-6232,CVE-2024-3219,CVE-2024-0450,CVE-2023-6597,CVE-2024-4032
|
- CVE:CVE-2024-6232,CVE-2024-3219,CVE-2024-0450,CVE-2023-6597,CVE-2024-4032
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user