From b70d3dfde91c8d3f28c810aa86aad82efe4fd578 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 30 Mar 2021 07:58:22 -0400 Subject: [PATCH] import python3-3.6.8-36.el8 --- SOURCES/00274-fix-arch-names.patch | 58 ---- ...-infinite-loop-in-the-tarfile-module.patch | 67 +++++ ...-for-ipv4interface-and-ipv6interface.patch | 70 +++++ ...chitecture-names-upstream-downstream.patch | 97 +++++++ ...est-method-crlf-injection-in-httplib.patch | 73 +++++ SOURCES/00355-CVE-2020-27619.patch | 42 +++ .../00356-k_and_a_options_for_pathfix.patch | 269 ++++++++++++++++++ SOURCES/00357-CVE-2021-3177.patch | 184 ++++++++++++ SPECS/python3.spec | 155 +++++++++- 9 files changed, 943 insertions(+), 72 deletions(-) delete mode 100644 SOURCES/00274-fix-arch-names.patch create mode 100644 SOURCES/00351-avoid-infinite-loop-in-the-tarfile-module.patch create mode 100644 SOURCES/00352-resolve-hash-collisions-for-ipv4interface-and-ipv6interface.patch create mode 100644 SOURCES/00353-architecture-names-upstream-downstream.patch create mode 100644 SOURCES/00354-cve-2020-26116-http-request-method-crlf-injection-in-httplib.patch create mode 100644 SOURCES/00355-CVE-2020-27619.patch create mode 100644 SOURCES/00356-k_and_a_options_for_pathfix.patch create mode 100644 SOURCES/00357-CVE-2021-3177.patch diff --git a/SOURCES/00274-fix-arch-names.patch b/SOURCES/00274-fix-arch-names.patch deleted file mode 100644 index 9d69223..0000000 --- a/SOURCES/00274-fix-arch-names.patch +++ /dev/null @@ -1,58 +0,0 @@ -diff -up Python-3.5.0/configure.ac.than Python-3.5.0/configure.ac ---- Python-3.5.0/configure.ac.than 2015-11-13 11:51:32.039560172 -0500 -+++ Python-3.5.0/configure.ac 2015-11-13 11:52:11.670168157 -0500 -@@ -788,9 +788,9 @@ cat >> conftest.c <> conftest.c <> conftest.c <> conftest.c < +Date: Wed, 15 Jul 2020 05:36:36 -0700 +Subject: [PATCH] 00351: Avoid infinite loop in the tarfile module + +Avoid infinite loop when reading specially crafted TAR files using the tarfile module +(CVE-2019-20907). +Fixed upstream: https://bugs.python.org/issue39017 +--- + Lib/tarfile.py | 2 ++ + Lib/test/recursion.tar | Bin 0 -> 516 bytes + Lib/test/test_tarfile.py | 7 +++++++ + .../2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst | 1 + + 4 files changed, 10 insertions(+) + create mode 100644 Lib/test/recursion.tar + create mode 100644 Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst + +diff --git a/Lib/tarfile.py b/Lib/tarfile.py +index 62d22150f5..2ea47978ff 100755 +--- a/Lib/tarfile.py ++++ b/Lib/tarfile.py +@@ -1231,6 +1231,8 @@ class TarInfo(object): + + length, keyword = match.groups() + length = int(length) ++ if length == 0: ++ raise InvalidHeaderError("invalid header") + value = buf[match.end(2) + 1:match.start(1) + length - 1] + + # Normally, we could just use "utf-8" as the encoding and "strict" +diff --git a/Lib/test/recursion.tar b/Lib/test/recursion.tar +new file mode 100644 +index 0000000000000000000000000000000000000000..b8237251964983f54ed1966297e887636cd0c5f4 +GIT binary patch +literal 516 +zcmYdFPRz+kEn=W0Fn}74P8%Xw3X=l~85kIuo0>8xq$A1Gm}!7)KUsFc41m#O8A5+e +I1_}|j06>QaCIA2c + +literal 0 +HcmV?d00001 + +diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py +index 4cd7d5370f..573be812ea 100644 +--- a/Lib/test/test_tarfile.py ++++ b/Lib/test/test_tarfile.py +@@ -395,6 +395,13 @@ class CommonReadTest(ReadTest): + with self.assertRaisesRegex(tarfile.ReadError, "unexpected end of data"): + tar.extractfile(t).read() + ++ def test_length_zero_header(self): ++ # bpo-39017 (CVE-2019-20907): reading a zero-length header should fail ++ # with an exception ++ with self.assertRaisesRegex(tarfile.ReadError, "file could not be opened successfully"): ++ with tarfile.open(support.findfile('recursion.tar')) as tar: ++ pass ++ + class MiscReadTestBase(CommonReadTest): + def requires_name_attribute(self): + pass +diff --git a/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst +new file mode 100644 +index 0000000000..ad26676f8b +--- /dev/null ++++ b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst +@@ -0,0 +1 @@ ++Avoid infinite loop when reading specially crafted TAR files using the tarfile module (CVE-2019-20907). diff --git a/SOURCES/00352-resolve-hash-collisions-for-ipv4interface-and-ipv6interface.patch b/SOURCES/00352-resolve-hash-collisions-for-ipv4interface-and-ipv6interface.patch new file mode 100644 index 0000000..c01a42e --- /dev/null +++ b/SOURCES/00352-resolve-hash-collisions-for-ipv4interface-and-ipv6interface.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tapas Kundu <39723251+tapakund@users.noreply.github.com> +Date: Wed, 1 Jul 2020 01:00:22 +0530 +Subject: [PATCH] 00352: Resolve hash collisions for IPv4Interface and + IPv6Interface + +CVE-2020-14422 +The hash() methods of classes IPv4Interface and IPv6Interface had issue +of generating constant hash values of 32 and 128 respectively causing hash collisions. +The fix uses the hash() function to generate hash values for the objects +instead of XOR operation. +Fixed upstream: https://bugs.python.org/issue41004 +--- + Lib/ipaddress.py | 4 ++-- + Lib/test/test_ipaddress.py | 11 +++++++++++ + .../Security/2020-06-29-16-02-29.bpo-41004.ovF0KZ.rst | 1 + + 3 files changed, 14 insertions(+), 2 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2020-06-29-16-02-29.bpo-41004.ovF0KZ.rst + +diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py +index 583f02ad54..98492136ca 100644 +--- a/Lib/ipaddress.py ++++ b/Lib/ipaddress.py +@@ -1418,7 +1418,7 @@ class IPv4Interface(IPv4Address): + return False + + def __hash__(self): +- return self._ip ^ self._prefixlen ^ int(self.network.network_address) ++ return hash((self._ip, self._prefixlen, int(self.network.network_address))) + + __reduce__ = _IPAddressBase.__reduce__ + +@@ -2092,7 +2092,7 @@ class IPv6Interface(IPv6Address): + return False + + def __hash__(self): +- return self._ip ^ self._prefixlen ^ int(self.network.network_address) ++ return hash((self._ip, self._prefixlen, int(self.network.network_address))) + + __reduce__ = _IPAddressBase.__reduce__ + +diff --git a/Lib/test/test_ipaddress.py b/Lib/test/test_ipaddress.py +index 1cef4217bc..7de444af4a 100644 +--- a/Lib/test/test_ipaddress.py ++++ b/Lib/test/test_ipaddress.py +@@ -1990,6 +1990,17 @@ class IpaddrUnitTest(unittest.TestCase): + sixtofouraddr.sixtofour) + self.assertFalse(bad_addr.sixtofour) + ++ # issue41004 Hash collisions in IPv4Interface and IPv6Interface ++ def testV4HashIsNotConstant(self): ++ ipv4_address1 = ipaddress.IPv4Interface("1.2.3.4") ++ ipv4_address2 = ipaddress.IPv4Interface("2.3.4.5") ++ self.assertNotEqual(ipv4_address1.__hash__(), ipv4_address2.__hash__()) ++ ++ # issue41004 Hash collisions in IPv4Interface and IPv6Interface ++ def testV6HashIsNotConstant(self): ++ ipv6_address1 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:1") ++ ipv6_address2 = ipaddress.IPv6Interface("2001:658:22a:cafe:200:0:0:2") ++ self.assertNotEqual(ipv6_address1.__hash__(), ipv6_address2.__hash__()) + + if __name__ == '__main__': + unittest.main() +diff --git a/Misc/NEWS.d/next/Security/2020-06-29-16-02-29.bpo-41004.ovF0KZ.rst b/Misc/NEWS.d/next/Security/2020-06-29-16-02-29.bpo-41004.ovF0KZ.rst +new file mode 100644 +index 0000000000..f5a9db52ff +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2020-06-29-16-02-29.bpo-41004.ovF0KZ.rst +@@ -0,0 +1 @@ ++CVE-2020-14422: The __hash__() methods of ipaddress.IPv4Interface and ipaddress.IPv6Interface incorrectly generated constant hash values of 32 and 128 respectively. This resulted in always causing hash collisions. The fix uses hash() to generate hash values for the tuple of (address, mask length, network address). diff --git a/SOURCES/00353-architecture-names-upstream-downstream.patch b/SOURCES/00353-architecture-names-upstream-downstream.patch new file mode 100644 index 0000000..13f2655 --- /dev/null +++ b/SOURCES/00353-architecture-names-upstream-downstream.patch @@ -0,0 +1,97 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Tue, 4 Aug 2020 12:04:03 +0200 +Subject: [PATCH] 00353: Original names for architectures with different names + downstream +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +https://fedoraproject.org/wiki/Changes/Python_Upstream_Architecture_Names + +Pythons in RHEL/Fedora used different names for some architectures +than upstream and other distros (for example ppc64 vs. powerpc64). +This was patched in patch 274, now it is sedded if %with legacy_archnames. + +That meant that an extension built with the default upstream settings +(on other distro or as an manylinux wheel) could not been found by Python +on RHEL/Fedora because it had a different suffix. +This patch adds the legacy names to importlib so Python is able +to import extensions with a legacy architecture name in its +file name. +It work both ways, so it support both %with and %without legacy_archnames. + +WARNING: This patch has no effect on Python built with bootstrap +enabled because Python/importlib_external.h is not regenerated +and therefore Python during bootstrap contains importlib from +upstream without this feature. It's possible to include +Python/importlib_external.h to this patch but it'd make rebasing +a nightmare because it's basically a binary file. + +Co-authored-by: Miro Hrončok +--- + Lib/importlib/_bootstrap_external.py | 40 ++++++++++++++++++++++++++-- + 1 file changed, 38 insertions(+), 2 deletions(-) + +diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py +index 9feec50842..5bb2454a5c 100644 +--- a/Lib/importlib/_bootstrap_external.py ++++ b/Lib/importlib/_bootstrap_external.py +@@ -1361,7 +1361,7 @@ def _get_supported_file_loaders(): + + Each item is a tuple (loader, suffixes). + """ +- extensions = ExtensionFileLoader, _imp.extension_suffixes() ++ extensions = ExtensionFileLoader, _alternative_architectures(_imp.extension_suffixes()) + source = SourceFileLoader, SOURCE_SUFFIXES + bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES + return [extensions, source, bytecode] +@@ -1428,7 +1428,7 @@ def _setup(_bootstrap_module): + + # Constants + setattr(self_module, '_relax_case', _make_relax_case()) +- EXTENSION_SUFFIXES.extend(_imp.extension_suffixes()) ++ EXTENSION_SUFFIXES.extend(_alternative_architectures(_imp.extension_suffixes())) + if builtin_os == 'nt': + SOURCE_SUFFIXES.append('.pyw') + if '_d.pyd' in EXTENSION_SUFFIXES: +@@ -1441,3 +1441,39 @@ def _install(_bootstrap_module): + supported_loaders = _get_supported_file_loaders() + sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)]) + sys.meta_path.append(PathFinder) ++ ++ ++_ARCH_MAP = { ++ "-arm-linux-gnueabi.": "-arm-linux-gnueabihf.", ++ "-armeb-linux-gnueabi.": "-armeb-linux-gnueabihf.", ++ "-mips64-linux-gnu.": "-mips64-linux-gnuabi64.", ++ "-mips64el-linux-gnu.": "-mips64el-linux-gnuabi64.", ++ "-ppc-linux-gnu.": "-powerpc-linux-gnu.", ++ "-ppc-linux-gnuspe.": "-powerpc-linux-gnuspe.", ++ "-ppc64-linux-gnu.": "-powerpc64-linux-gnu.", ++ "-ppc64le-linux-gnu.": "-powerpc64le-linux-gnu.", ++ # The above, but the other way around: ++ "-arm-linux-gnueabihf.": "-arm-linux-gnueabi.", ++ "-armeb-linux-gnueabihf.": "-armeb-linux-gnueabi.", ++ "-mips64-linux-gnuabi64.": "-mips64-linux-gnu.", ++ "-mips64el-linux-gnuabi64.": "-mips64el-linux-gnu.", ++ "-powerpc-linux-gnu.": "-ppc-linux-gnu.", ++ "-powerpc-linux-gnuspe.": "-ppc-linux-gnuspe.", ++ "-powerpc64-linux-gnu.": "-ppc64-linux-gnu.", ++ "-powerpc64le-linux-gnu.": "-ppc64le-linux-gnu.", ++} ++ ++ ++def _alternative_architectures(suffixes): ++ """Add a suffix with an alternative architecture name ++ to the list of suffixes so an extension built with ++ the default (upstream) setting is loadable with our Pythons ++ """ ++ ++ for suffix in suffixes: ++ for original, alternative in _ARCH_MAP.items(): ++ if original in suffix: ++ suffixes.append(suffix.replace(original, alternative)) ++ return suffixes ++ ++ return suffixes diff --git a/SOURCES/00354-cve-2020-26116-http-request-method-crlf-injection-in-httplib.patch b/SOURCES/00354-cve-2020-26116-http-request-method-crlf-injection-in-httplib.patch new file mode 100644 index 0000000..aa0b385 --- /dev/null +++ b/SOURCES/00354-cve-2020-26116-http-request-method-crlf-injection-in-httplib.patch @@ -0,0 +1,73 @@ +diff --git a/Lib/http/client.py b/Lib/http/client.py +index f0d2642..0a044e9 100644 +--- a/Lib/http/client.py ++++ b/Lib/http/client.py +@@ -151,6 +151,10 @@ _contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') + # _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") + # We are more lenient for assumed real world compatibility purposes. + ++# These characters are not allowed within HTTP method names ++# to prevent http header injection. ++_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') ++ + # We always set the Content-Length header for these methods because some + # servers will otherwise respond with a 411 + _METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} +@@ -1117,6 +1121,8 @@ class HTTPConnection: + else: + raise CannotSendRequest(self.__state) + ++ self._validate_method(method) ++ + # Save the method we use, we need it later in the response phase + self._method = method + if not url: +@@ -1207,6 +1213,15 @@ class HTTPConnection: + # For HTTP/1.0, the server will assume "not chunked" + pass + ++ def _validate_method(self, method): ++ """Validate a method name for putrequest.""" ++ # prevent http header injection ++ match = _contains_disallowed_method_pchar_re.search(method) ++ if match: ++ raise ValueError( ++ f"method can't contain control characters. {method!r} " ++ f"(found at least {match.group()!r})") ++ + def putheader(self, header, *values): + """Send a request header line to the server. + +diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py +index 5795b7a..af0350f 100644 +--- a/Lib/test/test_httplib.py ++++ b/Lib/test/test_httplib.py +@@ -359,6 +359,28 @@ class HeaderTests(TestCase): + self.assertEqual(lines[2], "header: Second: val") + + ++class HttpMethodTests(TestCase): ++ def test_invalid_method_names(self): ++ methods = ( ++ 'GET\r', ++ 'POST\n', ++ 'PUT\n\r', ++ 'POST\nValue', ++ 'POST\nHOST:abc', ++ 'GET\nrHost:abc\n', ++ 'POST\rRemainder:\r', ++ 'GET\rHOST:\n', ++ '\nPUT' ++ ) ++ ++ for method in methods: ++ with self.assertRaisesRegex( ++ ValueError, "method can't contain control characters"): ++ conn = client.HTTPConnection('example.com') ++ conn.sock = FakeSocket(None) ++ conn.request(method=method, url="/") ++ ++ + class TransferEncodingTest(TestCase): + expected_body = b"It's just a flesh wound" + diff --git a/SOURCES/00355-CVE-2020-27619.patch b/SOURCES/00355-CVE-2020-27619.patch new file mode 100644 index 0000000..6a4082d --- /dev/null +++ b/SOURCES/00355-CVE-2020-27619.patch @@ -0,0 +1,42 @@ +diff --git a/Lib/test/multibytecodec_support.py b/Lib/test/multibytecodec_support.py +index f9884c6..98feec2 100644 +--- a/Lib/test/multibytecodec_support.py ++++ b/Lib/test/multibytecodec_support.py +@@ -300,29 +300,23 @@ class TestBase_Mapping(unittest.TestCase): + self._test_mapping_file_plain() + + def _test_mapping_file_plain(self): +- unichrs = lambda s: ''.join(map(chr, map(eval, s.split('+')))) ++ def unichrs(s): ++ return ''.join(chr(int(x, 16)) for x in s.split('+')) ++ + urt_wa = {} + + with self.open_mapping_file() as f: + for line in f: + if not line: + break +- data = line.split('#')[0].strip().split() ++ data = line.split('#')[0].split() + if len(data) != 2: + continue + +- csetval = eval(data[0]) +- if csetval <= 0x7F: +- csetch = bytes([csetval & 0xff]) +- elif csetval >= 0x1000000: +- csetch = bytes([(csetval >> 24), ((csetval >> 16) & 0xff), +- ((csetval >> 8) & 0xff), (csetval & 0xff)]) +- elif csetval >= 0x10000: +- csetch = bytes([(csetval >> 16), ((csetval >> 8) & 0xff), +- (csetval & 0xff)]) +- elif csetval >= 0x100: +- csetch = bytes([(csetval >> 8), (csetval & 0xff)]) +- else: ++ if data[0][:2] != '0x': ++ self.fail(f"Invalid line: {line!r}") ++ csetch = bytes.fromhex(data[0][2:]) ++ if len(csetch) == 1 and 0x80 <= csetch[0]: + continue + + unich = unichrs(data[1]) diff --git a/SOURCES/00356-k_and_a_options_for_pathfix.patch b/SOURCES/00356-k_and_a_options_for_pathfix.patch new file mode 100644 index 0000000..3782e6e --- /dev/null +++ b/SOURCES/00356-k_and_a_options_for_pathfix.patch @@ -0,0 +1,269 @@ +From 0cfd9a7f26488567b9a3e5ec192099a8b80ad9df Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Tue, 19 Jan 2021 07:55:37 +0100 +Subject: [PATCH] [PATCH] bpo-37064: Add -k and -a options to pathfix.py tool + (GH-16387) + +* bpo-37064: Add option -k to Tools/scripts/pathfix.py (GH-15548) + +Add flag -k to pathscript.py script: preserve shebang flags. + +(cherry picked from commit 50254ac4c179cb412e90682098c97db786143929) + +* bpo-37064: Add option -a to pathfix.py tool (GH-15717) + +Add option -a to Tools/Scripts/pathfix.py script: add flags. + +(cherry picked from commit 1dc1acbd73f05f14c974b7ce1041787d7abef31e) +--- + Lib/test/test_tools/test_pathfix.py | 104 ++++++++++++++++++++++++++++ + Tools/scripts/pathfix.py | 64 +++++++++++++++-- + 2 files changed, 163 insertions(+), 5 deletions(-) + create mode 100644 Lib/test/test_tools/test_pathfix.py + +diff --git a/Lib/test/test_tools/test_pathfix.py b/Lib/test/test_tools/test_pathfix.py +new file mode 100644 +index 0000000..1f0585e +--- /dev/null ++++ b/Lib/test/test_tools/test_pathfix.py +@@ -0,0 +1,104 @@ ++import os ++import subprocess ++import sys ++import unittest ++from test import support ++from test.test_tools import import_tool, scriptsdir ++ ++ ++class TestPathfixFunctional(unittest.TestCase): ++ script = os.path.join(scriptsdir, 'pathfix.py') ++ ++ def setUp(self): ++ self.temp_file = support.TESTFN ++ self.addCleanup(support.unlink, support.TESTFN) ++ ++ def pathfix(self, shebang, pathfix_flags, exitcode=0, stdout='', stderr=''): ++ with open(self.temp_file, 'w', encoding='utf8') as f: ++ f.write(f'{shebang}\n' + 'print("Hello world")\n') ++ ++ proc = subprocess.run( ++ [sys.executable, self.script, ++ *pathfix_flags, '-n', self.temp_file], ++ universal_newlines=True, stdout=subprocess.PIPE, ++ stderr=subprocess.PIPE) ++ ++ if stdout == '' and proc.returncode == 0: ++ stdout = f'{self.temp_file}: updating\n' ++ self.assertEqual(proc.returncode, exitcode, proc) ++ self.assertEqual(proc.stdout, stdout, proc) ++ self.assertEqual(proc.stderr, stderr, proc) ++ ++ with open(self.temp_file, 'r', encoding='utf8') as f: ++ output = f.read() ++ ++ lines = output.split('\n') ++ self.assertEqual(lines[1:], ['print("Hello world")', '']) ++ new_shebang = lines[0] ++ ++ if proc.returncode != 0: ++ self.assertEqual(shebang, new_shebang) ++ ++ return new_shebang ++ ++ def test_pathfix(self): ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python', ++ ['-i', '/usr/bin/python3']), ++ '#! /usr/bin/python3') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python -R', ++ ['-i', '/usr/bin/python3']), ++ '#! /usr/bin/python3') ++ ++ def test_pathfix_keeping_flags(self): ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python -R', ++ ['-i', '/usr/bin/python3', '-k']), ++ '#! /usr/bin/python3 -R') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python', ++ ['-i', '/usr/bin/python3', '-k']), ++ '#! /usr/bin/python3') ++ ++ def test_pathfix_adding_flag(self): ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python', ++ ['-i', '/usr/bin/python3', '-a', 's']), ++ '#! /usr/bin/python3 -s') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python -S', ++ ['-i', '/usr/bin/python3', '-a', 's']), ++ '#! /usr/bin/python3 -s') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python -V', ++ ['-i', '/usr/bin/python3', '-a', 'v', '-k']), ++ '#! /usr/bin/python3 -vV') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python', ++ ['-i', '/usr/bin/python3', '-a', 'Rs']), ++ '#! /usr/bin/python3 -Rs') ++ self.assertEqual( ++ self.pathfix( ++ '#! /usr/bin/env python -W default', ++ ['-i', '/usr/bin/python3', '-a', 's', '-k']), ++ '#! /usr/bin/python3 -sW default') ++ ++ def test_pathfix_adding_errors(self): ++ self.pathfix( ++ '#! /usr/bin/env python -E', ++ ['-i', '/usr/bin/python3', '-a', 'W default', '-k'], ++ exitcode=2, ++ stderr="-a option doesn't support whitespaces") ++ ++ ++if __name__ == '__main__': ++ unittest.main() +diff --git a/Tools/scripts/pathfix.py b/Tools/scripts/pathfix.py +index c5bf984..2dfa6e8 100755 +--- a/Tools/scripts/pathfix.py ++++ b/Tools/scripts/pathfix.py +@@ -1,6 +1,6 @@ + #!/usr/bin/env python3 + +-# Change the #! line occurring in Python scripts. The new interpreter ++# Change the #! line (shebang) occurring in Python scripts. The new interpreter + # pathname must be given with a -i option. + # + # Command line arguments are files or directories to be processed. +@@ -10,7 +10,13 @@ + # arguments). + # The original file is kept as a back-up (with a "~" attached to its name), + # -n flag can be used to disable this. +-# ++ ++# Sometimes you may find shebangs with flags such as `#! /usr/bin/env python -si`. ++# Normally, pathfix overwrites the entire line, including the flags. ++# To change interpreter and keep flags from the original shebang line, use -k. ++# If you want to keep flags and add to them one single literal flag, use option -a. ++ ++ + # Undoubtedly you can do this using find and sed or perl, but this is + # a nice example of Python code that recurses down a directory tree + # and uses regular expressions. Also note several subtleties like +@@ -33,16 +39,21 @@ rep = sys.stdout.write + new_interpreter = None + preserve_timestamps = False + create_backup = True ++keep_flags = False ++add_flags = b'' + + + def main(): + global new_interpreter + global preserve_timestamps + global create_backup +- usage = ('usage: %s -i /interpreter -p -n file-or-directory ...\n' % ++ global keep_flags ++ global add_flags ++ ++ usage = ('usage: %s -i /interpreter -p -n -k -a file-or-directory ...\n' % + sys.argv[0]) + try: +- opts, args = getopt.getopt(sys.argv[1:], 'i:pn') ++ opts, args = getopt.getopt(sys.argv[1:], 'i:a:kpn') + except getopt.error as msg: + err(str(msg) + '\n') + err(usage) +@@ -54,6 +65,13 @@ def main(): + preserve_timestamps = True + if o == '-n': + create_backup = False ++ if o == '-k': ++ keep_flags = True ++ if o == '-a': ++ add_flags = a.encode() ++ if b' ' in add_flags: ++ err("-a option doesn't support whitespaces") ++ sys.exit(2) + if not new_interpreter or not new_interpreter.startswith(b'/') or \ + not args: + err('-i option or file-or-directory missing\n') +@@ -70,10 +88,14 @@ def main(): + if fix(arg): bad = 1 + sys.exit(bad) + ++ + ispythonprog = re.compile(r'^[a-zA-Z0-9_]+\.py$') ++ ++ + def ispython(name): + return bool(ispythonprog.match(name)) + ++ + def recursedown(dirname): + dbg('recursedown(%r)\n' % (dirname,)) + bad = 0 +@@ -96,6 +118,7 @@ def recursedown(dirname): + if recursedown(fullname): bad = 1 + return bad + ++ + def fix(filename): + ## dbg('fix(%r)\n' % (filename,)) + try: +@@ -166,12 +189,43 @@ def fix(filename): + # Return success + return 0 + ++ ++def parse_shebang(shebangline): ++ shebangline = shebangline.rstrip(b'\n') ++ start = shebangline.find(b' -') ++ if start == -1: ++ return b'' ++ return shebangline[start:] ++ ++ ++def populate_flags(shebangline): ++ old_flags = b'' ++ if keep_flags: ++ old_flags = parse_shebang(shebangline) ++ if old_flags: ++ old_flags = old_flags[2:] ++ if not (old_flags or add_flags): ++ return b'' ++ # On Linux, the entire string following the interpreter name ++ # is passed as a single argument to the interpreter. ++ # e.g. "#! /usr/bin/python3 -W Error -s" runs "/usr/bin/python3 "-W Error -s" ++ # so shebang should have single '-' where flags are given and ++ # flag might need argument for that reasons adding new flags is ++ # between '-' and original flags ++ # e.g. #! /usr/bin/python3 -sW Error ++ return b' -' + add_flags + old_flags ++ ++ + def fixline(line): + if not line.startswith(b'#!'): + return line ++ + if b"python" not in line: + return line +- return b'#! ' + new_interpreter + b'\n' ++ ++ flags = populate_flags(line) ++ return b'#! ' + new_interpreter + flags + b'\n' ++ + + if __name__ == '__main__': + main() +-- +2.29.2 + diff --git a/SOURCES/00357-CVE-2021-3177.patch b/SOURCES/00357-CVE-2021-3177.patch new file mode 100644 index 0000000..339e1b5 --- /dev/null +++ b/SOURCES/00357-CVE-2021-3177.patch @@ -0,0 +1,184 @@ +From e92381a0a6a3e1f000956e1f1e70e543b9c2bcd5 Mon Sep 17 00:00:00 2001 +From: Benjamin Peterson +Date: Mon, 18 Jan 2021 14:47:05 -0600 +Subject: [PATCH] [3.6] closes bpo-42938: Replace snprintf with Python unicode + formatting in ctypes param reprs. (24239). (cherry picked from commit + 916610ef90a0d0761f08747f7b0905541f0977c7) + +Co-authored-by: Benjamin Peterson +--- + Lib/ctypes/test/test_parameters.py | 43 +++++++++++++++ + .../2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst | 2 + + Modules/_ctypes/callproc.c | 55 +++++++------------ + 3 files changed, 66 insertions(+), 34 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst + +diff --git a/Lib/ctypes/test/test_parameters.py b/Lib/ctypes/test/test_parameters.py +index e4c25fd880cef..531894fdec838 100644 +--- a/Lib/ctypes/test/test_parameters.py ++++ b/Lib/ctypes/test/test_parameters.py +@@ -201,6 +201,49 @@ def __dict__(self): + with self.assertRaises(ZeroDivisionError): + WorseStruct().__setstate__({}, b'foo') + ++ def test_parameter_repr(self): ++ from ctypes import ( ++ c_bool, ++ c_char, ++ c_wchar, ++ c_byte, ++ c_ubyte, ++ c_short, ++ c_ushort, ++ c_int, ++ c_uint, ++ c_long, ++ c_ulong, ++ c_longlong, ++ c_ulonglong, ++ c_float, ++ c_double, ++ c_longdouble, ++ c_char_p, ++ c_wchar_p, ++ c_void_p, ++ ) ++ self.assertRegex(repr(c_bool.from_param(True)), r"^$") ++ self.assertEqual(repr(c_char.from_param(97)), "") ++ self.assertRegex(repr(c_wchar.from_param('a')), r"^$") ++ self.assertEqual(repr(c_byte.from_param(98)), "") ++ self.assertEqual(repr(c_ubyte.from_param(98)), "") ++ self.assertEqual(repr(c_short.from_param(511)), "") ++ self.assertEqual(repr(c_ushort.from_param(511)), "") ++ self.assertRegex(repr(c_int.from_param(20000)), r"^$") ++ self.assertRegex(repr(c_uint.from_param(20000)), r"^$") ++ self.assertRegex(repr(c_long.from_param(20000)), r"^$") ++ self.assertRegex(repr(c_ulong.from_param(20000)), r"^$") ++ self.assertRegex(repr(c_longlong.from_param(20000)), r"^$") ++ self.assertRegex(repr(c_ulonglong.from_param(20000)), r"^$") ++ self.assertEqual(repr(c_float.from_param(1.5)), "") ++ self.assertEqual(repr(c_double.from_param(1.5)), "") ++ self.assertEqual(repr(c_double.from_param(1e300)), "") ++ self.assertRegex(repr(c_longdouble.from_param(1.5)), r"^$") ++ self.assertRegex(repr(c_char_p.from_param(b'hihi')), "^$") ++ self.assertRegex(repr(c_wchar_p.from_param('hihi')), "^$") ++ self.assertRegex(repr(c_void_p.from_param(0x12)), r"^$") ++ + ################################################################ + + if __name__ == '__main__': +diff --git a/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst b/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst +new file mode 100644 +index 0000000000000..7df65a156feab +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2021-01-18-09-27-31.bpo-42938.4Zn4Mp.rst +@@ -0,0 +1,2 @@ ++Avoid static buffers when computing the repr of :class:`ctypes.c_double` and ++:class:`ctypes.c_longdouble` values. +diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c +index d1c190f359108..2bb289bce043f 100644 +--- a/Modules/_ctypes/callproc.c ++++ b/Modules/_ctypes/callproc.c +@@ -461,58 +461,47 @@ is_literal_char(unsigned char c) + static PyObject * + PyCArg_repr(PyCArgObject *self) + { +- char buffer[256]; + switch(self->tag) { + case 'b': + case 'B': +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.b); +- break; + case 'h': + case 'H': +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.h); +- break; + case 'i': + case 'I': +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.i); +- break; + case 'l': + case 'L': +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.l); +- break; + + case 'q': + case 'Q': +- sprintf(buffer, +-#ifdef MS_WIN32 +- "", +-#else +- "", +-#endif ++ return PyUnicode_FromFormat("", + self->tag, self->value.q); +- break; + case 'd': +- sprintf(buffer, "", +- self->tag, self->value.d); +- break; +- case 'f': +- sprintf(buffer, "", +- self->tag, self->value.f); +- break; +- ++ case 'f': { ++ PyObject *f = PyFloat_FromDouble((self->tag == 'f') ? self->value.f : self->value.d); ++ if (f == NULL) { ++ return NULL; ++ } ++ PyObject *result = PyUnicode_FromFormat("", self->tag, f); ++ Py_DECREF(f); ++ return result; ++ } + case 'c': + if (is_literal_char((unsigned char)self->value.c)) { +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.c); + } + else { +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, (unsigned char)self->value.c); + } +- break; + + /* Hm, are these 'z' and 'Z' codes useful at all? + Shouldn't they be replaced by the functionality of c_string +@@ -521,22 +510,20 @@ PyCArg_repr(PyCArgObject *self) + case 'z': + case 'Z': + case 'P': +- sprintf(buffer, "", ++ return PyUnicode_FromFormat("", + self->tag, self->value.p); + break; + + default: + if (is_literal_char((unsigned char)self->tag)) { +- sprintf(buffer, "", +- (unsigned char)self->tag, self); ++ return PyUnicode_FromFormat("", ++ (unsigned char)self->tag, (void *)self); + } + else { +- sprintf(buffer, "", +- (unsigned char)self->tag, self); ++ return PyUnicode_FromFormat("", ++ (unsigned char)self->tag, (void *)self); + } +- break; + } +- return PyUnicode_FromString(buffer); + } + + static PyMemberDef PyCArgType_members[] = { diff --git a/SPECS/python3.spec b/SPECS/python3.spec index 056dc0f..9d0b8b7 100644 --- a/SPECS/python3.spec +++ b/SPECS/python3.spec @@ -14,7 +14,7 @@ URL: https://www.python.org/ # WARNING When rebasing to a new Python version, # remember to update the python3-docs package as well Version: %{pybasever}.8 -Release: 30%{?dist} +Release: 36%{?dist} License: Python @@ -56,6 +56,15 @@ License: Python %bcond_with valgrind %endif +# https://fedoraproject.org/wiki/Changes/Python_Upstream_Architecture_Names +# For a very long time we have converted "upstream architecture names" to "Fedora names". +# This made sense at the time, see https://github.com/pypa/manylinux/issues/687#issuecomment-666362947 +# However, with manylinux wheels popularity growth, this is now a problem. +# Wheels built on a Linux that doesn't do this were not compatible with ours and vice versa. +# We now have a compatibility layer to workaround a problem, +# but we also no longer use the legacy arch names in Fedora 34+. +# This bcond controls the behavior. The defaults should be good for anybody. +%bcond_without legacy_archnames # ================================== # Notes from bootstraping Python 3.6 @@ -111,8 +120,21 @@ License: Python %global LDVERSION_optimized %{pybasever}%{ABIFLAGS_optimized} %global LDVERSION_debug %{pybasever}%{ABIFLAGS_debug} -%global SOABI_optimized cpython-%{pyshortver}%{ABIFLAGS_optimized}-%{_arch}-linux%{_gnu} -%global SOABI_debug cpython-%{pyshortver}%{ABIFLAGS_debug}-%{_arch}-linux%{_gnu} +# When we use the upstream arch triplets, we convert them from the legacy ones +# This is reversed in prep when %%with legacy_archnames, so we keep both macros +%global platform_triplet_legacy %{_arch}-linux%{_gnu} +%global platform_triplet_upstream %{expand:%(echo %{platform_triplet_legacy} | sed -E \\ + -e 's/^arm(eb)?-linux-gnueabi$/arm\\1-linux-gnueabihf/' \\ + -e 's/^mips64(el)?-linux-gnu$/mips64\\1-linux-gnuabi64/' \\ + -e 's/^ppc(64)?(le)?-linux-gnu$/powerpc\\1\\2-linux-gnu/')} +%if %{with legacy_archnames} +%global platform_triplet %{platform_triplet_legacy} +%else +%global platform_triplet %{platform_triplet_upstream} +%endif + +%global SOABI_optimized cpython-%{pyshortver}%{ABIFLAGS_optimized}-%{platform_triplet} +%global SOABI_debug cpython-%{pyshortver}%{ABIFLAGS_debug}-%{platform_triplet} # All bytecode files are in a __pycache__ subdirectory, with a name # reflecting the version of the bytecode. @@ -170,6 +192,7 @@ BuildRequires: gcc-c++ %if %{with gdbm} BuildRequires: gdbm-devel >= 1:1.13 %endif +BuildRequires: git-core BuildRequires: glibc-devel BuildRequires: gmp-devel BuildRequires: libappstream-glib @@ -328,10 +351,6 @@ Patch251: 00251-change-user-install-location.patch # Original proposal: https://bugzilla.redhat.com/show_bug.cgi?id=1404918 Patch262: 00262-pep538_coerce_legacy_c_locale.patch -# 00274 # -# Upstream uses Debian-style architecture naming. Change to match Fedora. -Patch274: 00274-fix-arch-names.patch - # 00294 # # Define TLS cipher suite on build time depending # on the OpenSSL default cipher suite selection. @@ -501,6 +520,70 @@ Patch345: 00345-fix-test_site-with-extra-pth-files.patch # Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1810618 Patch346: 00346-CVE-2020-8492.patch +# 00351 # +# Avoid infinite loop when reading specially crafted TAR files using the tarfile module +# (CVE-2019-20907). +# See: https://bugs.python.org/issue39017 +Patch351: 00351-avoid-infinite-loop-in-the-tarfile-module.patch + +# 00352 # 5253c417a23b3658fa115d2c72fa54b20293a31c +# Resolve hash collisions for IPv4Interface and IPv6Interface +# +# CVE-2020-14422 +# The hash() methods of classes IPv4Interface and IPv6Interface had issue +# of generating constant hash values of 32 and 128 respectively causing hash collisions. +# The fix uses the hash() function to generate hash values for the objects +# instead of XOR operation. +# Fixed upstream: https://bugs.python.org/issue41004 +Patch352: 00352-resolve-hash-collisions-for-ipv4interface-and-ipv6interface.patch + +# 00353 # +# Original names for architectures with different names downstream +# +# https://fedoraproject.org/wiki/Changes/Python_Upstream_Architecture_Names +# +# Pythons in RHEL/Fedora used different names for some architectures +# than upstream and other distros (for example ppc64 vs. powerpc64). +# This was patched in patch 274, now it is sedded if %%with legacy_archnames. +# +# That meant that an extension built with the default upstream settings +# (on other distro or as an manylinux wheel) could not been found by Python +# on RHEL/Fedora because it had a different suffix. +# This patch adds the legacy names to importlib so Python is able +# to import extensions with a legacy architecture name in its +# file name. +# It work both ways, so it support both %%with and %%without legacy_archnames. +# +# WARNING: This patch has no effect on Python built with bootstrap +# enabled because Python/importlib_external.h is not regenerated +# and therefore Python during bootstrap contains importlib from +# upstream without this feature. It's possible to include +# Python/importlib_external.h to this patch but it'd make rebasing +# a nightmare because it's basically a binary file. +Patch353: 00353-architecture-names-upstream-downstream.patch + +# 00354 # +# Reject control chars in HTTP method in http.client to prevent +# HTTP header injection +# Fixed ustream: https://bugs.python.org/issue39603 +Patch354: 00354-cve-2020-26116-http-request-method-crlf-injection-in-httplib.patch + +# 00355 # +# No longer call eval() on content received via HTTP in the CJK codec tests +# Fixed upstream: https://bugs.python.org/issue41944 +Patch355: 00355-CVE-2020-27619.patch + +# 00356 # +# options -a and -k for pathfix.py used in %%py3_shebang_fix +# Upstream: https://github.com/python/cpython/commit/c71c54c62600fd721baed3c96709e3d6e9c33817 +Patch356: 00356-k_and_a_options_for_pathfix.patch + +# 00357 # +# CVE-2021-3177 stack-based buffer overflow in PyCArg_repr in _ctypes/callproc.c +# Upstream: https://bugs.python.org/issue42938 +# Main BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1918168 +Patch357: 00357-CVE-2021-3177.patch + # (New patches go here ^^^) # # When adding new patches to "python" and "python3" in Fedora, EL, etc., @@ -799,7 +882,6 @@ rm Lib/ensurepip/_bundled/*.whl %patch251 -p1 %patch262 -p1 -%patch274 -p1 %patch294 -p1 %patch316 -p1 %patch317 -p1 @@ -819,11 +901,26 @@ rm Lib/ensurepip/_bundled/*.whl %patch345 -p1 %patch346 -p1 +# Patch 351 adds binary file for testing. We need to apply it using Git. +git apply %{PATCH351} + +%patch352 -p1 +%patch353 -p1 +%patch354 -p1 +%patch355 -p1 +%patch356 -p1 +%patch357 -p1 # Remove files that should be generated by the build # (This is after patching, so that we can use patches directly from upstream) rm configure pyconfig.h.in +# When we use the legacy arch names, we need to change them in configure.ac +%if %{with legacy_archnames} +sed -i configure.ac \ + -e 's/\b%{platform_triplet_upstream}\b/%{platform_triplet_legacy}/' +%endif + # ====================================================== # Configuring and building the code: @@ -904,6 +1001,9 @@ BuildPython() { $ExtraConfigArgs \ %{nil} + # Regenerate generated importlib frozen modules (see patch 353) + %make_build CFLAGS_NODIST="$CFLAGS_NODIST $MoreCFlags" regen-importlib + # Invoke the build %make_build CFLAGS_NODIST="$CFLAGS_NODIST $MoreCFlags" @@ -1103,7 +1203,7 @@ do LD_LIBRARY_PATH=./build/optimized ./build/optimized/python \ Tools/scripts/pathfix.py \ -i "%{_libexecdir}/platform-python${LDVersion}" -pn \ - %{buildroot}%{pylibdir}/config-${LDVersion}-%{_arch}-linux%{_gnu}/python-config.py + %{buildroot}%{pylibdir}/config-${LDVersion}-%{platform_triplet}/python-config.py done # Remove tests for python3-tools which was removed in @@ -1523,8 +1623,8 @@ fi # "Makefile" and the config-32/64.h file are needed by # distutils/sysconfig.py:_init_posix(), so we include them in the core # package, along with their parent directories (bug 531901): -%dir %{pylibdir}/config-%{LDVERSION_optimized}-%{_arch}-linux%{_gnu}/ -%{pylibdir}/config-%{LDVERSION_optimized}-%{_arch}-linux%{_gnu}/Makefile +%dir %{pylibdir}/config-%{LDVERSION_optimized}-%{platform_triplet}/ +%{pylibdir}/config-%{LDVERSION_optimized}-%{platform_triplet}/Makefile %dir %{_includedir}/python%{LDVERSION_optimized}/ %{_includedir}/python%{LDVERSION_optimized}/%{_pyconfig_h} @@ -1538,8 +1638,8 @@ fi %{_bindir}/2to3 # TODO: Remove 2to3-3.7 once rebased to 3.7 %{_bindir}/2to3-%{pybasever} -%{pylibdir}/config-%{LDVERSION_optimized}-%{_arch}-linux%{_gnu}/* -%exclude %{pylibdir}/config-%{LDVERSION_optimized}-%{_arch}-linux%{_gnu}/Makefile +%{pylibdir}/config-%{LDVERSION_optimized}-%{platform_triplet}/* +%exclude %{pylibdir}/config-%{LDVERSION_optimized}-%{platform_triplet}/Makefile %exclude %{pylibdir}/distutils/command/wininst-*.exe %{_includedir}/python%{LDVERSION_optimized}/*.h %exclude %{_includedir}/python%{LDVERSION_optimized}/%{_pyconfig_h} @@ -1687,7 +1787,7 @@ fi %{_libdir}/%{py_INSTSONAME_debug} # Analog of the -devel subpackage's files: -%{pylibdir}/config-%{LDVERSION_debug}-%{_arch}-linux%{_gnu} +%{pylibdir}/config-%{LDVERSION_debug}-%{platform_triplet} %{_includedir}/python%{LDVERSION_debug} %exclude %{_bindir}/python%{LDVERSION_debug}-config @@ -1735,6 +1835,33 @@ fi # ====================================================== %changelog +* Fri Jan 22 2021 Lumír Balhar - 3.6.8-36 +- Fix for CVE-2021-3177 +Resolves: rhbz#1918168 + +* Mon Jan 18 2021 Lumír Balhar - 3.6.8-35 +- New options -a and -k for pathfix.py script backported from upstream +Resolves: rhbz#1917691 + +* Fri Dec 04 2020 Charalampos Stratakis - 3.6.8-34 +- Security fix for CVE-2020-27619: eval() call on content received via HTTP in the CJK codec tests +Resolves: rhbz#1890237 + +* Tue Nov 24 2020 Lumír Balhar - 3.6.8-33 +- Add support for upstream architecture names +https://fedoraproject.org/wiki/Changes/Python_Upstream_Architecture_Names +Resolves: rhbz#1868003 + +* Mon Nov 09 2020 Charalampos Stratakis - 3.6.8-32 +- Security fix for CVE-2020-26116: Reject control chars in HTTP method in http.client +Resolves: rhbz#1883257 + +* Mon Aug 17 2020 Tomas Orsava - 3.6.8-31 +- Avoid infinite loop when reading specially crafted TAR files (CVE-2019-20907) +Resolves: rhbz#1856481 +- Resolve hash collisions for Pv4Interface and IPv6Interface (CVE-2020-14422) +Resolves: rhbz#1854926 + * Thu Jun 25 2020 Victor Stinner - 3.6.8-30 - Remove downstream 00178-dont-duplicate-flags-in-sysconfig.patch which introduced a bug on distutils.sysconfig.get_config_var('LIBPL')