Compare commits
10 Commits
a7762d83a9
...
caebf6a75a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caebf6a75a | ||
|
|
fdd4347c4b | ||
|
|
7f1b1a2665 | ||
|
|
e9a34e198f | ||
|
|
e9d921e1e8 | ||
|
|
25d18d0e7d | ||
|
|
c9709da6e2 | ||
|
|
5f32441d05 | ||
|
|
358ea4752a | ||
|
|
8d29a3ae78 |
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 e9125a4a29e9c615a2562446f07c1b1ffaa6061f Mon Sep 17 00:00:00 2001
|
||||
From: wangshuo <wangshuo@kylinos.cn>
|
||||
Date: Wed, 11 Dec 2024 01:05:25 +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 50734f6..0db51bc 100644
|
||||
--- a/Lib/test/test_ssl.py
|
||||
+++ b/Lib/test/test_ssl.py
|
||||
@@ -4222,6 +4222,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
|
||||
|
||||
61
Add-loongarch-support.patch
Normal file
61
Add-loongarch-support.patch
Normal file
@ -0,0 +1,61 @@
|
||||
From b87dad459825a407084c9acde88f42d86139715e Mon Sep 17 00:00:00 2001
|
||||
From: GuoCe <guoce@kylinos.cn>
|
||||
Date: Wed, 6 Mar 2024 18:17:32 +0800
|
||||
Subject: [PATCH] Add loongarch support
|
||||
|
||||
---
|
||||
config.guess | 3 +++
|
||||
config.sub | 2 ++
|
||||
configure.ac | 2 ++
|
||||
3 files changed, 7 insertions(+)
|
||||
|
||||
diff --git a/config.guess b/config.guess
|
||||
index 256083a..33fafea 100755
|
||||
--- a/config.guess
|
||||
+++ b/config.guess
|
||||
@@ -970,6 +970,9 @@ EOF
|
||||
m68*:Linux:*:*)
|
||||
echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
|
||||
exit ;;
|
||||
+ loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*)
|
||||
+ echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"
|
||||
+ exit ;;
|
||||
mips:Linux:*:* | mips64:Linux:*:*)
|
||||
eval "$set_cc_for_build"
|
||||
sed 's/^ //' << EOF > "$dummy.c"
|
||||
diff --git a/config.sub b/config.sub
|
||||
index ba37cf9..d971b78 100755
|
||||
--- a/config.sub
|
||||
+++ b/config.sub
|
||||
@@ -265,6 +265,7 @@ case $basic_machine in
|
||||
| k1om \
|
||||
| le32 | le64 \
|
||||
| lm32 \
|
||||
+ | loongarch32 | loongarch64 | loongarchx32 \
|
||||
| m32c | m32r | m32rle | m68000 | m68k | m88k \
|
||||
| maxq | mb | microblaze | microblazeel | mcore | mep | metag \
|
||||
| mips | mipsbe | mipseb | mipsel | mipsle \
|
||||
@@ -394,6 +395,7 @@ case $basic_machine in
|
||||
| k1om-* \
|
||||
| le32-* | le64-* \
|
||||
| lm32-* \
|
||||
+ | loongarch32 | loongarch64 | loongarchx32 \
|
||||
| m32c-* | m32r-* | m32rle-* \
|
||||
| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
|
||||
| m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index c2e9fbb..b83fdcf 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -779,6 +779,8 @@ cat >> conftest.c <<EOF
|
||||
hppa-linux-gnu
|
||||
# elif defined(__ia64__)
|
||||
ia64-linux-gnu
|
||||
+# elif defined(__loongarch64)
|
||||
+ loongarch64-linux-gnu
|
||||
# elif defined(__m68k__) && !defined(__mcoldfire__)
|
||||
m68k-linux-gnu
|
||||
# elif defined(__mips_hard_float) && defined(__mips_isa_rev) && (__mips_isa_rev >=6) && defined(_MIPSEL)
|
||||
--
|
||||
2.27.0
|
||||
|
||||
2469
backport-3.7-gh-102950-Implement-PEP-706-Filter-for-tarfile.e.patch
Normal file
2469
backport-3.7-gh-102950-Implement-PEP-706-Filter-for-tarfile.e.patch
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,64 @@
|
||||
From 4e2dd0c3626649224b87b757a292959d94152a00 Mon Sep 17 00:00:00 2001
|
||||
From: "Miss Islington (bot)"
|
||||
<31488909+miss-islington@users.noreply.github.com>
|
||||
Date: Fri, 26 May 2023 23:41:46 -0700
|
||||
Subject: [PATCH] [3.7] gh-104049: do not expose on-disk location from
|
||||
SimpleHTTPRequestHandler (GH-104122)
|
||||
|
||||
Do not expose the local server's on-disk location from `SimpleHTTPRequestHandler` when generating a directory index. (unnecessary information disclosure)
|
||||
|
||||
(cherry picked from commit c7c3a60c88de61a79ded9fdaf6bc6a29da4efb9a)
|
||||
|
||||
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
|
||||
Co-authored-by: Gregory P. Smith <greg@krypto.org>
|
||||
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
|
||||
---
|
||||
Lib/http/server.py | 2 +-
|
||||
Lib/test/test_httpservers.py | 8 ++++++++
|
||||
.../2023-05-01-15-03-25.gh-issue-104049.b01Y3g.rst | 2 ++
|
||||
3 files changed, 11 insertions(+), 1 deletion(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Security/2023-05-01-15-03-25.gh-issue-104049.b01Y3g.rst
|
||||
|
||||
diff --git a/Lib/http/server.py b/Lib/http/server.py
|
||||
index ba2acbc98bf..beabe3de7ab 100644
|
||||
--- a/Lib/http/server.py
|
||||
+++ b/Lib/http/server.py
|
||||
@@ -777,7 +777,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
displaypath = urllib.parse.unquote(self.path,
|
||||
errors='surrogatepass')
|
||||
except UnicodeDecodeError:
|
||||
- displaypath = urllib.parse.unquote(path)
|
||||
+ displaypath = urllib.parse.unquote(self.path)
|
||||
displaypath = html.escape(displaypath, quote=False)
|
||||
enc = sys.getfilesystemencoding()
|
||||
title = 'Directory listing for %s' % displaypath
|
||||
diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py
|
||||
index b3e15c475a4..8c9be689003 100644
|
||||
--- a/Lib/test/test_httpservers.py
|
||||
+++ b/Lib/test/test_httpservers.py
|
||||
@@ -413,6 +413,14 @@ class SimpleHTTPServerTestCase(BaseTestCase):
|
||||
self.check_status_and_reason(response, HTTPStatus.OK,
|
||||
data=support.TESTFN_UNDECODABLE)
|
||||
|
||||
+ def test_undecodable_parameter(self):
|
||||
+ # sanity check using a valid parameter
|
||||
+ response = self.request(self.base_url + '/?x=123').read()
|
||||
+ self.assertRegex(response, f'listing for {self.base_url}/\?x=123'.encode('latin1'))
|
||||
+ # now the bogus encoding
|
||||
+ response = self.request(self.base_url + '/?x=%bb').read()
|
||||
+ self.assertRegex(response, f'listing for {self.base_url}/\?x=\xef\xbf\xbd'.encode('latin1'))
|
||||
+
|
||||
def test_get_dir_redirect_location_domain_injection_bug(self):
|
||||
"""Ensure //evil.co/..%2f../../X does not put //evil.co/ in Location.
|
||||
|
||||
diff --git a/Misc/NEWS.d/next/Security/2023-05-01-15-03-25.gh-issue-104049.b01Y3g.rst b/Misc/NEWS.d/next/Security/2023-05-01-15-03-25.gh-issue-104049.b01Y3g.rst
|
||||
new file mode 100644
|
||||
index 00000000000..969deb26bfe
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Security/2023-05-01-15-03-25.gh-issue-104049.b01Y3g.rst
|
||||
@@ -0,0 +1,2 @@
|
||||
+Do not expose the local on-disk location in directory indexes
|
||||
+produced by :class:`http.client.SimpleHTTPRequestHandler`.
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -0,0 +1,308 @@
|
||||
From 4e32d16aa771abb1787e5e9faecb0bec0d639e3c Mon Sep 17 00:00:00 2001
|
||||
From: wangshuo <wangshuo@kylinos.cn>
|
||||
Date: Thu, 24 Oct 2024 18:25:51 +0800
|
||||
Subject: [PATCH 2/3] [3.7] gh-107845: Fix symlink handling for
|
||||
tarfile.data_filter (GH-107846)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
(cherry picked from commit acbd3f9)
|
||||
https://github.com/python/cpython/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>
|
||||
|
||||
Refer to:
|
||||
https://github.com/python/cpython/issues/107845
|
||||
https://github.com/encukou/cpython/commit/63556bccc21ef6726ad7bc5769c2dbb08cf5910f
|
||||
https://github.com/encukou/cpython/commit/8e15c2e44cbdbd48522db678ab2519a50f9d41b1
|
||||
---
|
||||
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 3f544c3..950e00d 100644
|
||||
--- a/Doc/library/tarfile.rst
|
||||
+++ b/Doc/library/tarfile.rst
|
||||
@@ -715,6 +715,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
|
||||
|
||||
diff --git a/Lib/tarfile.py b/Lib/tarfile.py
|
||||
index 71c5112..9a8d2dd 100755
|
||||
--- a/Lib/tarfile.py
|
||||
+++ b/Lib/tarfile.py
|
||||
@@ -750,7 +750,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):
|
||||
@@ -810,7 +810,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 f1aed04..a3db33d 100644
|
||||
--- a/Lib/test/test_tarfile.py
|
||||
+++ b/Lib/test/test_tarfile.py
|
||||
@@ -2984,10 +2984,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:
|
||||
@@ -3060,7 +3062,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
|
||||
@@ -3094,6 +3097,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)
|
||||
|
||||
@@ -3139,8 +3144,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():
|
||||
@@ -3181,9 +3193,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'):
|
||||
@@ -3197,6 +3246,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 "
|
||||
@@ -3207,10 +3257,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
|
||||
@@ -3239,11 +3303,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:
|
||||
@@ -3293,6 +3375,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 0000000..32c1fb9
|
||||
--- /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.33.0
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
From b6a790412ccacd9b90486fdb86e29f2e49c8fa6c Mon Sep 17 00:00:00 2001
|
||||
From: wangshuo <wangshuo@kylinos.cn>
|
||||
Date: Fri, 25 Oct 2024 10:13:37 +0800
|
||||
Subject: [PATCH 3/3] [3.7] gh-115133: Fix test_xml_etree error with expat
|
||||
versions that fix CVE-2023-52425
|
||||
|
||||
Feeding the parser by too small chunks defers parsing to prevent CVE-2023-52425.
|
||||
According to the upstream solution, chunk_size=22 is the smallest value
|
||||
that can pass the tests.
|
||||
|
||||
See https://github.com/python/cpython/issues/115133
|
||||
---
|
||||
Lib/test/test_xml_etree.py | 4 +++-
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
|
||||
index 5ba0de8..7b225ad 100644
|
||||
--- a/Lib/test/test_xml_etree.py
|
||||
+++ b/Lib/test/test_xml_etree.py
|
||||
@@ -1060,7 +1060,9 @@ class XMLPullParserTest(unittest.TestCase):
|
||||
expected)
|
||||
|
||||
def test_simple_xml(self):
|
||||
- for chunk_size in (None, 1, 5):
|
||||
+ # Feeding the parser by too small chunks defers parsing to prevent CVE-2023-52425.
|
||||
+ # See https://github.com/python/cpython/issues/115133
|
||||
+ for chunk_size in (None, 22, 25):
|
||||
with self.subTest(chunk_size=chunk_size):
|
||||
parser = ET.XMLPullParser()
|
||||
self.assert_event_tags(parser, [])
|
||||
--
|
||||
2.33.0
|
||||
|
||||
300
backport-3.7-gh-124651-CVE-2024-9287.patch
Normal file
300
backport-3.7-gh-124651-CVE-2024-9287.patch
Normal file
@ -0,0 +1,300 @@
|
||||
From c2ca86f086de75333051bff50b0e7d886302553f Mon Sep 17 00:00:00 2001
|
||||
From: Victor Stinner <vstinner@python.org>
|
||||
Date: Wed, 19 Dec 2024 11:23:56 +0800
|
||||
Subject: [PATCH] [3.7] gh-124651: Quote template strings in `venv` activation
|
||||
scripts (GH-124712) (GH-126185) (GH-126269) (GH-126301)
|
||||
|
||||
Fix CVE-2024-9287, backported to version 3.7 by wangshuo@kylinos.cn
|
||||
|
||||
Refer to:
|
||||
https://github.com/python/cpython/issues/124651
|
||||
https://github.com/python/cpython/pull/126301
|
||||
https://github.com/python/cpython/commit/633555735a023d3e4d92ba31da35b1205f9ecbd7
|
||||
---
|
||||
Lib/test/test_venv.py | 81 +++++++++++++++++++
|
||||
Lib/venv/__init__.py | 42 ++++++++--
|
||||
Lib/venv/scripts/common/activate | 8 +-
|
||||
Lib/venv/scripts/nt/activate.bat | 4 +-
|
||||
Lib/venv/scripts/posix/activate.csh | 4 +-
|
||||
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 a1fc675..2945907 100644
|
||||
--- a/Lib/test/test_venv.py
|
||||
+++ b/Lib/test/test_venv.py
|
||||
@@ -14,6 +14,7 @@ import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
+import shlex
|
||||
from test.support import (captured_stdout, captured_stderr, requires_zlib,
|
||||
can_symlink, EnvironmentVarGuard, rmtree)
|
||||
import threading
|
||||
@@ -83,6 +84,10 @@ class BaseTest(unittest.TestCase):
|
||||
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."""
|
||||
|
||||
@@ -293,6 +298,82 @@ class BasicTest(BaseTest):
|
||||
'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 c454082..de4d3eb 100644
|
||||
--- a/Lib/venv/__init__.py
|
||||
+++ b/Lib/venv/__init__.py
|
||||
@@ -11,6 +11,7 @@ import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import types
|
||||
+import shlex
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -323,11 +324,41 @@ class EnvBuilder:
|
||||
: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):
|
||||
@@ -367,6 +398,7 @@ class EnvBuilder:
|
||||
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 b9d498f..a002005 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,8 +54,8 @@ fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
- if [ "x__VENV_PROMPT__" != x ] ; then
|
||||
- PS1="__VENV_PROMPT__${PS1:-}"
|
||||
+ if [ x__VENV_PROMPT__ != x ] ; then
|
||||
+ PS1=__VENV_PROMPT__"${PS1:-}"
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat
|
||||
index f61413e..11210c7 100644
|
||||
--- a/Lib/venv/scripts/nt/activate.bat
|
||||
+++ b/Lib/venv/scripts/nt/activate.bat
|
||||
@@ -8,7 +8,7 @@ if defined _OLD_CODEPAGE (
|
||||
"%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 @@ set PYTHONHOME=
|
||||
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 b0c7028..ad53fd3 100644
|
||||
--- a/Lib/venv/scripts/posix/activate.csh
|
||||
+++ b/Lib/venv/scripts/posix/activate.csh
|
||||
@@ -8,10 +8,10 @@ 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"
|
||||
diff --git a/Lib/venv/scripts/posix/activate.fish b/Lib/venv/scripts/posix/activate.fish
|
||||
index b401058..16723a2 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
|
||||
@@ -53,7 +53,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
|
||||
# Prompt override?
|
||||
if test -n "__VENV_PROMPT__"
|
||||
- printf "%s%s" "__VENV_PROMPT__" (set_color normal)
|
||||
+ printf "%s%s" __VENV_PROMPT__ (set_color normal)
|
||||
else
|
||||
# ...Otherwise, prepend env
|
||||
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||
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 0000000..17fc917
|
||||
--- /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.
|
||||
--
|
||||
2.27.0
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
From 1ce801b81ce63867ce382f6e9f56873a844c2bc6 Mon Sep 17 00:00:00 2001
|
||||
From: "Miss Islington (bot)"
|
||||
<31488909+miss-islington@users.noreply.github.com>
|
||||
Date: Sat, 27 May 2023 00:04:28 -0700
|
||||
Subject: [PATCH] [3.7] gh-99889: Fix directory traversal security flaw in
|
||||
uu.decode() (GH-104333)
|
||||
|
||||
(cherry picked from commit 0aeda297931820436a50b78f4f7f0597274b5df4)
|
||||
Co-authored-by: Sam Carroll <70000253+samcarroll42@users.noreply.github.com>
|
||||
---
|
||||
Lib/test/test_uu.py | 28 +++++++++++++++++++
|
||||
Lib/uu.py | 9 +++++-
|
||||
...3-05-02-17-56-32.gh-issue-99889.l664SU.rst | 2 ++
|
||||
3 files changed, 38 insertions(+), 1 deletion(-)
|
||||
mode change 100755 => 100644 Lib/uu.py
|
||||
create mode 100644 Misc/NEWS.d/next/Security/2023-05-02-17-56-32.gh-issue-99889.l664SU.rst
|
||||
|
||||
diff --git a/Lib/test/test_uu.py b/Lib/test/test_uu.py
|
||||
index c8709f7a0d6..e5d93d6cd1c 100644
|
||||
--- a/Lib/test/test_uu.py
|
||||
+++ b/Lib/test/test_uu.py
|
||||
@@ -145,6 +145,34 @@ class UUTest(unittest.TestCase):
|
||||
uu.encode(inp, out, filename)
|
||||
self.assertIn(safefilename, out.getvalue())
|
||||
|
||||
+ def test_no_directory_traversal(self):
|
||||
+ relative_bad = b"""\
|
||||
+begin 644 ../../../../../../../../tmp/test1
|
||||
+$86)C"@``
|
||||
+`
|
||||
+end
|
||||
+"""
|
||||
+ with self.assertRaisesRegex(uu.Error, 'directory'):
|
||||
+ uu.decode(io.BytesIO(relative_bad))
|
||||
+ if os.altsep:
|
||||
+ relative_bad_bs = relative_bad.replace(b'/', b'\\')
|
||||
+ with self.assertRaisesRegex(uu.Error, 'directory'):
|
||||
+ uu.decode(io.BytesIO(relative_bad_bs))
|
||||
+
|
||||
+ absolute_bad = b"""\
|
||||
+begin 644 /tmp/test2
|
||||
+$86)C"@``
|
||||
+`
|
||||
+end
|
||||
+"""
|
||||
+ with self.assertRaisesRegex(uu.Error, 'directory'):
|
||||
+ uu.decode(io.BytesIO(absolute_bad))
|
||||
+ if os.altsep:
|
||||
+ absolute_bad_bs = absolute_bad.replace(b'/', b'\\')
|
||||
+ with self.assertRaisesRegex(uu.Error, 'directory'):
|
||||
+ uu.decode(io.BytesIO(absolute_bad_bs))
|
||||
+
|
||||
+
|
||||
class UUStdIOTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
diff --git a/Lib/uu.py b/Lib/uu.py
|
||||
old mode 100755
|
||||
new mode 100644
|
||||
index 9f1f37f1a64..9fe252a639e
|
||||
--- a/Lib/uu.py
|
||||
+++ b/Lib/uu.py
|
||||
@@ -130,7 +130,14 @@ def decode(in_file, out_file=None, mode=None, quiet=False):
|
||||
# If the filename isn't ASCII, what's up with that?!?
|
||||
out_file = hdrfields[2].rstrip(b' \t\r\n\f').decode("ascii")
|
||||
if os.path.exists(out_file):
|
||||
- raise Error('Cannot overwrite existing file: %s' % out_file)
|
||||
+ raise Error(f'Cannot overwrite existing file: {out_file}')
|
||||
+ if (out_file.startswith(os.sep) or
|
||||
+ f'..{os.sep}' in out_file or (
|
||||
+ os.altsep and
|
||||
+ (out_file.startswith(os.altsep) or
|
||||
+ f'..{os.altsep}' in out_file))
|
||||
+ ):
|
||||
+ raise Error(f'Refusing to write to {out_file} due to directory traversal')
|
||||
if mode is None:
|
||||
mode = int(hdrfields[1], 8)
|
||||
#
|
||||
diff --git a/Misc/NEWS.d/next/Security/2023-05-02-17-56-32.gh-issue-99889.l664SU.rst b/Misc/NEWS.d/next/Security/2023-05-02-17-56-32.gh-issue-99889.l664SU.rst
|
||||
new file mode 100644
|
||||
index 00000000000..b7002e81b6b
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Security/2023-05-02-17-56-32.gh-issue-99889.l664SU.rst
|
||||
@@ -0,0 +1,2 @@
|
||||
+Fixed a security in flaw in :func:`uu.decode` that could allow for
|
||||
+directory traversal based on the input if no ``out_file`` was specified.
|
||||
--
|
||||
2.25.1
|
||||
|
||||
489
backport-CVE-2023-27043.patch
Normal file
489
backport-CVE-2023-27043.patch
Normal file
@ -0,0 +1,489 @@
|
||||
From 213a9208f830980b5241a9f24a79778cb699b2c7 Mon Sep 17 00:00:00 2001
|
||||
From: GuoCe <guoce@kylinos.cn>
|
||||
Date: Tue, 5 Mar 2024 16:47:59 +0800
|
||||
Subject: [PATCH 3/3] Subject: [PATCH] [Backport] CVE-2023-27043 Reject
|
||||
malformed addresses in email.parseaddr()
|
||||
|
||||
Reference: https://github.com/python/cpython/pull/111116
|
||||
|
||||
Detect email address parsing errors and return empty tuple to
|
||||
indicate the parsing error (old API). Add an optional 'strict'
|
||||
parameter to getaddresses() and parseaddr() functions.
|
||||
|
||||
Offering: CloudBu CMP
|
||||
|
||||
CVE: CVE-2023-27043
|
||||
---
|
||||
Doc/library/email.utils.rst | 19 +-
|
||||
Lib/email/utils.py | 151 +++++++++++++-
|
||||
Lib/test/test_email/test_email.py | 187 +++++++++++++++++-
|
||||
...-10-20-15-28-08.gh-issue-102988.dStNO7.rst | 8 +
|
||||
4 files changed, 344 insertions(+), 21 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||
|
||||
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
|
||||
index 4d0e920..104229e 100644
|
||||
--- a/Doc/library/email.utils.rst
|
||||
+++ b/Doc/library/email.utils.rst
|
||||
@@ -60,13 +60,18 @@ of the new API.
|
||||
begins with angle brackets, they are stripped off.
|
||||
|
||||
|
||||
-.. function:: parseaddr(address)
|
||||
+.. function:: parseaddr(address, *, strict=True)
|
||||
|
||||
Parse address -- which should be the value of some address-containing field such
|
||||
as :mailheader:`To` or :mailheader:`Cc` -- into its constituent *realname* and
|
||||
*email address* parts. Returns a tuple of that information, unless the parse
|
||||
fails, in which case a 2-tuple of ``('', '')`` is returned.
|
||||
|
||||
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||
+
|
||||
+ .. versionchanged:: 3.13
|
||||
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||
+
|
||||
|
||||
.. function:: formataddr(pair, charset='utf-8')
|
||||
|
||||
@@ -84,12 +89,15 @@ of the new API.
|
||||
Added the *charset* option.
|
||||
|
||||
|
||||
-.. function:: getaddresses(fieldvalues)
|
||||
+.. function:: getaddresses(fieldvalues, *, strict=True)
|
||||
|
||||
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
|
||||
*fieldvalues* is a sequence of header field values as might be returned by
|
||||
- :meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
|
||||
- example that gets all the recipients of a message::
|
||||
+ :meth:`Message.get_all <email.message.Message.get_all>`.
|
||||
+
|
||||
+ If *strict* is true, use a strict parser which rejects malformed inputs.
|
||||
+
|
||||
+ Here's a simple example that gets all the recipients of a message::
|
||||
|
||||
from email.utils import getaddresses
|
||||
|
||||
@@ -99,6 +107,9 @@ of the new API.
|
||||
resent_ccs = msg.get_all('resent-cc', [])
|
||||
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
|
||||
|
||||
+ .. versionchanged:: 3.13
|
||||
+ Add *strict* optional parameter and reject malformed inputs by default.
|
||||
+
|
||||
|
||||
.. function:: parsedate(date)
|
||||
|
||||
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
|
||||
index e7a4149..14f73ea 100644
|
||||
--- a/Lib/email/utils.py
|
||||
+++ b/Lib/email/utils.py
|
||||
@@ -48,6 +48,7 @@ TICK = "'"
|
||||
specialsre = re.compile(r'[][\\()<>@,:;".]')
|
||||
escapesre = re.compile(r'[\\"]')
|
||||
|
||||
+
|
||||
def _has_surrogates(s):
|
||||
"""Return True if s contains surrogate-escaped binary data."""
|
||||
# This check is based on the fact that unless there are surrogates, utf8
|
||||
@@ -106,12 +107,127 @@ def formataddr(pair, charset='utf-8'):
|
||||
return address
|
||||
|
||||
|
||||
+def _iter_escaped_chars(addr):
|
||||
+ pos = 0
|
||||
+ escape = False
|
||||
+ for pos, ch in enumerate(addr):
|
||||
+ if escape:
|
||||
+ yield (pos, '\\' + ch)
|
||||
+ escape = False
|
||||
+ elif ch == '\\':
|
||||
+ escape = True
|
||||
+ else:
|
||||
+ yield (pos, ch)
|
||||
+ if escape:
|
||||
+ yield (pos, '\\')
|
||||
+
|
||||
+
|
||||
+def _strip_quoted_realnames(addr):
|
||||
+ """Strip real names between quotes."""
|
||||
+ if '"' not in addr:
|
||||
+ # Fast path
|
||||
+ return addr
|
||||
+
|
||||
+ start = 0
|
||||
+ open_pos = None
|
||||
+ result = []
|
||||
+ for pos, ch in _iter_escaped_chars(addr):
|
||||
+ if ch == '"':
|
||||
+ if open_pos is None:
|
||||
+ open_pos = pos
|
||||
+ else:
|
||||
+ if start != open_pos:
|
||||
+ result.append(addr[start:open_pos])
|
||||
+ start = pos + 1
|
||||
+ open_pos = None
|
||||
+
|
||||
+ if start < len(addr):
|
||||
+ result.append(addr[start:])
|
||||
+
|
||||
+ return ''.join(result)
|
||||
|
||||
-def getaddresses(fieldvalues):
|
||||
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
||||
- all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||
- a = _AddressList(all)
|
||||
- return a.addresslist
|
||||
+
|
||||
+supports_strict_parsing = True
|
||||
+
|
||||
+def getaddresses(fieldvalues, *, strict=True):
|
||||
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||
+
|
||||
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||
+ its place.
|
||||
+
|
||||
+ If strict is true, use a strict parser which rejects malformed inputs.
|
||||
+ """
|
||||
+
|
||||
+ # If strict is true, if the resulting list of parsed addresses is greater
|
||||
+ # than the number of fieldvalues in the input list, a parsing error has
|
||||
+ # occurred and consequently a list containing a single empty 2-tuple [('',
|
||||
+ # '')] is returned in its place. This is done to avoid invalid output.
|
||||
+ #
|
||||
+ # Malformed input: getaddresses(['alice@example.com <bob@example.com>'])
|
||||
+ # Invalid output: [('', 'alice@example.com'), ('', 'bob@example.com')]
|
||||
+ # Safe output: [('', '')]
|
||||
+
|
||||
+ if not strict:
|
||||
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||
+ a = _AddressList(all)
|
||||
+ return a.addresslist
|
||||
+
|
||||
+ fieldvalues = [str(v) for v in fieldvalues]
|
||||
+ fieldvalues = _pre_parse_validation(fieldvalues)
|
||||
+ addr = COMMASPACE.join(fieldvalues)
|
||||
+ a = _AddressList(addr)
|
||||
+ result = _post_parse_validation(a.addresslist)
|
||||
+
|
||||
+ # Treat output as invalid if the number of addresses is not equal to the
|
||||
+ # expected number of addresses.
|
||||
+ n = 0
|
||||
+ for v in fieldvalues:
|
||||
+ # When a comma is used in the Real Name part it is not a deliminator.
|
||||
+ # So strip those out before counting the commas.
|
||||
+ v = _strip_quoted_realnames(v)
|
||||
+ # Expected number of addresses: 1 + number of commas
|
||||
+ n += 1 + v.count(',')
|
||||
+ if len(result) != n:
|
||||
+ return [('', '')]
|
||||
+
|
||||
+ return result
|
||||
+
|
||||
+
|
||||
+def _check_parenthesis(addr):
|
||||
+ # Ignore parenthesis in quoted real names.
|
||||
+ addr = _strip_quoted_realnames(addr)
|
||||
+
|
||||
+ opens = 0
|
||||
+ for pos, ch in _iter_escaped_chars(addr):
|
||||
+ if ch == '(':
|
||||
+ opens += 1
|
||||
+ elif ch == ')':
|
||||
+ opens -= 1
|
||||
+ if opens < 0:
|
||||
+ return False
|
||||
+ return (opens == 0)
|
||||
+
|
||||
+
|
||||
+def _pre_parse_validation(email_header_fields):
|
||||
+ accepted_values = []
|
||||
+ for v in email_header_fields:
|
||||
+ if not _check_parenthesis(v):
|
||||
+ v = "('', '')"
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
+
|
||||
+
|
||||
+def _post_parse_validation(parsed_email_header_tuples):
|
||||
+ accepted_values = []
|
||||
+ # The parser would have parsed a correctly formatted domain-literal
|
||||
+ # The existence of an [ after parsing indicates a parsing failure
|
||||
+ for v in parsed_email_header_tuples:
|
||||
+ if '[' in v[1]:
|
||||
+ v = ('', '')
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
|
||||
|
||||
def _format_timetuple_and_zone(timetuple, zone):
|
||||
@@ -202,16 +318,33 @@ def parsedate_to_datetime(data):
|
||||
tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))
|
||||
|
||||
|
||||
-def parseaddr(addr):
|
||||
+def parseaddr(addr, *, strict=True):
|
||||
"""
|
||||
Parse addr into its constituent realname and email address parts.
|
||||
|
||||
Return a tuple of realname and email address, unless the parse fails, in
|
||||
which case return a 2-tuple of ('', '').
|
||||
+
|
||||
+ If strict is True, use a strict parser which rejects malformed inputs.
|
||||
"""
|
||||
- addrs = _AddressList(addr).addresslist
|
||||
- if not addrs:
|
||||
- return '', ''
|
||||
+ if not strict:
|
||||
+ addrs = _AddressList(addr).addresslist
|
||||
+ if not addrs:
|
||||
+ return ('', '')
|
||||
+ return addrs[0]
|
||||
+
|
||||
+ if isinstance(addr, list):
|
||||
+ addr = addr[0]
|
||||
+
|
||||
+ if not isinstance(addr, str):
|
||||
+ return ('', '')
|
||||
+
|
||||
+ addr = _pre_parse_validation([addr])[0]
|
||||
+ addrs = _post_parse_validation(_AddressList(addr).addresslist)
|
||||
+
|
||||
+ if not addrs or len(addrs) > 1:
|
||||
+ return ('', '')
|
||||
+
|
||||
return addrs[0]
|
||||
|
||||
|
||||
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
|
||||
index 66aaee1..56a8063 100644
|
||||
--- a/Lib/test/test_email/test_email.py
|
||||
+++ b/Lib/test/test_email/test_email.py
|
||||
@@ -16,6 +16,7 @@ from unittest.mock import patch
|
||||
|
||||
import email
|
||||
import email.policy
|
||||
+import email.utils
|
||||
|
||||
from email.charset import Charset
|
||||
from email.header import Header, decode_header, make_header
|
||||
@@ -3230,15 +3231,137 @@ Foo
|
||||
],
|
||||
)
|
||||
|
||||
+ def test_parsing_errors(self):
|
||||
+ """Test for parsing errors from CVE-2023-27043 and CVE-2019-16056"""
|
||||
+ alice = 'alice@example.org'
|
||||
+ bob = 'bob@example.com'
|
||||
+ empty = ('', '')
|
||||
+
|
||||
+ # Test utils.getaddresses() and utils.parseaddr() on malformed email
|
||||
+ # addresses: default behavior (strict=True) rejects malformed address,
|
||||
+ # and strict=False which tolerates malformed address.
|
||||
+ for invalid_separator, expected_non_strict in (
|
||||
+ ('(', [(f'<{bob}>', alice)]),
|
||||
+ (')', [('', alice), empty, ('', bob)]),
|
||||
+ ('<', [('', alice), empty, ('', bob), empty]),
|
||||
+ ('>', [('', alice), empty, ('', bob)]),
|
||||
+ ('[', [('', f'{alice}[<{bob}>]')]),
|
||||
+ (']', [('', alice), empty, ('', bob)]),
|
||||
+ ('@', [empty, empty, ('', bob)]),
|
||||
+ (';', [('', alice), empty, ('', bob)]),
|
||||
+ (':', [('', alice), ('', bob)]),
|
||||
+ ('.', [('', alice + '.'), ('', bob)]),
|
||||
+ ('"', [('', alice), ('', f'<{bob}>')]),
|
||||
+ ):
|
||||
+ address = f'{alice}{invalid_separator}<{bob}>'
|
||||
+ with self.subTest(address=address):
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [empty])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ expected_non_strict)
|
||||
+
|
||||
+ self.assertEqual(utils.parseaddr([address]),
|
||||
+ empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Comma (',') is treated differently depending on strict parameter.
|
||||
+ # Comma without quotes.
|
||||
+ address = f'{alice},<{bob}>'
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [('', alice), ('', bob)])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('', alice), ('', bob)])
|
||||
+ self.assertEqual(utils.parseaddr([address]),
|
||||
+ empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Real name between quotes containing comma.
|
||||
+ address = '"Alice, alice@example.org" <bob@example.com>'
|
||||
+ expected_strict = ('Alice, alice@example.org', 'bob@example.com')
|
||||
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Valid parenthesis in comments.
|
||||
+ address = 'alice@example.org (Alice)'
|
||||
+ expected_strict = ('Alice', 'alice@example.org')
|
||||
+ self.assertEqual(utils.getaddresses([address]), [expected_strict])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False), [expected_strict])
|
||||
+ self.assertEqual(utils.parseaddr([address]), expected_strict)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Invalid parenthesis in comments.
|
||||
+ address = 'alice@example.org )Alice('
|
||||
+ self.assertEqual(utils.getaddresses([address]), [empty])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('', 'alice@example.org'), ('', ''), ('', 'Alice')])
|
||||
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Two addresses with quotes separated by comma.
|
||||
+ address = '"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>'
|
||||
+ self.assertEqual(utils.getaddresses([address]),
|
||||
+ [('Jane Doe', 'jane@example.net'),
|
||||
+ ('John Doe', 'john@example.net')])
|
||||
+ self.assertEqual(utils.getaddresses([address], strict=False),
|
||||
+ [('Jane Doe', 'jane@example.net'),
|
||||
+ ('John Doe', 'john@example.net')])
|
||||
+ self.assertEqual(utils.parseaddr([address]), empty)
|
||||
+ self.assertEqual(utils.parseaddr([address], strict=False),
|
||||
+ ('', address))
|
||||
+
|
||||
+ # Test email.utils.supports_strict_parsing attribute
|
||||
+ self.assertEqual(email.utils.supports_strict_parsing, True)
|
||||
+
|
||||
def test_getaddresses_nasty(self):
|
||||
- eq = self.assertEqual
|
||||
- eq(utils.getaddresses(['foo: ;']), [('', '')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['[]*-- =~$']),
|
||||
- [('', ''), ('', ''), ('', '*--')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
|
||||
- [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
|
||||
+ for addresses, expected in (
|
||||
+ (['"Sürname, Firstname" <to@example.com>'],
|
||||
+ [('Sürname, Firstname', 'to@example.com')]),
|
||||
+
|
||||
+ (['foo: ;'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ (['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>'],
|
||||
+ [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')]),
|
||||
+
|
||||
+ ([r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>'],
|
||||
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')]),
|
||||
+
|
||||
+ (['(Empty list)(start)Undisclosed recipients :(nobody(I know))'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ (['Mary <@machine.tld:mary@example.net>, , jdoe@test . example'],
|
||||
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')]),
|
||||
+
|
||||
+ (['John Doe <jdoe@machine(comment). example>'],
|
||||
+ [('John Doe (comment)', 'jdoe@machine.example')]),
|
||||
+
|
||||
+ (['"Mary Smith: Personal Account" <smith@home.example>'],
|
||||
+ [('Mary Smith: Personal Account', 'smith@home.example')]),
|
||||
+
|
||||
+ (['Undisclosed recipients:;'],
|
||||
+ [('', '')]),
|
||||
+
|
||||
+ ([r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>'],
|
||||
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')]),
|
||||
+ ):
|
||||
+ with self.subTest(addresses=addresses):
|
||||
+ self.assertEqual(utils.getaddresses(addresses),
|
||||
+ expected)
|
||||
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||
+ expected)
|
||||
+
|
||||
+ addresses = ['[]*-- =~$']
|
||||
+ self.assertEqual(utils.getaddresses(addresses),
|
||||
+ [('', '')])
|
||||
+ self.assertEqual(utils.getaddresses(addresses, strict=False),
|
||||
+ [('', ''), ('', ''), ('', '*--')])
|
||||
|
||||
def test_getaddresses_embedded_comment(self):
|
||||
"""Test proper handling of a nested comment"""
|
||||
@@ -3422,6 +3545,54 @@ multipart/report
|
||||
m = cls(*constructor, policy=email.policy.default)
|
||||
self.assertIs(m.policy, email.policy.default)
|
||||
|
||||
+ def test_iter_escaped_chars(self):
|
||||
+ self.assertEqual(list(utils._iter_escaped_chars(r'a\\b\"c\\"d')),
|
||||
+ [(0, 'a'),
|
||||
+ (2, '\\\\'),
|
||||
+ (3, 'b'),
|
||||
+ (5, '\\"'),
|
||||
+ (6, 'c'),
|
||||
+ (8, '\\\\'),
|
||||
+ (9, '"'),
|
||||
+ (10, 'd')])
|
||||
+ self.assertEqual(list(utils._iter_escaped_chars('a\\')),
|
||||
+ [(0, 'a'), (1, '\\')])
|
||||
+
|
||||
+ def test_strip_quoted_realnames(self):
|
||||
+ def check(addr, expected):
|
||||
+ self.assertEqual(utils._strip_quoted_realnames(addr), expected)
|
||||
+
|
||||
+ check('"Jane Doe" <jane@example.net>, "John Doe" <john@example.net>',
|
||||
+ ' <jane@example.net>, <john@example.net>')
|
||||
+ check(r'"Jane \"Doe\"." <jane@example.net>',
|
||||
+ ' <jane@example.net>')
|
||||
+
|
||||
+ # special cases
|
||||
+ check(r'before"name"after', 'beforeafter')
|
||||
+ check(r'before"name"', 'before')
|
||||
+ check(r'b"name"', 'b') # single char
|
||||
+ check(r'"name"after', 'after')
|
||||
+ check(r'"name"a', 'a') # single char
|
||||
+ check(r'"name"', '')
|
||||
+
|
||||
+ # no change
|
||||
+ for addr in (
|
||||
+ 'Jane Doe <jane@example.net>, John Doe <john@example.net>',
|
||||
+ 'lone " quote',
|
||||
+ ):
|
||||
+ self.assertEqual(utils._strip_quoted_realnames(addr), addr)
|
||||
+
|
||||
+
|
||||
+ def test_check_parenthesis(self):
|
||||
+ addr = 'alice@example.net'
|
||||
+ self.assertTrue(utils._check_parenthesis(f'{addr} (Alice)'))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} )Alice('))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} (Alice))'))
|
||||
+ self.assertFalse(utils._check_parenthesis(f'{addr} ((Alice)'))
|
||||
+
|
||||
+ # Ignore real name between quotes
|
||||
+ self.assertTrue(utils._check_parenthesis(f'")Alice((" {addr}'))
|
||||
+
|
||||
|
||||
# Test the iterator/generators
|
||||
class TestIterators(TestEmailBase):
|
||||
diff --git a/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||
new file mode 100644
|
||||
index 0000000..3d0e9e4
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Library/2023-10-20-15-28-08.gh-issue-102988.dStNO7.rst
|
||||
@@ -0,0 +1,8 @@
|
||||
+:func:`email.utils.getaddresses` and :func:`email.utils.parseaddr` now
|
||||
+return ``('', '')`` 2-tuples in more situations where invalid email
|
||||
+addresses are encountered instead of potentially inaccurate values. Add
|
||||
+optional *strict* parameter to these two functions: use ``strict=False`` to
|
||||
+get the old behavior, accept malformed inputs.
|
||||
+``getattr(email.utils, 'supports_strict_parsing', False)`` can be use to check
|
||||
+if the *strict* paramater is available. Patch by Thomas Dwyer and Victor
|
||||
+Stinner to improve the CVE-2023-27043 fix.
|
||||
--
|
||||
2.27.0
|
||||
|
||||
267
backport-Fix-parsing-errors-in-email-_parseaddr.py.patch
Normal file
267
backport-Fix-parsing-errors-in-email-_parseaddr.py.patch
Normal file
@ -0,0 +1,267 @@
|
||||
From 42fe8d3b828bbea6c530225a6369eda8ad86c9f3 Mon Sep 17 00:00:00 2001
|
||||
From: GuoCe <guoce@kylinos.cn>
|
||||
Date: Tue, 5 Mar 2024 15:33:14 +0800
|
||||
Subject: [PATCH 1/3] Subject: [PATCH] [Backport] Fix parsing errors in
|
||||
email/_parseaddr.py
|
||||
|
||||
Reference: https://github.com/python/cpython/issues/102988
|
||||
|
||||
The e-mail module of Python 0 - 2.7.18, 3.x - 3.11 incorrectly parses e-mail addresses which contain a special character. This vulnerability allows attackers to send messages from e-mail addresses that would otherwise be rejected.
|
||||
|
||||
Offering: CloudBu CMP
|
||||
|
||||
CVE: CVE-2023-27043
|
||||
---
|
||||
Doc/library/email.utils.rst | 26 +++++-
|
||||
Lib/email/utils.py | 63 +++++++++++++--
|
||||
Lib/test/test_email/test_email.py | 81 ++++++++++++++++++-
|
||||
...-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 4 +
|
||||
4 files changed, 164 insertions(+), 10 deletions(-)
|
||||
create mode 100644 Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
|
||||
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
|
||||
index 4d0e920..97df962 100644
|
||||
--- a/Doc/library/email.utils.rst
|
||||
+++ b/Doc/library/email.utils.rst
|
||||
@@ -67,6 +67,11 @@ of the new API.
|
||||
*email address* parts. Returns a tuple of that information, unless the parse
|
||||
fails, in which case a 2-tuple of ``('', '')`` is returned.
|
||||
|
||||
+ .. versionchanged:: 3.12
|
||||
+ For security reasons, addresses that were ambiguous and could parse into
|
||||
+ multiple different addresses now cause ``('', '')`` to be returned
|
||||
+ instead of only one of the *potential* addresses.
|
||||
+
|
||||
|
||||
.. function:: formataddr(pair, charset='utf-8')
|
||||
|
||||
@@ -89,7 +94,7 @@ of the new API.
|
||||
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
|
||||
*fieldvalues* is a sequence of header field values as might be returned by
|
||||
:meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
|
||||
- example that gets all the recipients of a message::
|
||||
+ example that gets all the recipients of a message:
|
||||
|
||||
from email.utils import getaddresses
|
||||
|
||||
@@ -99,6 +104,25 @@ of the new API.
|
||||
resent_ccs = msg.get_all('resent-cc', [])
|
||||
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
|
||||
|
||||
+ When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')``
|
||||
+ is returned in its place. Other errors in parsing the list of
|
||||
+ addresses such as a fieldvalue seemingly parsing into multiple
|
||||
+ addresses may result in a list containing a single empty 2-tuple
|
||||
+ ``[('', '')]`` being returned rather than returning potentially
|
||||
+ invalid output.
|
||||
+
|
||||
+ Example malformed input parsing:
|
||||
+
|
||||
+ .. doctest::
|
||||
+
|
||||
+ >>> from email.utils import getaddresses
|
||||
+ >>> getaddresses(['alice@example.com <bob@example.com>', 'me@example.com'])
|
||||
+ [('', '')]
|
||||
+
|
||||
+ .. versionchanged:: 3.12
|
||||
+ The 2-tuple of ``('', '')`` in the returned values when parsing
|
||||
+ fails were added as to address a security issue.
|
||||
+
|
||||
|
||||
.. function:: parsedate(date)
|
||||
|
||||
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
|
||||
index 858f620..8b1d4d1 100644
|
||||
--- a/Lib/email/utils.py
|
||||
+++ b/Lib/email/utils.py
|
||||
@@ -106,12 +106,54 @@ def formataddr(pair, charset='utf-8'):
|
||||
return address
|
||||
|
||||
|
||||
+def _pre_parse_validation(email_header_fields):
|
||||
+ accepted_values = []
|
||||
+ for v in email_header_fields:
|
||||
+ s = v.replace('\\(', '').replace('\\)', '')
|
||||
+ if s.count('(') != s.count(')'):
|
||||
+ v = "('', '')"
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
+
|
||||
+
|
||||
+def _post_parse_validation(parsed_email_header_tuples):
|
||||
+ accepted_values = []
|
||||
+ # The parser would have parsed a correctly formatted domain-literal
|
||||
+ # The existence of an [ after parsing indicates a parsing failure
|
||||
+ for v in parsed_email_header_tuples:
|
||||
+ if '[' in v[1]:
|
||||
+ v = ('', '')
|
||||
+ accepted_values.append(v)
|
||||
+
|
||||
+ return accepted_values
|
||||
+
|
||||
|
||||
def getaddresses(fieldvalues):
|
||||
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
||||
- all = COMMASPACE.join(fieldvalues)
|
||||
+ """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||
+
|
||||
+ When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||
+ its place.
|
||||
+
|
||||
+ If the resulting list of parsed address is not the same as the number of
|
||||
+ fieldvalues in the input list a parsing error has occurred. A list
|
||||
+ containing a single empty 2-tuple [('', '')] is returned in its place.
|
||||
+ This is done to avoid invalid output.
|
||||
+ """
|
||||
+ fieldvalues = [str(v) for v in fieldvalues]
|
||||
+ fieldvalues = _pre_parse_validation(fieldvalues)
|
||||
+ all = COMMASPACE.join(v for v in fieldvalues)
|
||||
a = _AddressList(all)
|
||||
- return a.addresslist
|
||||
+ result = _post_parse_validation(a.addresslist)
|
||||
+
|
||||
+ n = 0
|
||||
+ for v in fieldvalues:
|
||||
+ n += v.count(',') + 1
|
||||
+
|
||||
+ if len(result) != n:
|
||||
+ return [('', '')]
|
||||
+
|
||||
+ return result
|
||||
|
||||
|
||||
def _format_timetuple_and_zone(timetuple, zone):
|
||||
@@ -209,9 +251,18 @@ def parseaddr(addr):
|
||||
Return a tuple of realname and email address, unless the parse fails, in
|
||||
which case return a 2-tuple of ('', '').
|
||||
"""
|
||||
- addrs = _AddressList(addr).addresslist
|
||||
- if not addrs:
|
||||
- return '', ''
|
||||
+ if isinstance(addr, list):
|
||||
+ addr = addr[0]
|
||||
+
|
||||
+ if not isinstance(addr, str):
|
||||
+ return ('', '')
|
||||
+
|
||||
+ addr = _pre_parse_validation([addr])[0]
|
||||
+ addrs = _post_parse_validation(_AddressList(addr).addresslist)
|
||||
+
|
||||
+ if not addrs or len(addrs) > 1:
|
||||
+ return ('', '')
|
||||
+
|
||||
return addrs[0]
|
||||
|
||||
|
||||
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
|
||||
index 64bcdcc..8b5c9c1 100644
|
||||
--- a/Lib/test/test_email/test_email.py
|
||||
+++ b/Lib/test/test_email/test_email.py
|
||||
@@ -3213,15 +3213,90 @@ Foo
|
||||
[('Al Person', 'aperson@dom.ain'),
|
||||
('Bud Person', 'bperson@dom.ain')])
|
||||
|
||||
+ def test_getaddresses_parsing_errors(self):
|
||||
+ """Test for parsing errors from CVE-2023-27043"""
|
||||
+ eq = self.assertEqual
|
||||
+ eq(utils.getaddresses(['alice@example.org(<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org)<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org<<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org><bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org@<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org,<bob@example.com>']),
|
||||
+ [('', 'alice@example.org'), ('', 'bob@example.com')])
|
||||
+ eq(utils.getaddresses(['alice@example.org;<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org:<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org.<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org"<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org[<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(['alice@example.org]<bob@example.com>']),
|
||||
+ [('', '')])
|
||||
+
|
||||
+ def test_parseaddr_parsing_errors(self):
|
||||
+ """Test for parsing errors from CVE-2023-27043"""
|
||||
+ eq = self.assertEqual
|
||||
+ eq(utils.parseaddr(['alice@example.org(<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org)<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org<<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org><bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org@<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org,<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org;<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org:<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org.<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org"<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org[<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+ eq(utils.parseaddr(['alice@example.org]<bob@example.com>']),
|
||||
+ ('', ''))
|
||||
+
|
||||
def test_getaddresses_nasty(self):
|
||||
eq = self.assertEqual
|
||||
eq(utils.getaddresses(['foo: ;']), [('', '')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['[]*-- =~$']),
|
||||
- [('', ''), ('', ''), ('', '*--')])
|
||||
+ eq(utils.getaddresses(['[]*-- =~$']), [('', '')])
|
||||
eq(utils.getaddresses(
|
||||
['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
|
||||
[('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ [r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>']),
|
||||
+ [('Pete (A nice ) chap his account his host)', 'pete@silly.test')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']),
|
||||
+ [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['John Doe <jdoe@machine(comment). example>']),
|
||||
+ [('John Doe (comment)', 'jdoe@machine.example')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['"Mary Smith: Personal Account" <smith@home.example>']),
|
||||
+ [('Mary Smith: Personal Account', 'smith@home.example')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['Undisclosed recipients:;']),
|
||||
+ [('', '')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ [r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>']),
|
||||
+ [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')])
|
||||
|
||||
def test_getaddresses_embedded_comment(self):
|
||||
"""Test proper handling of a nested comment"""
|
||||
diff --git a/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst b/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
new file mode 100644
|
||||
index 0000000..e0434cc
|
||||
--- /dev/null
|
||||
+++ b/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
@@ -0,0 +1,4 @@
|
||||
+CVE-2023-27043: Prevent :func:`email.utils.parseaddr`
|
||||
+and :func:`email.utils.getaddresses` from returning the realname portion of an
|
||||
+invalid RFC2822 email header in the email address portion of the 2-tuple
|
||||
+returned after being parsed by :class:`email._parseaddr.AddressList`.
|
||||
--
|
||||
2.27.0
|
||||
|
||||
286
backport-Revert-fixes-for-CVE-2023-27043.patch
Normal file
286
backport-Revert-fixes-for-CVE-2023-27043.patch
Normal file
@ -0,0 +1,286 @@
|
||||
From 2c367ef26f6cb9897a862f5871047703fe0af31e Mon Sep 17 00:00:00 2001
|
||||
From: GuoCe <guoce@kylinos.cn>
|
||||
Date: Tue, 5 Mar 2024 16:03:17 +0800
|
||||
Subject: [PATCH 2/3] Subject: [PATCH] [Backport] Revert fixes for
|
||||
CVE-2023-27043
|
||||
|
||||
Reference: https://github.com/python/cpython/pull/106733
|
||||
|
||||
Revert "gh-102988: Detect email address parsing errors and return empty tuple to indicate the parsing error (old API) (#105127)"
|
||||
This reverts commit and adds the regression test suggested in the issue.
|
||||
|
||||
Offering: CloudBu CMP
|
||||
|
||||
CVE: CVE-2023-27043
|
||||
---
|
||||
Doc/library/email.utils.rst | 26 +----
|
||||
Lib/email/utils.py | 63 ++----------
|
||||
Lib/test/test_email/test_email.py | 96 ++++---------------
|
||||
...-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst | 8 +-
|
||||
4 files changed, 30 insertions(+), 163 deletions(-)
|
||||
|
||||
diff --git a/Doc/library/email.utils.rst b/Doc/library/email.utils.rst
|
||||
index 97df962..4d0e920 100644
|
||||
--- a/Doc/library/email.utils.rst
|
||||
+++ b/Doc/library/email.utils.rst
|
||||
@@ -67,11 +67,6 @@ of the new API.
|
||||
*email address* parts. Returns a tuple of that information, unless the parse
|
||||
fails, in which case a 2-tuple of ``('', '')`` is returned.
|
||||
|
||||
- .. versionchanged:: 3.12
|
||||
- For security reasons, addresses that were ambiguous and could parse into
|
||||
- multiple different addresses now cause ``('', '')`` to be returned
|
||||
- instead of only one of the *potential* addresses.
|
||||
-
|
||||
|
||||
.. function:: formataddr(pair, charset='utf-8')
|
||||
|
||||
@@ -94,7 +89,7 @@ of the new API.
|
||||
This method returns a list of 2-tuples of the form returned by ``parseaddr()``.
|
||||
*fieldvalues* is a sequence of header field values as might be returned by
|
||||
:meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple
|
||||
- example that gets all the recipients of a message:
|
||||
+ example that gets all the recipients of a message::
|
||||
|
||||
from email.utils import getaddresses
|
||||
|
||||
@@ -104,25 +99,6 @@ of the new API.
|
||||
resent_ccs = msg.get_all('resent-cc', [])
|
||||
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs)
|
||||
|
||||
- When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')``
|
||||
- is returned in its place. Other errors in parsing the list of
|
||||
- addresses such as a fieldvalue seemingly parsing into multiple
|
||||
- addresses may result in a list containing a single empty 2-tuple
|
||||
- ``[('', '')]`` being returned rather than returning potentially
|
||||
- invalid output.
|
||||
-
|
||||
- Example malformed input parsing:
|
||||
-
|
||||
- .. doctest::
|
||||
-
|
||||
- >>> from email.utils import getaddresses
|
||||
- >>> getaddresses(['alice@example.com <bob@example.com>', 'me@example.com'])
|
||||
- [('', '')]
|
||||
-
|
||||
- .. versionchanged:: 3.12
|
||||
- The 2-tuple of ``('', '')`` in the returned values when parsing
|
||||
- fails were added as to address a security issue.
|
||||
-
|
||||
|
||||
.. function:: parsedate(date)
|
||||
|
||||
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
|
||||
index 8b1d4d1..e7a4149 100644
|
||||
--- a/Lib/email/utils.py
|
||||
+++ b/Lib/email/utils.py
|
||||
@@ -106,54 +106,12 @@ def formataddr(pair, charset='utf-8'):
|
||||
return address
|
||||
|
||||
|
||||
-def _pre_parse_validation(email_header_fields):
|
||||
- accepted_values = []
|
||||
- for v in email_header_fields:
|
||||
- s = v.replace('\\(', '').replace('\\)', '')
|
||||
- if s.count('(') != s.count(')'):
|
||||
- v = "('', '')"
|
||||
- accepted_values.append(v)
|
||||
-
|
||||
- return accepted_values
|
||||
-
|
||||
-
|
||||
-def _post_parse_validation(parsed_email_header_tuples):
|
||||
- accepted_values = []
|
||||
- # The parser would have parsed a correctly formatted domain-literal
|
||||
- # The existence of an [ after parsing indicates a parsing failure
|
||||
- for v in parsed_email_header_tuples:
|
||||
- if '[' in v[1]:
|
||||
- v = ('', '')
|
||||
- accepted_values.append(v)
|
||||
-
|
||||
- return accepted_values
|
||||
-
|
||||
|
||||
def getaddresses(fieldvalues):
|
||||
- """Return a list of (REALNAME, EMAIL) or ('','') for each fieldvalue.
|
||||
-
|
||||
- When parsing fails for a fieldvalue, a 2-tuple of ('', '') is returned in
|
||||
- its place.
|
||||
-
|
||||
- If the resulting list of parsed address is not the same as the number of
|
||||
- fieldvalues in the input list a parsing error has occurred. A list
|
||||
- containing a single empty 2-tuple [('', '')] is returned in its place.
|
||||
- This is done to avoid invalid output.
|
||||
- """
|
||||
- fieldvalues = [str(v) for v in fieldvalues]
|
||||
- fieldvalues = _pre_parse_validation(fieldvalues)
|
||||
- all = COMMASPACE.join(v for v in fieldvalues)
|
||||
+ """Return a list of (REALNAME, EMAIL) for each fieldvalue."""
|
||||
+ all = COMMASPACE.join(str(v) for v in fieldvalues)
|
||||
a = _AddressList(all)
|
||||
- result = _post_parse_validation(a.addresslist)
|
||||
-
|
||||
- n = 0
|
||||
- for v in fieldvalues:
|
||||
- n += v.count(',') + 1
|
||||
-
|
||||
- if len(result) != n:
|
||||
- return [('', '')]
|
||||
-
|
||||
- return result
|
||||
+ return a.addresslist
|
||||
|
||||
|
||||
def _format_timetuple_and_zone(timetuple, zone):
|
||||
@@ -251,18 +209,9 @@ def parseaddr(addr):
|
||||
Return a tuple of realname and email address, unless the parse fails, in
|
||||
which case return a 2-tuple of ('', '').
|
||||
"""
|
||||
- if isinstance(addr, list):
|
||||
- addr = addr[0]
|
||||
-
|
||||
- if not isinstance(addr, str):
|
||||
- return ('', '')
|
||||
-
|
||||
- addr = _pre_parse_validation([addr])[0]
|
||||
- addrs = _post_parse_validation(_AddressList(addr).addresslist)
|
||||
-
|
||||
- if not addrs or len(addrs) > 1:
|
||||
- return ('', '')
|
||||
-
|
||||
+ addrs = _AddressList(addr).addresslist
|
||||
+ if not addrs:
|
||||
+ return '', ''
|
||||
return addrs[0]
|
||||
|
||||
|
||||
diff --git a/Lib/test/test_email/test_email.py b/Lib/test/test_email/test_email.py
|
||||
index 8b5c9c1..66aaee1 100644
|
||||
--- a/Lib/test/test_email/test_email.py
|
||||
+++ b/Lib/test/test_email/test_email.py
|
||||
@@ -3213,90 +3213,32 @@ Foo
|
||||
[('Al Person', 'aperson@dom.ain'),
|
||||
('Bud Person', 'bperson@dom.ain')])
|
||||
|
||||
- def test_getaddresses_parsing_errors(self):
|
||||
- """Test for parsing errors from CVE-2023-27043"""
|
||||
- eq = self.assertEqual
|
||||
- eq(utils.getaddresses(['alice@example.org(<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org)<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org<<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org><bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org@<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org,<bob@example.com>']),
|
||||
- [('', 'alice@example.org'), ('', 'bob@example.com')])
|
||||
- eq(utils.getaddresses(['alice@example.org;<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org:<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org.<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org"<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org[<bob@example.com>']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(['alice@example.org]<bob@example.com>']),
|
||||
- [('', '')])
|
||||
-
|
||||
- def test_parseaddr_parsing_errors(self):
|
||||
- """Test for parsing errors from CVE-2023-27043"""
|
||||
- eq = self.assertEqual
|
||||
- eq(utils.parseaddr(['alice@example.org(<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org)<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org<<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org><bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org@<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org,<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org;<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org:<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org.<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org"<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org[<bob@example.com>']),
|
||||
- ('', ''))
|
||||
- eq(utils.parseaddr(['alice@example.org]<bob@example.com>']),
|
||||
- ('', ''))
|
||||
+ def test_getaddresses_comma_in_name(self):
|
||||
+ """GH-106669 regression test."""
|
||||
+ self.assertEqual(
|
||||
+ utils.getaddresses(
|
||||
+ [
|
||||
+ '"Bud, Person" <bperson@dom.ain>',
|
||||
+ 'aperson@dom.ain (Al Person)',
|
||||
+ '"Mariusz Felisiak" <to@example.com>',
|
||||
+ ]
|
||||
+ ),
|
||||
+ [
|
||||
+ ('Bud, Person', 'bperson@dom.ain'),
|
||||
+ ('Al Person', 'aperson@dom.ain'),
|
||||
+ ('Mariusz Felisiak', 'to@example.com'),
|
||||
+ ],
|
||||
+ )
|
||||
|
||||
def test_getaddresses_nasty(self):
|
||||
eq = self.assertEqual
|
||||
eq(utils.getaddresses(['foo: ;']), [('', '')])
|
||||
- eq(utils.getaddresses(['[]*-- =~$']), [('', '')])
|
||||
+ eq(utils.getaddresses(
|
||||
+ ['[]*-- =~$']),
|
||||
+ [('', ''), ('', ''), ('', '*--')])
|
||||
eq(utils.getaddresses(
|
||||
['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
|
||||
[('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
|
||||
- eq(utils.getaddresses(
|
||||
- [r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>']),
|
||||
- [('Pete (A nice ) chap his account his host)', 'pete@silly.test')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['(Empty list)(start)Undisclosed recipients :(nobody(I know))']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['Mary <@machine.tld:mary@example.net>, , jdoe@test . example']),
|
||||
- [('Mary', 'mary@example.net'), ('', ''), ('', 'jdoe@test.example')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['John Doe <jdoe@machine(comment). example>']),
|
||||
- [('John Doe (comment)', 'jdoe@machine.example')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['"Mary Smith: Personal Account" <smith@home.example>']),
|
||||
- [('Mary Smith: Personal Account', 'smith@home.example')])
|
||||
- eq(utils.getaddresses(
|
||||
- ['Undisclosed recipients:;']),
|
||||
- [('', '')])
|
||||
- eq(utils.getaddresses(
|
||||
- [r'<boss@nil.test>, "Giant; \"Big\" Box" <bob@example.net>']),
|
||||
- [('', 'boss@nil.test'), ('Giant; "Big" Box', 'bob@example.net')])
|
||||
|
||||
def test_getaddresses_embedded_comment(self):
|
||||
"""Test proper handling of a nested comment"""
|
||||
diff --git a/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst b/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
index e0434cc..c67ec45 100644
|
||||
--- a/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
+++ b/Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
|
||||
@@ -1,4 +1,4 @@
|
||||
-CVE-2023-27043: Prevent :func:`email.utils.parseaddr`
|
||||
-and :func:`email.utils.getaddresses` from returning the realname portion of an
|
||||
-invalid RFC2822 email header in the email address portion of the 2-tuple
|
||||
-returned after being parsed by :class:`email._parseaddr.AddressList`.
|
||||
+Reverted the :mod:`email.utils` security improvement change released in
|
||||
+3.12beta4 that unintentionally caused :mod:`email.utils.getaddresses` to fail
|
||||
+to parse email addresses with a comma in the quoted name field.
|
||||
+See :gh:`106669`.
|
||||
--
|
||||
2.27.0
|
||||
|
||||
81
python3.spec
81
python3.spec
@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language
|
||||
URL: https://www.python.org/
|
||||
|
||||
Version: 3.7.9
|
||||
Release: 37
|
||||
Release: 43
|
||||
License: Python-2.0
|
||||
|
||||
%global branchversion 3.7
|
||||
@ -73,7 +73,9 @@ BuildRequires: tcl-devel
|
||||
BuildRequires: tix-devel
|
||||
BuildRequires: tk-devel
|
||||
|
||||
%ifnarch loongarch64
|
||||
BuildRequires: valgrind-devel
|
||||
%endif
|
||||
|
||||
BuildRequires: xz-devel
|
||||
BuildRequires: zlib-devel
|
||||
@ -176,6 +178,22 @@ Patch6065: backport-0003-CVE-2023-40217.patch
|
||||
patch9000: Don-t-override-PYTHONPATH-which-is-already-set.patch
|
||||
patch9001: add-the-sm3-method-for-obtaining-the-salt-value.patch
|
||||
Patch9002: fix-CVE-2023-24329.patch
|
||||
Patch9003: backport-Fix-parsing-errors-in-email-_parseaddr.py.patch
|
||||
Patch9004: backport-Revert-fixes-for-CVE-2023-27043.patch
|
||||
Patch9005: backport-CVE-2023-27043.patch
|
||||
Patch9006: Add-loongarch-support.patch
|
||||
|
||||
# fix CVE-2007-4559
|
||||
Patch9007: backport-3.7-gh-102950-Implement-PEP-706-Filter-for-tarfile.e.patch
|
||||
Patch9008: backport-3.7-gh-107845-Fix-symlink-handling-for-tarfile.data_.patch
|
||||
# fix test error
|
||||
Patch9009: backport-3.7-gh-115133-Fix-test_xml_etree-error-with-expat-ve.patch
|
||||
|
||||
Patch9010: backport-3.7-gh-104049-do-not-expose-on-disk-location-from-Si.patch
|
||||
Patch9011: backport-3.7-gh-99889-Fix-directory-traversal-security-flaw-i.patch
|
||||
Patch9012: 0001-expected_algs-list-to-include-TLS_SM4.patch
|
||||
Patch9013: backport-3.7-gh-124651-CVE-2024-9287.patch
|
||||
|
||||
|
||||
Provides: python%{branchversion} = %{version}-%{release}
|
||||
Provides: python(abi) = %{branchversion}
|
||||
@ -336,6 +354,19 @@ rm Lib/ensurepip/_bundled/*.whl
|
||||
%patch9000 -p1
|
||||
%patch9001 -p1
|
||||
%patch9002 -p1
|
||||
%patch9003 -p1
|
||||
%patch9004 -p1
|
||||
%patch9005 -p1
|
||||
%patch9006 -p1
|
||||
|
||||
%patch9007 -p1
|
||||
%patch9008 -p1
|
||||
%patch9009 -p1
|
||||
|
||||
%patch9010 -p1
|
||||
%patch9011 -p1
|
||||
%patch9012 -p1
|
||||
%patch9013 -p1
|
||||
|
||||
sed -i "s/generic_os/%{_vendor}/g" Lib/platform.py
|
||||
rm configure pyconfig.h.in
|
||||
@ -381,7 +412,9 @@ pushd ${DebugBuildDir}
|
||||
--enable-loadable-sqlite-extensions \
|
||||
--with-dtrace \
|
||||
--with-ssl-default-suites=openssl \
|
||||
%ifnarch loongarch64
|
||||
--with-valgrind \
|
||||
%endif
|
||||
--without-ensurepip \
|
||||
--with-pydebug
|
||||
|
||||
@ -405,7 +438,9 @@ pushd ${OptimizedBuildDir}
|
||||
--enable-loadable-sqlite-extensions \
|
||||
--with-dtrace \
|
||||
--with-ssl-default-suites=openssl \
|
||||
%ifnarch loongarch64
|
||||
--with-valgrind \
|
||||
%endif
|
||||
--without-ensurepip \
|
||||
%{optimizations_flag}
|
||||
|
||||
@ -937,14 +972,54 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP"
|
||||
%{_mandir}/*/*
|
||||
|
||||
%changelog
|
||||
* Fri Mar 01 GuoCe <guoce@kylinos.cn> - 3.7.9-37
|
||||
* Thu Dec 19 2024 wangshuo <wangshuo@kylinos.cn> - 3.7.9-43
|
||||
- Type:CVE
|
||||
- CVE:CVE-2024-9287
|
||||
- SUG:NA
|
||||
- DESC:fix CVE-2024-9287
|
||||
- gh-124651: Quote template strings in `venv` activation scripts
|
||||
|
||||
* Wed Dec 11 2024 wangshuo <wangshuo@kylinos.cn> - 3.7.9-42
|
||||
- Type:update
|
||||
- ID:NA
|
||||
- SUG:NA
|
||||
- DESC:support TLS_SM4
|
||||
|
||||
* Tue Oct 29 2024 wangshuo <wangshuo@kylinos.cn> - 3.7.9-41
|
||||
- Type:bugfix
|
||||
- ID:NA
|
||||
- SUG:NA
|
||||
- DESC:backport upstream patches
|
||||
- gh-104049: do not expose on-disk location from SimpleHTTPRequestHandler
|
||||
- gh-99889: Fix directory traversal security flaw in uu.decode()
|
||||
|
||||
* Fri Oct 25 2024 wangshuo <wangshuo@kylinos.cn> - 3.7.9-40
|
||||
- Type:CVE
|
||||
- CVE:CVE-2007-4559
|
||||
- SUG:NA
|
||||
- DESC:Patch9007-9008, fix CVE-2007-4559
|
||||
- Patch9009, fix test_xml_etree error with expat versions that fix CVE-2023-52425
|
||||
|
||||
* Wed Mar 06 2024 GuoCe <guoce@kylinos.cn> - 3.7.9-39
|
||||
- Type:bugfix
|
||||
- CVE:NA
|
||||
- SUG:NA
|
||||
- DESC: add loongarch64 support and disable valgrind-devel for loongarch64
|
||||
|
||||
* Tue Mar 05 2024 GuoCe <guoce@kylinos.cn> - 3.7.9-38
|
||||
- Type:CVE
|
||||
- CVE:CVE-2023-27043
|
||||
- SUG:NA
|
||||
- DESC:fix CVE-2023-27043
|
||||
|
||||
* Fri Mar 01 2024 GuoCe <guoce@kylinos.cn> - 3.7.9-37
|
||||
- Type:bugfix
|
||||
- CVE:NA
|
||||
- SUG:NA
|
||||
- DESC:Modify the spec file to synchronize the version information of
|
||||
CHANGLOG and VERSION.
|
||||
|
||||
* Tue Sep 19 zhuofeng <zhuofeng2@huawei.com> - 3.7.9-36
|
||||
* Tue Sep 19 2023 zhuofeng <zhuofeng2@huawei.com> - 3.7.9-36
|
||||
- Type:CVE
|
||||
- CVE:CVE-2023-40217
|
||||
- SUG:NA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user