[Python-checkins] bpo-31400: Improve SSL error handling on Windows (#3463)

Christian Heimes webhook-mailer at python.org
Fri Sep 8 18:16:18 EDT 2017


https://github.com/python/cpython/commit/e6eb48c10dc389d1d70657593de6a6cb3087d3d1
commit: e6eb48c10dc389d1d70657593de6a6cb3087d3d1
branch: master
author: Steve Dower <steve.dower at microsoft.com>
committer: Christian Heimes <christian at python.org>
date: 2017-09-08T15:16:15-07:00
summary:

bpo-31400: Improve SSL error handling on Windows (#3463)

* bpo-31392: Improve SSL error handling on Windows

* Remove unnecessary Windows mention in NEWS

files:
A Misc/NEWS.d/next/Library/2017-09-08-14-19-57.bpo-31400.YOTPKi.rst
M Modules/_ssl.c

diff --git a/Misc/NEWS.d/next/Library/2017-09-08-14-19-57.bpo-31400.YOTPKi.rst b/Misc/NEWS.d/next/Library/2017-09-08-14-19-57.bpo-31400.YOTPKi.rst
new file mode 100644
index 00000000000..1321300dbd4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-09-08-14-19-57.bpo-31400.YOTPKi.rst
@@ -0,0 +1 @@
+Improves SSL error handling to avoid losing error numbers.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 5ec31a77365..bd22f16fae7 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -321,6 +321,11 @@ typedef struct {
     enum py_ssl_server_or_client socket_type;
     PyObject *owner; /* Python level "owner" passed to servername callback */
     PyObject *server_hostname;
+    int ssl_errno; /* last seen error from SSL */
+    int c_errno; /* last seen error from libc */
+#ifdef MS_WINDOWS
+    int ws_errno; /* last seen error from winsock */
+#endif
 } PySSLSocket;
 
 typedef struct {
@@ -340,6 +345,21 @@ static PyTypeObject PySSLSocket_Type;
 static PyTypeObject PySSLMemoryBIO_Type;
 static PyTypeObject PySSLSession_Type;
 
+#ifdef MS_WINDOWS
+#define _PySSL_UPDATE_ERRNO_IF(cond, sock, retcode) if (cond) { \
+        (sock)->ws_errno = WSAGetLastError(); \
+        _PySSL_FIX_ERRNO; \
+        (sock)->c_errno = errno; \
+        (sock)->ssl_errno = SSL_get_error((sock->ssl), (retcode)); \
+    } else { sock->ws_errno = 0; sock->c_errno = 0; sock->ssl_errno = 0; }
+#else
+#define _PySSL_UPDATE_ERRNO_IF(cond, sock, retcode) if (cond) { \
+        (sock)->c_errno = errno; \
+        (sock)->ssl_errno = SSL_get_error((sock->ssl), (retcode)); \
+    } else { (sock)->c_errno = 0; (sock)->ssl_errno = 0; }
+#endif
+#define _PySSL_UPDATE_ERRNO(sock, retcode) _PySSL_UPDATE_ERRNO_IF(1, (sock), (retcode))
+
 /*[clinic input]
 module _ssl
 class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type"
@@ -580,7 +600,7 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
     e = ERR_peek_last_error();
 
     if (sslsock->ssl != NULL) {
-        err = SSL_get_error(sslsock->ssl, ret);
+        err = sslsock->ssl_errno;
 
         switch (err) {
         case SSL_ERROR_ZERO_RETURN:
@@ -616,8 +636,16 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
                     errstr = "EOF occurred in violation of protocol";
                 } else if (s && ret == -1) {
                     /* underlying BIO reported an I/O error */
-                    Py_INCREF(s);
                     ERR_clear_error();
+#ifdef MS_WINDOWS
+                    if (sslsock->ws_errno)
+                        return PyErr_SetFromWindowsErr(sslsock->ws_errno);
+#endif
+                    if (sslsock->c_errno) {
+                        errno = sslsock->c_errno;
+                        return PyErr_SetFromErrno(PyExc_OSError);
+                    }
+                    Py_INCREF(s);
                     s->errorhandler();
                     Py_DECREF(s);
                     return NULL;
@@ -696,6 +724,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
         }
         self->server_hostname = hostname;
     }
+    self->ssl_errno = 0;
+    self->c_errno = 0;
+#ifdef MS_WINDOWS
+    self->ws_errno = 0;
+#endif
 
     /* Make sure the SSL error state is initialized */
     (void) ERR_get_state();
@@ -792,8 +825,9 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
     do {
         PySSL_BEGIN_ALLOW_THREADS
         ret = SSL_do_handshake(self->ssl);
-        err = SSL_get_error(self->ssl, ret);
+        _PySSL_UPDATE_ERRNO_IF(ret < 1, self, ret);
         PySSL_END_ALLOW_THREADS
+        err = self->ssl_errno;
 
         if (PyErr_CheckSignals())
             goto error;
@@ -2064,8 +2098,9 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
     do {
         PySSL_BEGIN_ALLOW_THREADS
         len = SSL_write(self->ssl, b->buf, (int)b->len);
-        err = SSL_get_error(self->ssl, len);
+        _PySSL_UPDATE_ERRNO_IF(len <= 0, self, len);
         PySSL_END_ALLOW_THREADS
+        err = self->ssl_errno;
 
         if (PyErr_CheckSignals())
             goto error;
@@ -2119,6 +2154,7 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
 
     PySSL_BEGIN_ALLOW_THREADS
     count = SSL_pending(self->ssl);
+    _PySSL_UPDATE_ERRNO_IF(count < 0, self, count);
     PySSL_END_ALLOW_THREADS
     if (count < 0)
         return PySSL_SetError(self, count, __FILE__, __LINE__);
@@ -2207,7 +2243,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
     do {
         PySSL_BEGIN_ALLOW_THREADS
         count = SSL_read(self->ssl, mem, len);
-        err = SSL_get_error(self->ssl, count);
+        _PySSL_UPDATE_ERRNO_IF(count <= 0, self, count);
         PySSL_END_ALLOW_THREADS
 
         if (PyErr_CheckSignals())
@@ -2216,6 +2252,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
         if (has_timeout)
             timeout = deadline - _PyTime_GetMonotonicClock();
 
+        err = self->ssl_errno;
         if (err == SSL_ERROR_WANT_READ) {
             sockstate = PySSL_select(sock, 0, timeout);
         } else if (err == SSL_ERROR_WANT_WRITE) {
@@ -2272,7 +2309,7 @@ static PyObject *
 _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
 /*[clinic end generated code: output=ca1aa7ed9d25ca42 input=ede2cc1a2ddf0ee4]*/
 {
-    int err, ssl_err, sockstate, nonblocking;
+    int err, sockstate, nonblocking;
     int zeros = 0;
     PySocketSockObject *sock = GET_SOCKET(self);
     _PyTime_t timeout, deadline = 0;
@@ -2311,6 +2348,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
         if (self->shutdown_seen_zero)
             SSL_set_read_ahead(self->ssl, 0);
         err = SSL_shutdown(self->ssl);
+        _PySSL_UPDATE_ERRNO_IF(err < 0, self, err);
         PySSL_END_ALLOW_THREADS
 
         /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
@@ -2331,16 +2369,16 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
             timeout = deadline - _PyTime_GetMonotonicClock();
 
         /* Possibly retry shutdown until timeout or failure */
-        ssl_err = SSL_get_error(self->ssl, err);
-        if (ssl_err == SSL_ERROR_WANT_READ)
+        _PySSL_UPDATE_ERRNO(self, err);
+        if (self->ssl_errno == SSL_ERROR_WANT_READ)
             sockstate = PySSL_select(sock, 0, timeout);
-        else if (ssl_err == SSL_ERROR_WANT_WRITE)
+        else if (self->ssl_errno == SSL_ERROR_WANT_WRITE)
             sockstate = PySSL_select(sock, 1, timeout);
         else
             break;
 
         if (sockstate == SOCKET_HAS_TIMED_OUT) {
-            if (ssl_err == SSL_ERROR_WANT_READ)
+            if (self->ssl_errno == SSL_ERROR_WANT_READ)
                 PyErr_SetString(PySocketModule.timeout_error,
                                 "The read operation timed out");
             else



More information about the Python-checkins mailing list