This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author kiilerix
Recipients kiilerix
Date 2012-01-06.17:38:31
SpamBayes Score 5.6304184e-11
Marked as misclassified No
Message-id <1325871512.71.0.286774610121.issue13721@psf.upfronthosting.co.za>
In-reply-to
Content
According to http://docs.python.org/release/2.7.2/library/ssl wrap_socket can be used either on connected sockets or on un-connected sockets which then can be .connect'ed.

In the latter case SSLSocket.connect() will (AFAIK) always raise a nice exception if the connection fails before the ssl negotiation has completed.

But when an existing connected but failing socket is wrapped then SSLSocket.__init__ will create an instance with self._sslobj = None without negotiating ssl and without raising any exception. Many SSLSocket methods (such as .cipher) checks for self._sslobj, but for example .getpeercert doesn't and will derefence None. That can lead to spurious crashes in applications.

This problem showed up with Mercurial and connections from China to code.google.com being blocked by the Chinese firewall - see for example https://bugzilla.redhat.com/show_bug.cgi?id=771691 .

In that case

  import socket, ssl, time
  s = socket.create_connection(('code.google.com', 443))
  time.sleep(1)
  ssl_sock = ssl.wrap_socket(s)
  ssl_sock.getpeercert(True)

would fail with
  ...
      ssl_sock.getpeercert(True)
    File "/usr/lib64/python2.7/ssl.py", line 172, in getpeercert
      return self._sslobj.peer_certificate(binary_form)
  AttributeError: 'NoneType' object has no attribute 'peer_certificate'

The problem occurs in the case where The Chinese Wall responds correctly to the SYN with SYN+ACK but soon after sends a RST. The sleep is necessary to reproduce it consistently; that makes sure the RST has been received and getpeername fails. Otherwise getpeername succeeds and the connection reset is only seen later on while negotiation ssl, and socket errors there are handled 'correctly'.

The problem can be reproduced on Linux with
  iptables -t filter -A FORWARD -p tcp --dport 443 ! --tcp-flags SYN SYN -j REJECT --reject-with tcp-reset

I would expect that wrap_socket / SSLSocket.__init__ raised an exception if the wrapped socket has been connected but failed. Calling getpeername is insufficient to detect that (and it is too racy to be reliable).

Alternatively all SSLSocket methods should take care not to dereference self._sslobj and they should respond properly - preferably with a socket/ssl exception.

Alternatively the documentation should describe how all applications that wraps connected sockets has to verify that it actually was connected. Checking .cipher() is apparently currently the least ugly way to do that?

One good(?) reason to wrap connected sockets is to be able to use socket.create_connection which tries all IP adresses of a fqdn before it fails. (Btw: That isn't described in the documentation! That confused me while debugging this.)

I guess applications (like Mercurial) that for that reason wants to use create_connection with 2.7.2 and older should check .cipher() as a workaround?
History
Date User Action Args
2012-01-06 17:38:32kiilerixsetrecipients: + kiilerix
2012-01-06 17:38:32kiilerixsetmessageid: <1325871512.71.0.286774610121.issue13721@psf.upfronthosting.co.za>
2012-01-06 17:38:32kiilerixlinkissue13721 messages
2012-01-06 17:38:31kiilerixcreate