pkgsrc-Changes archive

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

CVS commit: pkgsrc



Module Name:    pkgsrc
Committed By:   mgorny
Date:           Sun Sep 20 11:06:23 UTC 2020

Modified Files:
        pkgsrc/doc: CHANGES-2020
        pkgsrc/lang/python27: Makefile distinfo
Added Files:
        pkgsrc/lang/python27/patches: patch-Lib_httplib.py patch-Lib_tarfile.py
            patch-Lib_test_test__httplib.py patch-Lib_test_test__urllib2.py
            patch-Lib_urllib2.py

Log Message:
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


To generate a diff of this commit:
cvs rdiff -u -r1.5384 -r1.5385 pkgsrc/doc/CHANGES-2020
cvs rdiff -u -r1.89 -r1.90 pkgsrc/lang/python27/Makefile
cvs rdiff -u -r1.78 -r1.79 pkgsrc/lang/python27/distinfo
cvs rdiff -u -r0 -r1.1 pkgsrc/lang/python27/patches/patch-Lib_httplib.py \
    pkgsrc/lang/python27/patches/patch-Lib_tarfile.py \
    pkgsrc/lang/python27/patches/patch-Lib_test_test__httplib.py \
    pkgsrc/lang/python27/patches/patch-Lib_test_test__urllib2.py \
    pkgsrc/lang/python27/patches/patch-Lib_urllib2.py

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/doc/CHANGES-2020
diff -u pkgsrc/doc/CHANGES-2020:1.5384 pkgsrc/doc/CHANGES-2020:1.5385
--- pkgsrc/doc/CHANGES-2020:1.5384      Sun Sep 20 09:55:27 2020
+++ pkgsrc/doc/CHANGES-2020     Sun Sep 20 11:06:23 2020
@@ -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 @@ Changes to the packages collection and i
        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]

Index: pkgsrc/lang/python27/Makefile
diff -u pkgsrc/lang/python27/Makefile:1.89 pkgsrc/lang/python27/Makefile:1.90
--- pkgsrc/lang/python27/Makefile:1.89  Tue Sep  1 09:26:54 2020
+++ pkgsrc/lang/python27/Makefile       Sun Sep 20 11:06:23 2020
@@ -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

Index: pkgsrc/lang/python27/distinfo
diff -u pkgsrc/lang/python27/distinfo:1.78 pkgsrc/lang/python27/distinfo:1.79
--- pkgsrc/lang/python27/distinfo:1.78  Tue Sep  1 09:26:54 2020
+++ pkgsrc/lang/python27/distinfo       Sun Sep 20 11:06:23 2020
@@ -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_instal
 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

Added files:

Index: pkgsrc/lang/python27/patches/patch-Lib_httplib.py
diff -u /dev/null pkgsrc/lang/python27/patches/patch-Lib_httplib.py:1.1
--- /dev/null   Sun Sep 20 11:06:23 2020
+++ pkgsrc/lang/python27/patches/patch-Lib_httplib.py   Sun Sep 20 11:06:23 2020
@@ -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.
Index: pkgsrc/lang/python27/patches/patch-Lib_tarfile.py
diff -u /dev/null pkgsrc/lang/python27/patches/patch-Lib_tarfile.py:1.1
--- /dev/null   Sun Sep 20 11:06:23 2020
+++ pkgsrc/lang/python27/patches/patch-Lib_tarfile.py   Sun Sep 20 11:06:23 2020
@@ -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")
Index: pkgsrc/lang/python27/patches/patch-Lib_test_test__httplib.py
diff -u /dev/null pkgsrc/lang/python27/patches/patch-Lib_test_test__httplib.py:1.1
--- /dev/null   Sun Sep 20 11:06:23 2020
+++ pkgsrc/lang/python27/patches/patch-Lib_test_test__httplib.py        Sun Sep 20 11:06:23 2020
@@ -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):
Index: pkgsrc/lang/python27/patches/patch-Lib_test_test__urllib2.py
diff -u /dev/null pkgsrc/lang/python27/patches/patch-Lib_test_test__urllib2.py:1.1
--- /dev/null   Sun Sep 20 11:06:23 2020
+++ pkgsrc/lang/python27/patches/patch-Lib_test_test__urllib2.py        Sun Sep 20 11:06:23 2020
@@ -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()
Index: pkgsrc/lang/python27/patches/patch-Lib_urllib2.py
diff -u /dev/null pkgsrc/lang/python27/patches/patch-Lib_urllib2.py:1.1
--- /dev/null   Sun Sep 20 11:06:23 2020
+++ pkgsrc/lang/python27/patches/patch-Lib_urllib2.py   Sun Sep 20 11:06:23 2020
@@ -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:
++                scheme = ''
++            yield (scheme, None)
+ 
+     def http_error_auth_reqed(self, authreq, host, req, headers):
+         # host may be an authority (without userinfo) or a URL with an
+         # authority
+-        # XXX could be multiple headers
+-        authreq = headers.get(authreq, None)
+-
+-        if authreq:
+-            mo = AbstractBasicAuthHandler.rx.search(authreq)
+-            if mo:
+-                scheme, quote, realm = mo.groups()
+-                if quote not in ['"', "'"]:
+-                    warnings.warn("Basic Auth Realm was unquoted",
+-                                  UserWarning, 2)
+-                if scheme.lower() == 'basic':
++        headers = headers.getheaders(authreq)
++        if not headers:
++            # no header found
++            return
++
++        unsupported = None
++        for header in headers:
++            for scheme, realm in self._parse_realm(header):
++                if scheme.lower() != 'basic':
++                    unsupported = scheme
++                    continue
++
++                if realm is not None:
++                    # Use the first matching Basic challenge.
++                    # Ignore following challenges even if they use the Basic
++                    # scheme.
+                     return self.retry_http_basic_auth(host, req, realm)
+ 
++        if unsupported is not None:
++            raise ValueError("AbstractBasicAuthHandler does not "
++                             "support the following scheme: %r"
++                             % (scheme,))
++
+     def retry_http_basic_auth(self, host, req, realm):
+         user, pw = self.passwd.find_user_password(realm, host)
+         if pw is not None:



Home | Main Index | Thread Index | Old Index