Skip to content

Commit e6eb48c

Browse files
zoobatiran
authored andcommitted
bpo-31400: Improve SSL error handling on Windows (#3463)
* bpo-31392: Improve SSL error handling on Windows * Remove unnecessary Windows mention in NEWS
1 parent 0915360 commit e6eb48c

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improves SSL error handling to avoid losing error numbers.

Modules/_ssl.c

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@ typedef struct {
321321
enum py_ssl_server_or_client socket_type;
322322
PyObject *owner; /* Python level "owner" passed to servername callback */
323323
PyObject *server_hostname;
324+
int ssl_errno; /* last seen error from SSL */
325+
int c_errno; /* last seen error from libc */
326+
#ifdef MS_WINDOWS
327+
int ws_errno; /* last seen error from winsock */
328+
#endif
324329
} PySSLSocket;
325330

326331
typedef struct {
@@ -340,6 +345,21 @@ static PyTypeObject PySSLSocket_Type;
340345
static PyTypeObject PySSLMemoryBIO_Type;
341346
static PyTypeObject PySSLSession_Type;
342347

348+
#ifdef MS_WINDOWS
349+
#define _PySSL_UPDATE_ERRNO_IF(cond, sock, retcode) if (cond) { \
350+
(sock)->ws_errno = WSAGetLastError(); \
351+
_PySSL_FIX_ERRNO; \
352+
(sock)->c_errno = errno; \
353+
(sock)->ssl_errno = SSL_get_error((sock->ssl), (retcode)); \
354+
} else { sock->ws_errno = 0; sock->c_errno = 0; sock->ssl_errno = 0; }
355+
#else
356+
#define _PySSL_UPDATE_ERRNO_IF(cond, sock, retcode) if (cond) { \
357+
(sock)->c_errno = errno; \
358+
(sock)->ssl_errno = SSL_get_error((sock->ssl), (retcode)); \
359+
} else { (sock)->c_errno = 0; (sock)->ssl_errno = 0; }
360+
#endif
361+
#define _PySSL_UPDATE_ERRNO(sock, retcode) _PySSL_UPDATE_ERRNO_IF(1, (sock), (retcode))
362+
343363
/*[clinic input]
344364
module _ssl
345365
class _ssl._SSLContext "PySSLContext *" "&PySSLContext_Type"
@@ -580,7 +600,7 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
580600
e = ERR_peek_last_error();
581601

582602
if (sslsock->ssl != NULL) {
583-
err = SSL_get_error(sslsock->ssl, ret);
603+
err = sslsock->ssl_errno;
584604

585605
switch (err) {
586606
case SSL_ERROR_ZERO_RETURN:
@@ -616,8 +636,16 @@ PySSL_SetError(PySSLSocket *sslsock, int ret, const char *filename, int lineno)
616636
errstr = "EOF occurred in violation of protocol";
617637
} else if (s && ret == -1) {
618638
/* underlying BIO reported an I/O error */
619-
Py_INCREF(s);
620639
ERR_clear_error();
640+
#ifdef MS_WINDOWS
641+
if (sslsock->ws_errno)
642+
return PyErr_SetFromWindowsErr(sslsock->ws_errno);
643+
#endif
644+
if (sslsock->c_errno) {
645+
errno = sslsock->c_errno;
646+
return PyErr_SetFromErrno(PyExc_OSError);
647+
}
648+
Py_INCREF(s);
621649
s->errorhandler();
622650
Py_DECREF(s);
623651
return NULL;
@@ -696,6 +724,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
696724
}
697725
self->server_hostname = hostname;
698726
}
727+
self->ssl_errno = 0;
728+
self->c_errno = 0;
729+
#ifdef MS_WINDOWS
730+
self->ws_errno = 0;
731+
#endif
699732

700733
/* Make sure the SSL error state is initialized */
701734
(void) ERR_get_state();
@@ -792,8 +825,9 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
792825
do {
793826
PySSL_BEGIN_ALLOW_THREADS
794827
ret = SSL_do_handshake(self->ssl);
795-
err = SSL_get_error(self->ssl, ret);
828+
_PySSL_UPDATE_ERRNO_IF(ret < 1, self, ret);
796829
PySSL_END_ALLOW_THREADS
830+
err = self->ssl_errno;
797831

798832
if (PyErr_CheckSignals())
799833
goto error;
@@ -2064,8 +2098,9 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
20642098
do {
20652099
PySSL_BEGIN_ALLOW_THREADS
20662100
len = SSL_write(self->ssl, b->buf, (int)b->len);
2067-
err = SSL_get_error(self->ssl, len);
2101+
_PySSL_UPDATE_ERRNO_IF(len <= 0, self, len);
20682102
PySSL_END_ALLOW_THREADS
2103+
err = self->ssl_errno;
20692104

20702105
if (PyErr_CheckSignals())
20712106
goto error;
@@ -2119,6 +2154,7 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
21192154

21202155
PySSL_BEGIN_ALLOW_THREADS
21212156
count = SSL_pending(self->ssl);
2157+
_PySSL_UPDATE_ERRNO_IF(count < 0, self, count);
21222158
PySSL_END_ALLOW_THREADS
21232159
if (count < 0)
21242160
return PySSL_SetError(self, count, __FILE__, __LINE__);
@@ -2207,7 +2243,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
22072243
do {
22082244
PySSL_BEGIN_ALLOW_THREADS
22092245
count = SSL_read(self->ssl, mem, len);
2210-
err = SSL_get_error(self->ssl, count);
2246+
_PySSL_UPDATE_ERRNO_IF(count <= 0, self, count);
22112247
PySSL_END_ALLOW_THREADS
22122248

22132249
if (PyErr_CheckSignals())
@@ -2216,6 +2252,7 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, int len, int group_right_1,
22162252
if (has_timeout)
22172253
timeout = deadline - _PyTime_GetMonotonicClock();
22182254

2255+
err = self->ssl_errno;
22192256
if (err == SSL_ERROR_WANT_READ) {
22202257
sockstate = PySSL_select(sock, 0, timeout);
22212258
} else if (err == SSL_ERROR_WANT_WRITE) {
@@ -2272,7 +2309,7 @@ static PyObject *
22722309
_ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
22732310
/*[clinic end generated code: output=ca1aa7ed9d25ca42 input=ede2cc1a2ddf0ee4]*/
22742311
{
2275-
int err, ssl_err, sockstate, nonblocking;
2312+
int err, sockstate, nonblocking;
22762313
int zeros = 0;
22772314
PySocketSockObject *sock = GET_SOCKET(self);
22782315
_PyTime_t timeout, deadline = 0;
@@ -2311,6 +2348,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
23112348
if (self->shutdown_seen_zero)
23122349
SSL_set_read_ahead(self->ssl, 0);
23132350
err = SSL_shutdown(self->ssl);
2351+
_PySSL_UPDATE_ERRNO_IF(err < 0, self, err);
23142352
PySSL_END_ALLOW_THREADS
23152353

23162354
/* If err == 1, a secure shutdown with SSL_shutdown() is complete */
@@ -2331,16 +2369,16 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
23312369
timeout = deadline - _PyTime_GetMonotonicClock();
23322370

23332371
/* Possibly retry shutdown until timeout or failure */
2334-
ssl_err = SSL_get_error(self->ssl, err);
2335-
if (ssl_err == SSL_ERROR_WANT_READ)
2372+
_PySSL_UPDATE_ERRNO(self, err);
2373+
if (self->ssl_errno == SSL_ERROR_WANT_READ)
23362374
sockstate = PySSL_select(sock, 0, timeout);
2337-
else if (ssl_err == SSL_ERROR_WANT_WRITE)
2375+
else if (self->ssl_errno == SSL_ERROR_WANT_WRITE)
23382376
sockstate = PySSL_select(sock, 1, timeout);
23392377
else
23402378
break;
23412379

23422380
if (sockstate == SOCKET_HAS_TIMED_OUT) {
2343-
if (ssl_err == SSL_ERROR_WANT_READ)
2381+
if (self->ssl_errno == SSL_ERROR_WANT_READ)
23442382
PyErr_SetString(PySocketModule.timeout_error,
23452383
"The read operation timed out");
23462384
else

0 commit comments

Comments
 (0)