Handle port scan on SSL server gracefully.#750
Handle port scan on SSL server gracefully.#750mayfield wants to merge 1 commit intotornadoweb:masterfrom
Conversation
Port scans and other quickly closing connections will result in an unwrapped/disconnected socket. Tornado needs to check the ssl socket's _connected attribute prior to attempting a ssl handshake in this case.
|
socket._connected is not a documented public attribute; is there any way to do this that doesn't rely on implementation details? Can you reproduce this in a test case? What exactly has to happen for the failure to occur (and how exactly does do_handshake fail when this happens)? |
|
Hi Ben, Yeah, I'm not particularly happy with the impl either. Frankly I'm not convinced it shouldn't be fixed in the python ssl module, but that's of little utility for deployments on deprecated python releases. As for test case, I considered trying to just emulate the behavior of nmap but you'd ultimately being dealing with a race where this ordering is required but not guaranteed... The problem I see there is that the accept() and subsequent getpeername() in the SSLSocket constructor would be difficult to separate deterministically such that you could close the connecting socket between the two. It could probably be done with some monkey patching or zeus forbid sys.settrace(). Please advise if you want me to attempt such feats. Regarding not using the "_connected" attribute. The only other approach that I can see would be testing for _sslobj, which is probably just as offensive as testing _connected; I could also catch the TypeError in the read method but this is probably too big of a net, IMO. The responsible code in SSLSocket.init looks like this.. |
|
Excellent, thanks for the analysis. Does it really get far enough to have a TypeError in read? It looks to me like it would be an AttributeError in do_handshake, but I've only read the code, I haven't tried too hard to reproduce the problem. What platform are you on? My understanding is that once a socket becomes connected, getpeername is valid until the socket is closed; it won't concurrently revert to its ENOTCONN state. This suggests that we can work around the problem by calling original_connection.getpeername in TCPServer._handle_connection before or after the call to ssl_wrap_socket, and if it throws an exception don't proceed to create the IOStream. I'd prefer to do this test before calling ssl_wrap_socket, if I'm right about when getpeername can change out from under you (although that would imply the equally-surprising result that accept() is returning sockets that are not actually connected) |
|
It looks like I was wrong: getpeername will return ENOTCONN if a previously-connected socket gets an RST (https://code.google.com/p/pyftpdlib/issues/detail?id=100). I can reproduce this now (with nmap -sT), so I've got a fix (which is different for mac and linux) Related python bug: http://bugs.python.org/issue13721 |
|
Thanks Ben. |
Port scans and other quickly closing connections will
result in an unwrapped/disconnected socket. Tornado
needs to check the ssl socket's _connected attribute
prior to attempting a ssl handshake in this case.
This patch makes the SSL server behave similarly to its'
non-ssl counterpart.