[3.6] bpo-31400: Improve SSL error handling on Windows (GH-3463) (#3466)

* bpo-31392: Improve SSL error handling on Windows

* Remove unnecessary Windows mention in NEWS.
(cherry picked from commit e6eb48c10d)
This commit is contained in:
Christian Heimes 2017-09-09 08:22:11 -07:00 committed by GitHub
parent a4baf1c543
commit 16f16dbd0e
2 changed files with 48 additions and 10 deletions

View file

@ -0,0 +1 @@
Improves SSL error handling to avoid losing error numbers.

View file

@ -302,6 +302,11 @@ typedef struct {
enum py_ssl_server_or_client socket_type; enum py_ssl_server_or_client socket_type;
PyObject *owner; /* Python level "owner" passed to servername callback */ PyObject *owner; /* Python level "owner" passed to servername callback */
PyObject *server_hostname; 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; } PySSLSocket;
typedef struct { typedef struct {
@ -321,6 +326,20 @@ static PyTypeObject PySSLSocket_Type;
static PyTypeObject PySSLMemoryBIO_Type; static PyTypeObject PySSLMemoryBIO_Type;
static PyTypeObject PySSLSession_Type; static PyTypeObject PySSLSession_Type;
#ifdef MS_WINDOWS
#define _PySSL_UPDATE_ERRNO_IF(cond, sock, retcode) if (cond) { \
(sock)->ws_errno = WSAGetLastError(); \
(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] /*[clinic input]
module _ssl module _ssl
class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type" class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type"
@ -494,7 +513,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
e = ERR_peek_last_error(); e = ERR_peek_last_error();
if (obj->ssl != NULL) { if (obj->ssl != NULL) {
err = SSL_get_error(obj->ssl, ret); err = obj->ssl_errno;
switch (err) { switch (err) {
case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_ZERO_RETURN:
@ -530,8 +549,16 @@ PySSL_SetError(PySSLSocket *obj, int ret, const char *filename, int lineno)
errstr = "EOF occurred in violation of protocol"; errstr = "EOF occurred in violation of protocol";
} else if (s && ret == -1) { } else if (s && ret == -1) {
/* underlying BIO reported an I/O error */ /* underlying BIO reported an I/O error */
Py_INCREF(s);
ERR_clear_error(); ERR_clear_error();
#ifdef MS_WINDOWS
if (obj->ws_errno)
return PyErr_SetFromWindowsErr(obj->ws_errno);
#endif
if (obj->c_errno) {
errno = obj->c_errno;
return PyErr_SetFromErrno(PyExc_OSError);
}
Py_INCREF(s);
s->errorhandler(); s->errorhandler();
Py_DECREF(s); Py_DECREF(s);
return NULL; return NULL;
@ -609,6 +636,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
} }
self->server_hostname = hostname; 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 */ /* Make sure the SSL error state is initialized */
(void) ERR_get_state(); (void) ERR_get_state();
@ -706,8 +738,9 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
do { do {
PySSL_BEGIN_ALLOW_THREADS PySSL_BEGIN_ALLOW_THREADS
ret = SSL_do_handshake(self->ssl); 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 PySSL_END_ALLOW_THREADS
err = self->ssl_errno;
if (PyErr_CheckSignals()) if (PyErr_CheckSignals())
goto error; goto error;
@ -2003,8 +2036,9 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
do { do {
PySSL_BEGIN_ALLOW_THREADS PySSL_BEGIN_ALLOW_THREADS
len = SSL_write(self->ssl, b->buf, (int)b->len); 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 PySSL_END_ALLOW_THREADS
err = self->ssl_errno;
if (PyErr_CheckSignals()) if (PyErr_CheckSignals())
goto error; goto error;
@ -2058,6 +2092,7 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
PySSL_BEGIN_ALLOW_THREADS PySSL_BEGIN_ALLOW_THREADS
count = SSL_pending(self->ssl); count = SSL_pending(self->ssl);
_PySSL_UPDATE_ERRNO_IF(count < 0, self, count);
PySSL_END_ALLOW_THREADS PySSL_END_ALLOW_THREADS
if (count < 0) if (count < 0)
return PySSL_SetError(self, count, __FILE__, __LINE__); return PySSL_SetError(self, count, __FILE__, __LINE__);
@ -2146,7 +2181,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
do { do {
PySSL_BEGIN_ALLOW_THREADS PySSL_BEGIN_ALLOW_THREADS
count = SSL_read(self->ssl, mem, len); 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 PySSL_END_ALLOW_THREADS
if (PyErr_CheckSignals()) if (PyErr_CheckSignals())
@ -2155,6 +2190,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
if (has_timeout) if (has_timeout)
timeout = deadline - _PyTime_GetMonotonicClock(); timeout = deadline - _PyTime_GetMonotonicClock();
err = self->ssl_errno;
if (err == SSL_ERROR_WANT_READ) { if (err == SSL_ERROR_WANT_READ) {
sockstate = PySSL_select(sock, 0, timeout); sockstate = PySSL_select(sock, 0, timeout);
} else if (err == SSL_ERROR_WANT_WRITE) { } else if (err == SSL_ERROR_WANT_WRITE) {
@ -2211,7 +2247,7 @@ static PyObject *
_ssl__SSLSocket_shutdown_impl(PySSLSocket *self) _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
/*[clinic end generated code: output=ca1aa7ed9d25ca42 input=ede2cc1a2ddf0ee4]*/ /*[clinic end generated code: output=ca1aa7ed9d25ca42 input=ede2cc1a2ddf0ee4]*/
{ {
int err, ssl_err, sockstate, nonblocking; int err, sockstate, nonblocking;
int zeros = 0; int zeros = 0;
PySocketSockObject *sock = GET_SOCKET(self); PySocketSockObject *sock = GET_SOCKET(self);
_PyTime_t timeout, deadline = 0; _PyTime_t timeout, deadline = 0;
@ -2250,6 +2286,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
if (self->shutdown_seen_zero) if (self->shutdown_seen_zero)
SSL_set_read_ahead(self->ssl, 0); SSL_set_read_ahead(self->ssl, 0);
err = SSL_shutdown(self->ssl); err = SSL_shutdown(self->ssl);
_PySSL_UPDATE_ERRNO_IF(err < 0, self, err);
PySSL_END_ALLOW_THREADS PySSL_END_ALLOW_THREADS
/* If err == 1, a secure shutdown with SSL_shutdown() is complete */ /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
@ -2270,16 +2307,16 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
timeout = deadline - _PyTime_GetMonotonicClock(); timeout = deadline - _PyTime_GetMonotonicClock();
/* Possibly retry shutdown until timeout or failure */ /* Possibly retry shutdown until timeout or failure */
ssl_err = SSL_get_error(self->ssl, err); _PySSL_UPDATE_ERRNO(self, err);
if (ssl_err == SSL_ERROR_WANT_READ) if (self->ssl_errno == SSL_ERROR_WANT_READ)
sockstate = PySSL_select(sock, 0, timeout); 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); sockstate = PySSL_select(sock, 1, timeout);
else else
break; break;
if (sockstate == SOCKET_HAS_TIMED_OUT) { 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, PyErr_SetString(PySocketModule.timeout_error,
"The read operation timed out"); "The read operation timed out");
else else