Gentoo Websites Logo
Go to: Gentoo Home Documentation Forums Lists Bugs Planet Store Wiki Get Gentoo!
Bug 639156 - =app-portage/mirrorselect mirrorselect.selectors url_open'ed wrong URL and doesn't catch ssl.CertificateError exception
Summary: =app-portage/mirrorselect mirrorselect.selectors url_open'ed wrong URL and do...
Status: RESOLVED FIXED
Alias: None
Product: Portage Development
Classification: Unclassified
Component: Tools (show other bugs)
Hardware: All Linux
: Normal normal with 2 votes (vote)
Assignee: Portage Tools Team
URL:
Whiteboard:
Keywords: InVCS
Depends on: 686446
Blocks:
  Show dependency tree
 
Reported: 2017-11-29 11:32 UTC by Xiami
Modified: 2019-07-18 19:16 UTC (History)
5 users (show)

See Also:
Package list:
Runtime testing required: ---


Attachments
[PATCH 1/2] selectors.py: fix crash on certificate verification failure (0001-selectors.py-fix-crash-on-certificate-verification-f.patch,1.43 KB, patch)
2017-12-14 10:56 UTC, Xiami
Details | Diff
[PATCH 2/2] selectors.py: use original url for https sites (0002-selectors.py-use-original-url-for-https-sites.patch,810 bytes, patch)
2017-12-14 10:57 UTC, Xiami
Details | Diff
[PATCH 2/2] selectors.py: use original url for https sites (0002-selectors.py-use-original-url-for-https-sites.patch,835 bytes, patch)
2017-12-28 09:46 UTC, Xiami
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Xiami 2017-11-29 11:32:35 UTC
Today I run mirrorselect -HDs5 to fetch a latest fastest mirror list and encounter:

_deeptime(): maxtime is 10
deeptime(): ip's for host mirror.dkm.cz: ['86.49.49.49', '[2a02:8300:8000:3::49]']
deeptime(): testing url: https://86.49.49.49/gentoo/distfiles/../snapshots/deltas/snapshot-20171127-20171128.patch.bz2
Traceback (most recent call last):
  File "/usr/lib/python-exec/python3.5/mirrorselect", line 61, in <module>
    MirrorSelect().main(sys.argv)
  File "/usr/lib64/python3.5/site-packages/mirrorselect/main.py", line 373, in main
    urls = self.select_urls(hosts, options)
  File "/usr/lib64/python3.5/site-packages/mirrorselect/main.py", line 322, in select_urls
    selector = Deep(hosts, options, self.output)
  File "/usr/lib64/python3.5/site-packages/mirrorselect/selectors.py", line 226, in __init__
    self.deeptest()
  File "/usr/lib64/python3.5/site-packages/mirrorselect/selectors.py", line 253, in deeptest
    mytime, ignore = self.deeptime(host, maxtime)
  File "/usr/lib64/python3.5/site-packages/mirrorselect/selectors.py", line 338, in deeptime
    ip, ips[ips.index(ip):])
  File "/usr/lib64/python3.5/site-packages/mirrorselect/selectors.py", line 421, in _test_connection
    f = url_open(test_url)
  File "/usr/lib64/python3.5/urllib/request.py", line 163, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib64/python3.5/urllib/request.py", line 466, in open
    response = self._open(req, data)
  File "/usr/lib64/python3.5/urllib/request.py", line 484, in _open
    '_open', req)
  File "/usr/lib64/python3.5/urllib/request.py", line 444, in _call_chain
    result = func(*args)
  File "/usr/lib64/python3.5/urllib/request.py", line 1297, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "/usr/lib64/python3.5/urllib/request.py", line 1254, in do_open
    h.request(req.get_method(), req.selector, req.data, headers)
  File "/usr/lib64/python3.5/http/client.py", line 1107, in request
    self._send_request(method, url, body, headers)
  File "/usr/lib64/python3.5/http/client.py", line 1152, in _send_request
    self.endheaders(body)
  File "/usr/lib64/python3.5/http/client.py", line 1103, in endheaders
    self._send_output(message_body)
  File "/usr/lib64/python3.5/http/client.py", line 934, in _send_output
    self.send(msg)
  File "/usr/lib64/python3.5/http/client.py", line 877, in send
    self.connect()
  File "/usr/lib64/python3.5/http/client.py", line 1261, in connect
    server_hostname=server_hostname)
  File "/usr/lib64/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
  File "/usr/lib64/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib64/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib64/python3.5/ssl.py", line 646, in do_handshake
    match_hostname(self.getpeercert(), self.server_hostname)
  File "/usr/lib64/python3.5/ssl.py", line 309, in match_hostname
    % (hostname, dnsnames[0]))
ssl.CertificateError: hostname '86.49.49.49' doesn't match 'mirror.dkm.cz'


1) For both http and https URLs, simply replace hostname with IP is not good. If target IP serves multiple virtual hosts, we may not get what we want. Also, for TLS connections, hostname is used in certificate verification.
2) mirrorselect.selectors doesn't catch ssl.CertificateError causing a crash.

Besides, I found using signal.alarm to timeout a getaddrinfo() call is useless. But that's unrelated to this bug, perhaps.

I'll try to give a patch to fix those,when i'm idle.

Whatever, report it before I get off work today. :)
Comment 1 Brian Dolbec (RETIRED) gentoo-dev 2017-11-29 15:41:08 UTC
A patch would be very much appreciated.
Comment 2 Xiami 2017-12-14 10:56:53 UTC
Created attachment 510026 [details, diff]
[PATCH 1/2] selectors.py: fix crash on certificate verification failure
Comment 3 Xiami 2017-12-14 10:57:30 UTC
Created attachment 510028 [details, diff]
[PATCH 2/2] selectors.py: use original url for https sites
Comment 4 Xiami 2017-12-14 11:01:44 UTC
Patches uploaded.

Also at https://github.com/Xiami2012/mirrorselect/commits/fix-ssl-cert

Sorry for late reply. was busy and had a cold for a week.
Comment 5 Andrew 2017-12-26 19:29:41 UTC
This is a duplicate of bug 604968. It has a more lengthy discussion which comes to the same conclusion.

Thanks for the patch Xiami. Would be nice to have the issue fixed.
Comment 6 Andrew 2017-12-26 19:49:16 UTC
I have tested the second patch and can confirm that it solves the problem: mirrorselect --deep completes successfully.
Comment 7 Zac Medico gentoo-dev 2017-12-27 03:09:45 UTC
(In reply to Xiami from comment #3)
> Created attachment 510028 [details, diff] [details, diff]
> [PATCH 2/2] selectors.py: use original url for https sites

We can handle it earlier, in the deeptime method like this:

diff --git a/mirrorselect/selectors.py b/mirrorselect/selectors.py
index cf70b21..722b266 100644
--- a/mirrorselect/selectors.py
+++ b/mirrorselect/selectors.py
@@ -329,6 +329,10 @@ class Deep(object):
 		delta = 0
 		f = None
 
+		if url_parts.scheme == "https":
+			# ips don't work because the hostname must match the cert
+			ips = [url_parts.hostname]
+
 		for ip in ips:
 			test_parts = url_parts._replace(netloc=ip)
 			test_url = url_unparse(test_parts)
Comment 8 Xiami 2017-12-28 09:46:10 UTC
(In reply to Zac Medico from comment #7)
> (In reply to Xiami from comment #3)
> > Created attachment 510028 [details, diff] [details, diff] [details, diff]
> > [PATCH 2/2] selectors.py: use original url for https sites
> 
> We can handle it earlier, in the deeptime method like this:
> 
> diff --git a/mirrorselect/selectors.py b/mirrorselect/selectors.py
> index cf70b21..722b266 100644
> --- a/mirrorselect/selectors.py
> +++ b/mirrorselect/selectors.py
> @@ -329,6 +329,10 @@ class Deep(object):
>  		delta = 0
>  		f = None
>  
> +		if url_parts.scheme == "https":
> +			# ips don't work because the hostname must match the cert
> +			ips = [url_parts.hostname]
> +
>  		for ip in ips:
>  			test_parts = url_parts._replace(netloc=ip)
>  			test_url = url_unparse(test_parts)

That's great. Thanks. :)
Comment 9 Xiami 2017-12-28 09:46:28 UTC
Created attachment 511816 [details, diff]
[PATCH 2/2] selectors.py: use original url for https sites

Already tested by myself.
Comment 10 Andrew 2018-01-11 20:10:01 UTC
Are there any reasons to not apply the suggested patch?
Comment 11 Zac Medico gentoo-dev 2018-01-11 20:42:27 UTC
The patches look good to me.

It looks like bug 566778 is another good candidate to fix for the next release.
Comment 12 Xiami 2018-01-12 03:39:02 UTC
(In reply to Zac Medico from comment #11)
> The patches look good to me.
> 
> It looks like bug 566778 is another good candidate to fix for the next
> release.

Hmmm... Patch in Bug#566778 is fixing another rare situation and irrelevant to my patch. They fix different problems and can be merged.

What I fix is at TLS-handshake stage, to make urlopen know the correct CN/SAN the certificate should have.

However, Bug#566778's patch deals at http protocol. To connect a web server hosting multiple vhosts on one ip, changing domain part in URL may get data from a vhost we don't expect.
_test_connection() in selectors.py shows a work around to this. In HTTPError handler, it restores url to the original url (with domain part not replaced) if it fails testing the last ip.
This work around is a little disturbing in a situation where two vhost have the same directory layout (e.g. Both provides /distfiles/glibc.2.25.tar.xz).
Setting request header Host solves this. It's great. If applied, the old work around can be removed.

At least in nginx (which i had tested only), define two ssl sites, one with server_name a.example.com and the other b.example.com .
And `curl -H 'Host: a.example.com' https://b.example.com` gets and validates cert of b.example.com, but returning web contents under a.example.com .

Therefore, they are patches dealing different problems. It's good to apply both though for ssl sites adding a Host header is redundant.
Comment 13 Michal Plichta 2018-08-17 20:09:07 UTC
just another traceback:

clayman /home/emc # mirrorselect -s3 -b10 -D
* Using url: https://api.gentoo.org/mirrors/distfiles.xml
* Downloading a list of mirrors...
 Got 131 mirrors.
* Downloading mirrorselect-test files from each mirror... [36 of 131]Traceback (most recent call last):
  File "/usr/lib/python-exec/python3.6/mirrorselect", line 61, in <module>
    MirrorSelect().main(sys.argv)
  File "/usr/lib64/python3.6/site-packages/mirrorselect/main.py", line 373, in main
    urls = self.select_urls(hosts, options)
  File "/usr/lib64/python3.6/site-packages/mirrorselect/main.py", line 322, in select_urls
    selector = Deep(hosts, options, self.output)
  File "/usr/lib64/python3.6/site-packages/mirrorselect/selectors.py", line 226, in __init__
    self.deeptest()
  File "/usr/lib64/python3.6/site-packages/mirrorselect/selectors.py", line 253, in deeptest
    mytime, ignore = self.deeptime(host, maxtime)
  File "/usr/lib64/python3.6/site-packages/mirrorselect/selectors.py", line 338, in deeptime
    ip, ips[ips.index(ip):])
  File "/usr/lib64/python3.6/site-packages/mirrorselect/selectors.py", line 421, in _test_connection
    f = url_open(test_url)
  File "/usr/lib64/python3.6/urllib/request.py", line 223, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib64/python3.6/urllib/request.py", line 526, in open
    response = self._open(req, data)
  File "/usr/lib64/python3.6/urllib/request.py", line 544, in _open
    '_open', req)
  File "/usr/lib64/python3.6/urllib/request.py", line 504, in _call_chain
    result = func(*args)
  File "/usr/lib64/python3.6/urllib/request.py", line 1361, in https_open
    context=self._context, check_hostname=self._check_hostname)
  File "/usr/lib64/python3.6/urllib/request.py", line 1318, in do_open
    encode_chunked=req.has_header('Transfer-encoding'))
  File "/usr/lib64/python3.6/http/client.py", line 1239, in request
    self._send_request(method, url, body, headers, encode_chunked)
  File "/usr/lib64/python3.6/http/client.py", line 1285, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.6/http/client.py", line 1234, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
  File "/usr/lib64/python3.6/http/client.py", line 1026, in _send_output
    self.send(msg)
  File "/usr/lib64/python3.6/http/client.py", line 964, in send
    self.connect()
  File "/usr/lib64/python3.6/http/client.py", line 1400, in connect
    server_hostname=server_hostname)
  File "/usr/lib64/python3.6/ssl.py", line 407, in wrap_socket
    _context=self, _session=session)
  File "/usr/lib64/python3.6/ssl.py", line 814, in __init__
    self.do_handshake()
  File "/usr/lib64/python3.6/ssl.py", line 1068, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib64/python3.6/ssl.py", line 694, in do_handshake
    match_hostname(self.getpeercert(), self.server_hostname)
  File "/usr/lib64/python3.6/ssl.py", line 331, in match_hostname
    % (hostname, dnsnames[0]))
ssl.CertificateError: hostname '2a02:8300:8000:3::49' doesn't match 'mirror.dkm.cz'
Comment 14 Nick Soveiko 2019-01-31 20:40:44 UTC
i just moved a server to a new location and while trying to update mirror list encountered the same problem

$ mirrorselect -D -s3 -o
* Using url: https://api.gentoo.org/mirrors/distfiles.xml
* Downloading a list of mirrors...
 Got 147 mirrors.
* Downloading mirrorselect-test files from each mirror... [6 of 147]Traceback (most recent call last):
  File "/usr/lib/python-exec/python2.7/mirrorselect", line 61, in <module>
    MirrorSelect().main(sys.argv)
  File "/usr/lib64/python2.7/site-packages/mirrorselect/main.py", line 373, in main
    urls = self.select_urls(hosts, options)
  File "/usr/lib64/python2.7/site-packages/mirrorselect/main.py", line 322, in select_urls
    selector = Deep(hosts, options, self.output)
  File "/usr/lib64/python2.7/site-packages/mirrorselect/selectors.py", line 226, in __init__
    self.deeptest()
  File "/usr/lib64/python2.7/site-packages/mirrorselect/selectors.py", line 253, in deeptest
    mytime, ignore = self.deeptime(host, maxtime)
  File "/usr/lib64/python2.7/site-packages/mirrorselect/selectors.py", line 338, in deeptime
    ip, ips[ips.index(ip):])
  File "/usr/lib64/python2.7/site-packages/mirrorselect/selectors.py", line 421, in _test_connection
    f = url_open(test_url)
  File "/usr/lib64/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib64/python2.7/urllib2.py", line 429, in open
    response = self._open(req, data)
  File "/usr/lib64/python2.7/urllib2.py", line 447, in _open
    '_open', req)
  File "/usr/lib64/python2.7/urllib2.py", line 407, in _call_chain
    result = func(*args)
  File "/usr/lib64/python2.7/urllib2.py", line 1241, in https_open
    context=self._context)
  File "/usr/lib64/python2.7/urllib2.py", line 1195, in do_open
    h.request(req.get_method(), req.get_selector(), req.data, headers)
  File "/usr/lib64/python2.7/httplib.py", line 1042, in request
    self._send_request(method, url, body, headers)
  File "/usr/lib64/python2.7/httplib.py", line 1082, in _send_request
    self.endheaders(body)
  File "/usr/lib64/python2.7/httplib.py", line 1038, in endheaders
    self._send_output(message_body)
  File "/usr/lib64/python2.7/httplib.py", line 882, in _send_output
    self.send(msg)
  File "/usr/lib64/python2.7/httplib.py", line 844, in send
    self.connect()
  File "/usr/lib64/python2.7/httplib.py", line 1263, in connect
    server_hostname=server_hostname)
  File "/usr/lib64/python2.7/ssl.py", line 369, in wrap_socket
    _context=self)
  File "/usr/lib64/python2.7/ssl.py", line 617, in __init__
    self.do_handshake()
  File "/usr/lib64/python2.7/ssl.py", line 854, in do_handshake
    match_hostname(self.getpeercert(), self.server_hostname)
  File "/usr/lib64/python2.7/ssl.py", line 288, in match_hostname
    % (hostname, ', '.join(map(repr, dnsnames))))
ssl.CertificateError: hostname '92.60.51.128' doesn't match either of '*.zeroone.sk', 'zeroone.sk'

app-portage/mirrorselect-2.2.3
Comment 15 Larry the Git Cow gentoo-dev 2019-02-13 08:09:14 UTC
The bug has been referenced in the following commit(s):

https://gitweb.gentoo.org/proj/mirrorselect.git/commit/?id=a6532e7c6b655ebba0dce53f92d9fca180b23be6

commit a6532e7c6b655ebba0dce53f92d9fca180b23be6
Author:     Xiami <i@f2light.com>
AuthorDate: 2017-12-14 10:30:29 +0000
Commit:     Zac Medico <zmedico@gentoo.org>
CommitDate: 2019-02-13 08:06:57 +0000

    selectors.py: handle ssl.CertificateError (bug 639156)
    
    Bug: https://bugs.gentoo.org/639156
    Signed-off-by: Zac Medico <zmedico@gentoo.org>

 mirrorselect/selectors.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Comment 16 Larry the Git Cow gentoo-dev 2019-02-13 08:22:09 UTC
The bug has been referenced in the following commit(s):

https://gitweb.gentoo.org/proj/mirrorselect.git/commit/?id=856abee86416d4b2159f81d34cf28ef3422b92ec

commit 856abee86416d4b2159f81d34cf28ef3422b92ec
Author:     Michel Ganguin <ganguin@romandie.com>
AuthorDate: 2018-12-31 21:54:29 +0000
Commit:     Zac Medico <zmedico@gentoo.org>
CommitDate: 2019-02-13 08:20:47 +0000

    selectors.py: Give urllib hostname info (bug 604968)
    
    Give urllib hostname info such that:
    * it will not fail when using HTTPS because of hostname mismatch (CertificateError)
    * it will not fail when the server is a virtualhost
    * it will not fail when the server validates ssl SNI
    
    Bug: https://bugs.gentoo.org/566778
    Bug: https://bugs.gentoo.org/604968
    Bug: https://bugs.gentoo.org/639156
    Signed-off-by: Zac Medico <zmedico@gentoo.org>

 mirrorselect/selectors.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)
Comment 17 Larry the Git Cow gentoo-dev 2019-02-13 09:01:29 UTC
The bug has been referenced in the following commit(s):

https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=8cf18832afef56fa988b0291ec8877d739a8c6ba

commit 8cf18832afef56fa988b0291ec8877d739a8c6ba
Author:     Zac Medico <zmedico@gentoo.org>
AuthorDate: 2019-02-13 08:59:32 +0000
Commit:     Zac Medico <zmedico@gentoo.org>
CommitDate: 2019-02-13 09:01:14 +0000

    app-portage/mirrorselect: version bump to 2.2.4
    
    Bug: https://bugs.gentoo.org/566778
    Bug: https://bugs.gentoo.org/604968
    Bug: https://bugs.gentoo.org/639156
    Package-Manager: Portage-2.3.60, Repoman-2.3.12
    Signed-off-by: Zac Medico <zmedico@gentoo.org>

 app-portage/mirrorselect/Manifest                  |  1 +
 app-portage/mirrorselect/mirrorselect-2.2.4.ebuild | 35 ++++++++++++++++++++++
 2 files changed, 36 insertions(+)