From 978cfa779ba16e6eae9e61dcdbe004039db62731 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Mar 17 2020 12:35:19 +0000 Subject: import python-virtualenv-15.1.0-4.el7_7 --- diff --git a/SOURCES/CVE-2018-18074.patch b/SOURCES/CVE-2018-18074.patch new file mode 100644 index 0000000..dc2100d --- /dev/null +++ b/SOURCES/CVE-2018-18074.patch @@ -0,0 +1,90 @@ +From 82224a0b35a9b381708cd6dee990aa9c4b4db7cd Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Thu, 16 Jan 2020 10:08:55 +0100 +Subject: [PATCH] Fix for CVE-2018-18074 + +This patch contains the fix for CVE-2018-18074 and +a subsequent regression fix combined in one. +--- + sessions.py | 36 +++++++++++++++++++++++++++++------- + utils.py | 1 + + 2 files changed, 30 insertions(+), 7 deletions(-) + +diff --git a/sessions.py b/sessions.py +index bcbcc88..c65c908 100644 +--- a/sessions.py ++++ b/sessions.py +@@ -17,7 +17,7 @@ from .cookies import ( + cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) + from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT + from .hooks import default_hooks, dispatch_hook +-from .utils import to_key_val_list, default_headers, to_native_string ++from .utils import to_key_val_list, default_headers, to_native_string, DEFAULT_PORTS + from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) + from .packages.urllib3._collections import RecentlyUsedContainer +@@ -85,6 +85,32 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + + + class SessionRedirectMixin(object): ++ ++ def should_strip_auth(self, old_url, new_url): ++ """Decide whether Authorization header should be removed when redirecting""" ++ old_parsed = urlparse(old_url) ++ new_parsed = urlparse(new_url) ++ if old_parsed.hostname != new_parsed.hostname: ++ return True ++ # Special case: allow http -> https redirect when using the standard ++ # ports. This isn't specified by RFC 7235, but is kept to avoid ++ # breaking backwards compatibility with older versions of requests ++ # that allowed any redirects on the same host. ++ if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) ++ and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): ++ return False ++ ++ # Handle default port usage corresponding to scheme. ++ changed_port = old_parsed.port != new_parsed.port ++ changed_scheme = old_parsed.scheme != new_parsed.scheme ++ default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) ++ if (not changed_scheme and old_parsed.port in default_port ++ and new_parsed.port in default_port): ++ return False ++ ++ # Standard case: root URI must match ++ return changed_port or changed_scheme ++ + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None, **adapter_kwargs): + """Receives a Response. Returns a generator of Responses.""" +@@ -190,14 +216,10 @@ class SessionRedirectMixin(object): + headers = prepared_request.headers + url = prepared_request.url + +- if 'Authorization' in headers: ++ if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): + # If we get redirected to a new host, we should strip out any + # authentication headers. +- original_parsed = urlparse(response.request.url) +- redirect_parsed = urlparse(url) +- +- if (original_parsed.hostname != redirect_parsed.hostname): +- del headers['Authorization'] ++ del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None +diff --git a/utils.py b/utils.py +index 30a03ca..9080923 100644 +--- a/utils.py ++++ b/utils.py +@@ -34,6 +34,7 @@ NETRC_FILES = ('.netrc', '_netrc') + + DEFAULT_CA_BUNDLE_PATH = certs.where() + ++DEFAULT_PORTS = {'http': 80, 'https': 443} + + def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" +-- +2.24.1 + diff --git a/SOURCES/CVE-2018-20060.patch b/SOURCES/CVE-2018-20060.patch new file mode 100644 index 0000000..4ea0e42 --- /dev/null +++ b/SOURCES/CVE-2018-20060.patch @@ -0,0 +1,92 @@ +From 20f7d039446f71c40ea240c0a9f6e2465659ec68 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 13 Jan 2020 09:31:42 +0100 +Subject: [PATCH 1/2] CVE-2018-20060 + +--- + poolmanager.py | 11 ++++++++++- + util/retry.py | 11 ++++++++++- + 2 files changed, 20 insertions(+), 2 deletions(-) + +diff --git a/poolmanager.py b/poolmanager.py +index 1023dcb..b87f315 100644 +--- a/poolmanager.py ++++ b/poolmanager.py +@@ -156,8 +156,9 @@ class PoolManager(RequestMethods): + + kw['assert_same_host'] = False + kw['redirect'] = False ++ + if 'headers' not in kw: +- kw['headers'] = self.headers ++ kw['headers'] = self.headers.copy() + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) +@@ -179,6 +180,14 @@ class PoolManager(RequestMethods): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + ++ # Strip headers marked as unsafe to forward to the redirected location. ++ # Check remove_headers_on_redirect to avoid a potential network call within ++ # conn.is_same_host() which may use socket.gethostbyname() in the future. ++ if (retries.remove_headers_on_redirect ++ and not conn.is_same_host(redirect_location)): ++ for header in retries.remove_headers_on_redirect: ++ kw['headers'].pop(header, None) ++ + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: +diff --git a/util/retry.py b/util/retry.py +index 2d3aa20..d6bd74e 100644 +--- a/util/retry.py ++++ b/util/retry.py +@@ -103,6 +103,11 @@ class Retry(object): + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + ++ :param iterable remove_headers_on_redirect: ++ Sequence of headers to remove from the request when a response ++ indicating a redirect is returned before firing off the redirected ++ request ++ + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have +@@ -112,13 +117,15 @@ class Retry(object): + DEFAULT_METHOD_WHITELIST = frozenset([ + 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + ++ DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) ++ + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__(self, total=10, connect=None, read=None, redirect=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, + backoff_factor=0, raise_on_redirect=True, raise_on_status=True, +- _observed_errors=0): ++ _observed_errors=0, remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + + self.total = total + self.connect = connect +@@ -135,6 +142,7 @@ class Retry(object): + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self._observed_errors = _observed_errors # TODO: use .history instead? ++ self.remove_headers_on_redirect = remove_headers_on_redirect + + def new(self, **kw): + params = dict( +@@ -146,6 +154,7 @@ class Retry(object): + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + _observed_errors=self._observed_errors, ++ remove_headers_on_redirect=self.remove_headers_on_redirect, + ) + params.update(kw) + return type(self)(**params) +-- +2.24.1 + diff --git a/SOURCES/CVE-2019-11236.patch b/SOURCES/CVE-2019-11236.patch new file mode 100644 index 0000000..b03a72f --- /dev/null +++ b/SOURCES/CVE-2019-11236.patch @@ -0,0 +1,42 @@ +From bc4ac186771b27772246057fdce51775ebbf0f24 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 13 Jan 2020 09:34:25 +0100 +Subject: [PATCH 2/2] CVE-2019-11236 + +--- + util/url.py | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/util/url.py b/util/url.py +index e996204..bf0b803 100644 +--- a/util/url.py ++++ b/util/url.py +@@ -1,8 +1,14 @@ + from __future__ import absolute_import + from collections import namedtuple ++import re + + from ..exceptions import LocationParseError ++try: ++ from urllib.parse import quote ++except ImportError: ++ from urllib import quote + ++_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') + + url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + +@@ -146,6 +152,10 @@ def parse_url(url): + # Empty + return Url() + ++ # Prevent CVE-2019-9740. ++ # adapted from https://github.com/python/cpython/pull/12755 ++ url = _contains_disallowed_url_pchar_re.sub(lambda match: quote(match.group()), url) ++ + scheme = None + auth = None + host = None +-- +2.24.1 + diff --git a/SPECS/python-virtualenv.spec b/SPECS/python-virtualenv.spec index f330b53..3eb516f 100644 --- a/SPECS/python-virtualenv.spec +++ b/SPECS/python-virtualenv.spec @@ -3,7 +3,7 @@ Name: python-virtualenv Version: 15.1.0 -Release: 2%{?dist} +Release: 4%{?dist} Summary: Tool to create isolated Python environments Group: Development/Languages @@ -17,6 +17,24 @@ Source0: https://files.pythonhosted.org/packages/source/v/virtualenv/virt # https://github.com/pypa/virtualenv/commit/3d7361ff2e31472cb69d00150fbdf5a3c9af2a0d Patch0: disable-pypi-downloads-on-venv-creation.patch +# Patch for CVE in the bundled urllib3 +# CVE-2018-20060 Cross-host redirect does not remove Authorization header allow for credential exposure +# https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2018-20060 +Patch1: CVE-2018-20060.patch + +# Patch for CVE in the bundled urllib3 +# CVE-2019-11236 CRLF injection due to not encoding the '\r\n' sequence leading to possible attack on internal service +# https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2019-11236 +Patch2: CVE-2019-11236.patch + +# Patch for CVE in the bundled requests +# CVE-2018-18074 Redirect from HTTPS to HTTP does not remove Authorization header +# This patch fixes both the CVE +# https://bugzilla.redhat.com/show_bug.cgi?id=1643829 +# and the subsequent regression +# https://github.com/psf/requests/pull/4851 +Patch3: CVE-2018-18074.patch + BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch @@ -42,6 +60,22 @@ licensed under an MIT-style permissive license. %patch0 -p1 %{__sed} -i -e "1s|#!/usr/bin/env python||" virtualenv.py +# Patching of bundled libraries +pushd virtualenv_support/ +# Extract wheel content +unzip pip-9.0.*-any.whl +pushd pip/_vendor/requests/packages/urllib3/ +%patch1 -p1 +%patch2 -p1 +popd # out of wheel +pushd pip/_vendor/requests/ +%patch3 -p1 +popd # out of wheel +# Replace the pip folder in the zip archive (.whl) +zip -r pip-9.0.*-any.whl pip +# Remove unzipped folders +rm -rf pip/ pip-9.0.*.dist-info/ +popd # out of virtualenv_support %build # Build code @@ -80,6 +114,19 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Thu Feb 13 2020 Lumír Balhar - 15.1.0-4 +- Bump +Resolves: rhbz#1649153 +Resolves: rhbz#1700824 +Resolves: rhbz#1643829 + +* Tue Jan 14 2020 Lumír Balhar - 15.1.0-3 +- Add three new patches for CVEs in bundled urllib3 and requests +CVE-2018-20060, CVE-2019-11236, CVE-2018-18074 +Resolves: rhbz#1649153 +Resolves: rhbz#1700824 +Resolves: rhbz#1643829 + * Wed Sep 13 2017 Charalampos Stratakis - 15.1.0-2 - Add back the versioned virtualenv script Resolves: rhbz#1461154