[issue13721] ssl.wrap_socket on a connected but failed connection succeeds and .peer_certificate gives AttributeError

Mads Kiilerich report at bugs.python.org
Fri Jan 6 18:38:32 CET 2012


New submission from Mads Kiilerich <mads at kiilerich.com>:

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?

----------
components: Library (Lib)
messages: 150754
nosy: kiilerix
priority: normal
severity: normal
status: open
title: ssl.wrap_socket on a connected but failed connection succeeds and .peer_certificate gives AttributeError
type: behavior
versions: Python 2.7

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue13721>
_______________________________________


More information about the Python-bugs-list mailing list