[pypy-issue] Issue #2578: Behavioral discrepancy between CPython and PyPy ssl modules (pypy/pypy)

Nathaniel Smith issues-reply at bitbucket.org
Wed Jun 14 04:32:32 EDT 2017

New issue 2578: Behavioral discrepancy between CPython and PyPy ssl modules

Nathaniel Smith:

Here's a weird edge case where the CPython and PyPy ssl modules behave differently. I'm not entirely sure whether it's a bug or not, and it's easy enough for me to work around, but I found the PyPy behavior surprising and who knows it might point to some deeper issue, so I wanted to make a formal note.

* Client and server negotiate a TLS connection
* Client sends `close_notify` then immediately closes socket 
* Server receives `close_notify` then attempts to send `close_notify` back

In this scenario, on CPython the server gets a `BrokenPipeError`, which makes sense – this is the usual error for trying to send data on a socket that the other side already closed. On PyPy, the server gets a `SSLEOFError: EOF occurred in violation of protocol`. This is (a) different, and (b) the text is not true, the EOF was totally legal.

(Quoth RFC 5246: "Unless some other fatal alert has been transmitted, each party is required to send a close_notify alert before closing the write side of the connection.  The other party MUST respond with a close_notify alert of its own and close down the connection immediately, discarding any pending writes.  It is not required for the initiator of the close to wait for the responding close_notify alert before closing the read side of the connection.")

Weirdly, running under strace I can see PyPy getting `EPIPE` before raising `SSLEOFError`, and then the next syscall it makes is to start looking up python files so it can print the traceback:

[pid 17390] write(4, "\25\3\3\0\32\211\327\250b\301+o\373$\363\32\t\r\231\261\256\322\f\230\362\205vr\224VD", 31) = -1 EPIPE (Broken pipe)
[pid 17390] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=17387, si_uid=1000} ---
[pid 17390] stat("/home/njs/pypy/pypy3.5-5.8-beta-linux_x86_64-portable/lib-python/3/threading.py", {st_mode=S_IFREG|0644, st_size=48919, ...}) = 0

And looking at the definition of `pyssl_error` in lib_pypy/_cffi_ssl/_stdssl/error.py, it looks like the difference between raising `SSLEOFError` and an `OSError` like `BrokenPipeError` is the setting of a "did the BIO indicate an error" flag. If that flag is broken in general then that's definitely worrisome.

Sample code here:

CC: @alex_gaynor

More information about the pypy-issue mailing list