New submission from Mads Kiilerich <m...@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 <rep...@bugs.python.org>
<http://bugs.python.org/issue13721>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to