pkgsrc-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[pkgsrc/trunk]: pkgsrc lang/python27: backport vulnerability fixes from Gentoo



details:   https://anonhg.NetBSD.org/pkgsrc/rev/5361fbc49071
branches:  trunk
changeset: 439530:5361fbc49071
user:      mgorny <mgorny%pkgsrc.org@localhost>
date:      Sun Sep 20 11:06:23 2020 +0000

description:
lang/python27: backport vulnerability fixes from Gentoo

Backport 3 vulnerability fixes from Python 3.6 using rebased patches
from Gentoo.  These are:

bpo-39017 (CVE-2019-20907): infinite loop in tarfile.py
bpo-39503 (CVE-2020-8492): ReDoS on AbstractBasicAuthHandler
bpo-39603 (no CVE): header injection via HTTP method

diffstat:

 doc/CHANGES-2020                                      |   3 +-
 lang/python27/Makefile                                |   4 +-
 lang/python27/distinfo                                |   7 +-
 lang/python27/patches/patch-Lib_httplib.py            |  42 ++++++++
 lang/python27/patches/patch-Lib_tarfile.py            |  13 ++
 lang/python27/patches/patch-Lib_test_test__httplib.py |  31 ++++++
 lang/python27/patches/patch-Lib_test_test__urllib2.py |  97 +++++++++++++++++++
 lang/python27/patches/patch-Lib_urllib2.py            |  86 ++++++++++++++++
 8 files changed, 279 insertions(+), 4 deletions(-)

diffs (truncated from 343 to 300 lines):

diff -r 8608492e1fde -r 5361fbc49071 doc/CHANGES-2020
--- a/doc/CHANGES-2020  Sun Sep 20 11:05:31 2020 +0000
+++ b/doc/CHANGES-2020  Sun Sep 20 11:06:23 2020 +0000
@@ -1,4 +1,4 @@
-$NetBSD: CHANGES-2020,v 1.5384 2020/09/20 09:55:27 mef Exp $
+$NetBSD: CHANGES-2020,v 1.5385 2020/09/20 11:06:23 mgorny Exp $
 
 Changes to the packages collection and infrastructure in 2020:
 
@@ -8060,3 +8060,4 @@
        Updated textproc/R-DT to 0.15 [mef 2020-09-20]
        Updated lang/llvm to 10.0.1nb1 [he 2020-09-20]
        Updated textproc/R-jsonlite to 1.7.1 [mef 2020-09-20]
+       Updated lang/python27 to 2.7.18nb3 [mgorny 2020-09-20]
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/Makefile
--- a/lang/python27/Makefile    Sun Sep 20 11:05:31 2020 +0000
+++ b/lang/python27/Makefile    Sun Sep 20 11:06:23 2020 +0000
@@ -1,9 +1,9 @@
-# $NetBSD: Makefile,v 1.89 2020/09/01 09:26:54 schmonz Exp $
+# $NetBSD: Makefile,v 1.90 2020/09/20 11:06:23 mgorny Exp $
 
 .include "dist.mk"
 
 PKGNAME=       python27-${PY_DISTVERSION}
-PKGREVISION=   2
+PKGREVISION=   3
 CATEGORIES=    lang python
 
 MAINTAINER=    pkgsrc-users%NetBSD.org@localhost
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/distinfo
--- a/lang/python27/distinfo    Sun Sep 20 11:05:31 2020 +0000
+++ b/lang/python27/distinfo    Sun Sep 20 11:06:23 2020 +0000
@@ -1,4 +1,4 @@
-$NetBSD: distinfo,v 1.78 2020/09/01 09:26:54 schmonz Exp $
+$NetBSD: distinfo,v 1.79 2020/09/20 11:06:23 mgorny Exp $
 
 SHA1 (Python-2.7.18.tar.xz) = 678d4cf483a1c92efd347ee8e1e79326dc82810b
 RMD160 (Python-2.7.18.tar.xz) = 40a514bb05c9e631454ea8466e28f5bb229428ad
@@ -13,10 +13,15 @@
 SHA1 (patch-Lib_distutils_command_install__egg__info.py) = ec7f9e0cd04489b1f6497c44d75bff6864ad1047
 SHA1 (patch-Lib_distutils_unixccompiler.py) = db16c9aca2f29730945f28247b88b18828739bbb
 SHA1 (patch-Lib_distutils_util.py) = 5bcfad96f8e490351160f1a7c1f4ece7706a33fa
+SHA1 (patch-Lib_httplib.py) = 685891e4ea58062036c87e69e8a0911dd377e64f
 SHA1 (patch-Lib_lib2to3_pgen2_driver.py) = 5d6dab14197f27363394ff1aeee22a8ced8026d2
 SHA1 (patch-Lib_multiprocessing_process.py) = 15699bd8ec822bf54a0631102e00e0a34f882803
 SHA1 (patch-Lib_plistlib.py) = 96ae702995d434e2d7ec0ac62e37427a90b61d13
 SHA1 (patch-Lib_sysconfig.py) = 8a7a0e5cbfec279a05945dffafea1b1131a76f0e
+SHA1 (patch-Lib_tarfile.py) = 105bd378ccd1574ef765cf417269759363148876
+SHA1 (patch-Lib_test_test__httplib.py) = 867d48f677fce23b05c512c0839e704d89e70f14
+SHA1 (patch-Lib_test_test__urllib2.py) = 651d99068f5d25fa9dc1e9be7e923db64920b378
+SHA1 (patch-Lib_urllib2.py) = 8c3b3b1796b57fe544f118313da157d38263b0e5
 SHA1 (patch-Makefile.pre.in) = ceaf34237588b527478ce1f9163c9168382fa201
 SHA1 (patch-Modules___multiprocessing_multiprocessing.h) = 7ca8fe22ba4bdcde6d39dd50fe2e86c25994c146
 SHA1 (patch-Modules___multiprocessing_semaphore.c) = 03b9c33ef38da383d5f7c2c84c17fe38cdd2911e
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/patches/patch-Lib_httplib.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/lang/python27/patches/patch-Lib_httplib.py        Sun Sep 20 11:06:23 2020 +0000
@@ -0,0 +1,42 @@
+$NetBSD: patch-Lib_httplib.py,v 1.1 2020/09/20 11:06:23 mgorny Exp $
+
+--- Lib/httplib.py.orig        2020-04-19 21:13:39.000000000 +0000
++++ Lib/httplib.py
+@@ -257,6 +257,10 @@ _contains_disallowed_url_pchar_re = re.c
+ #  _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'}
+@@ -935,6 +939,8 @@ class HTTPConnection:
+         else:
+             raise CannotSendRequest()
+ 
++        self._validate_method(method)
++
+         # Save the method for use later in the response phase
+         self._method = method
+ 
+@@ -1020,6 +1026,17 @@ class HTTPConnection:
+         # On Python 2, request is already encoded (default)
+         return request
+ 
++    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:
++            msg = (
++                "method can't contain control characters. {method!r} "
++                "(found at least {matched!r})"
++            ).format(matched=match.group(), method=method)
++            raise ValueError(msg)
++
+     def _validate_path(self, url):
+         """Validate a url for putrequest."""
+         # Prevent CVE-2019-9740.
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/patches/patch-Lib_tarfile.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/lang/python27/patches/patch-Lib_tarfile.py        Sun Sep 20 11:06:23 2020 +0000
@@ -0,0 +1,13 @@
+$NetBSD: patch-Lib_tarfile.py,v 1.1 2020/09/20 11:06:23 mgorny Exp $
+
+--- Lib/tarfile.py.orig        2020-04-19 21:13:39.000000000 +0000
++++ Lib/tarfile.py
+@@ -1400,6 +1400,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]
+ 
+             keyword = keyword.decode("utf8")
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/patches/patch-Lib_test_test__httplib.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/lang/python27/patches/patch-Lib_test_test__httplib.py     Sun Sep 20 11:06:23 2020 +0000
@@ -0,0 +1,31 @@
+$NetBSD: patch-Lib_test_test__httplib.py,v 1.1 2020/09/20 11:06:23 mgorny Exp $
+
+--- Lib/test/test_httplib.py.orig      2020-04-19 21:13:39.000000000 +0000
++++ Lib/test/test_httplib.py
+@@ -384,6 +384,26 @@ class HeaderTests(TestCase):
+             with self.assertRaisesRegexp(ValueError, 'Invalid header'):
+                 conn.putheader(name, value)
+ 
++    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.assertRaisesRegexp(
++                    ValueError, "method can't contain control characters"):
++                conn = httplib.HTTPConnection('example.com')
++                conn.sock = FakeSocket(None)
++                conn.request(method=method, url="/")
++
+ 
+ class BasicTest(TestCase):
+     def test_status_lines(self):
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/patches/patch-Lib_test_test__urllib2.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/lang/python27/patches/patch-Lib_test_test__urllib2.py     Sun Sep 20 11:06:23 2020 +0000
@@ -0,0 +1,97 @@
+$NetBSD: patch-Lib_test_test__urllib2.py,v 1.1 2020/09/20 11:06:23 mgorny Exp $
+
+--- Lib/test/test_urllib2.py.orig      2020-04-19 21:13:39.000000000 +0000
++++ Lib/test/test_urllib2.py
+@@ -1128,42 +1128,67 @@ class HandlerTests(unittest.TestCase):
+         self.assertEqual(req.get_host(), "proxy.example.com:3128")
+         self.assertEqual(req.get_header("Proxy-authorization"),"FooBar")
+ 
+-    def test_basic_auth(self, quote_char='"'):
++    def check_basic_auth(self, headers, realm):
+         opener = OpenerDirector()
+         password_manager = MockPasswordManager()
+         auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
+-        realm = "ACME Widget Store"
+-        http_handler = MockHTTPHandler(
+-            401, 'WWW-Authenticate: Basic realm=%s%s%s\r\n\r\n' %
+-            (quote_char, realm, quote_char) )
++        body = '\r\n'.join(headers) + '\r\n\r\n'
++        http_handler = MockHTTPHandler(401, body)
+         opener.add_handler(auth_handler)
+         opener.add_handler(http_handler)
+         self._test_basic_auth(opener, auth_handler, "Authorization",
+                               realm, http_handler, password_manager,
+                               "http://acme.example.com/protected";,
+-                              "http://acme.example.com/protected";
+-                             )
++                              "http://acme.example.com/protected";)
+ 
+-    def test_basic_auth_with_single_quoted_realm(self):
+-        self.test_basic_auth(quote_char="'")
++    def test_basic_auth(self):
++        realm = "realm2%example.com@localhost"
++        realm2 = "realm2%example.com@localhost"
++        basic = 'Basic realm="{realm}"'.format(realm=realm)
++        basic2 = 'Basic realm="{realm2}"'.format(realm2=realm2)
++        other_no_realm = 'Otherscheme xxx'
++        digest = ('Digest realm="{realm2}", '
++                  'qop="auth, auth-int", '
++                  'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", '
++                  'opaque="5ccc069c403ebaf9f0171e9517f40e41"'
++                  .format(realm2=realm2))
++        for realm_str in (
++            # test "quote" and 'quote'
++            'Basic realm="{realm}"'.format(realm=realm),
++            "Basic realm='{realm}'".format(realm=realm),
+ 
+-    def test_basic_auth_with_unquoted_realm(self):
+-        opener = OpenerDirector()
+-        password_manager = MockPasswordManager()
+-        auth_handler = urllib2.HTTPBasicAuthHandler(password_manager)
+-        realm = "ACME Widget Store"
+-        http_handler = MockHTTPHandler(
+-            401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm)
+-        opener.add_handler(auth_handler)
+-        opener.add_handler(http_handler)
+-        msg = "Basic Auth Realm was unquoted"
+-        with test_support.check_warnings((msg, UserWarning)):
+-            self._test_basic_auth(opener, auth_handler, "Authorization",
+-                                  realm, http_handler, password_manager,
+-                                  "http://acme.example.com/protected";,
+-                                  "http://acme.example.com/protected";
+-                                 )
++            # charset is ignored
++            'Basic realm="{realm}", charset="UTF-8"'.format(realm=realm),
++
++            # Multiple challenges per header
++            ', '.join((basic, basic2)),
++            ', '.join((basic, other_no_realm)),
++            ', '.join((other_no_realm, basic)),
++            ', '.join((basic, digest)),
++            ', '.join((digest, basic)),
++        ):
++            headers = ['WWW-Authenticate: {realm_str}'
++                       .format(realm_str=realm_str)]
++            self.check_basic_auth(headers, realm)
++
++        # no quote: expect a warning
++        with test_support.check_warnings(("Basic Auth Realm was unquoted",
++                                     UserWarning)):
++            headers = ['WWW-Authenticate: Basic realm={realm}'
++                       .format(realm=realm)]
++            self.check_basic_auth(headers, realm)
+ 
++        # Multiple headers: one challenge per header.
++        # Use the first Basic realm.
++        for challenges in (
++            [basic,  basic2],
++            [basic,  digest],
++            [digest, basic],
++        ):
++            headers = ['WWW-Authenticate: {challenge}'
++                       .format(challenge=challenge)
++                       for challenge in challenges]
++            self.check_basic_auth(headers, realm)
+ 
+     def test_proxy_basic_auth(self):
+         opener = OpenerDirector()
diff -r 8608492e1fde -r 5361fbc49071 lang/python27/patches/patch-Lib_urllib2.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/lang/python27/patches/patch-Lib_urllib2.py        Sun Sep 20 11:06:23 2020 +0000
@@ -0,0 +1,86 @@
+$NetBSD: patch-Lib_urllib2.py,v 1.1 2020/09/20 11:06:23 mgorny Exp $
+
+--- Lib/urllib2.py.orig        2020-04-19 21:13:39.000000000 +0000
++++ Lib/urllib2.py
+@@ -856,8 +856,15 @@ class AbstractBasicAuthHandler:
+ 
+     # allow for double- and single-quoted realm values
+     # (single quotes are a violation of the RFC, but appear in the wild)
+-    rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
+-                    'realm=(["\']?)([^"\']*)\\2', re.I)
++    rx = re.compile('(?:^|,)'   # start of the string or ','
++                    '[ \t]*'    # optional whitespaces
++                    '([^ \t]+)' # scheme like "Basic"
++                    '[ \t]+'    # mandatory whitespaces
++                    # realm=xxx
++                    # realm='xxx'
++                    # realm="xxx"
++                    'realm=(["\']?)([^"\']*)\\2',
++                    re.I)
+ 
+     # XXX could pre-emptively send auth info already accepted (RFC 2617,
+     # end of section 2, and section 1.2 immediately after "credentials"
+@@ -869,23 +876,52 @@ class AbstractBasicAuthHandler:
+         self.passwd = password_mgr
+         self.add_password = self.passwd.add_password
+ 
++    def _parse_realm(self, header):
++        # parse WWW-Authenticate header: accept multiple challenges per header
++        found_challenge = False
++        for mo in AbstractBasicAuthHandler.rx.finditer(header):
++            scheme, quote, realm = mo.groups()
++            if quote not in ['"', "'"]:
++                warnings.warn("Basic Auth Realm was unquoted",
++                              UserWarning, 3)
++
++            yield (scheme, realm)
++
++            found_challenge = True
++
++        if not found_challenge:
++            if header:
++                scheme = header.split()[0]
++            else:



Home | Main Index | Thread Index | Old Index