Description
Bug report
Bug description:
It was dealt with other issues before, but the contents are not intuitive, so I rearrange them.
The SSL_write()
and SSL_read()
used in python 3.9 were modified from python 3.10 to SSL_write_ex()
and SSL_read_ex()
, and the return value was fixed to 0 on error. However, in PySSL_SetError
, there is no update accordingly, so this part needs to be modified.
Related issue can be referred to the following link.
OpenSSL 1.1.1: use SSL_write_ex() and SSL_read_ex()
The difference between SSL_write()
and SSL_write_ex()
is as follows.
int SSL_write(SSL *s, const void *buf, int num)
{
int ret;
size_t written;
if (num < 0) {
ERR_raise(ERR_LIB_SSL, SSL_R_BAD_LENGTH);
return -1;
}
ret = ssl_write_internal(s, buf, (size_t)num, 0, &written);
/*
* The cast is safe here because ret should be <= INT_MAX because num is
* <= INT_MAX
*/
if (ret > 0)
ret = (int)written;
return ret;
}
SSL_write()
has a return value of 0 or less when an error occurs.
int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written)
{
return SSL_write_ex2(s, buf, num, 0, written);
}
int SSL_write_ex2(SSL *s, const void *buf, size_t num, uint64_t flags,
size_t *written)
{
int ret = ssl_write_internal(s, buf, num, flags, written);
if (ret < 0)
ret = 0;
return ret;
}
SSL_write_ex()
is fixed to 0 when an error occurs. As the return value changes, the behavior of the PySSL_SetError
function changes.
Line 648 in f9154f8
case SSL_ERROR_SYSCALL:
{
if (e == 0) {
PySocketSockObject *s = GET_SOCKET(sslsock);
if (ret == 0 || (((PyObject *)s) == Py_None)) {
p = PY_SSL_ERROR_EOF;
type = state->PySSLEOFErrorObject;
errstr = "EOF occurred in violation of protocol";
} else if (s && ret == -1) {
/* underlying BIO reported an I/O error */
ERR_clear_error();
#ifdef MS_WINDOWS
if (err.ws) {
return PyErr_SetFromWindowsErr(err.ws);
}
#endif
if (err.c) {
errno = err.c;
return PyErr_SetFromErrno(PyExc_OSError);
}
else {
p = PY_SSL_ERROR_EOF;
type = state->PySSLEOFErrorObject;
errstr = "EOF occurred in violation of protocol";
}
} else { /* possible? */
p = PY_SSL_ERROR_SYSCALL;
type = state->PySSLSyscallErrorObject;
errstr = "Some I/O error occurred";
}
} else {
if (ERR_GET_LIB(e) == ERR_LIB_SSL &&
ERR_GET_REASON(e) == SSL_R_CERTIFICATE_VERIFY_FAILED) {
type = state->PySSLCertVerificationErrorObject;
}
p = PY_SSL_ERROR_SYSCALL;
}
break;
}
According to the part if (ret == 0 || ((PyObject *) == Py_None))
, the retrun of the function SSL_write_ex()
is fixed to 0, resulting in SSLEOFError
instead of OSError
.
I determined that the change to this error was not intended. If the connection is terminated by the other peer, it is better for the user to respond to it by returning it as an error such as BrokenPIPE rather than an error that is a protocol rule violation.
The modifications affected by the change have been identified as follows.
- The behavior of the SSL test server for OSError has changed. ConnectionError did not previously shut down the server, but now it does.
- I think this is because the change has not been tested as OSError does not occur at write or read.
- It is believed that an intermittent EOF error occurred instead of an OSError.
- OSError has disappeared, only EOF errors have been handled.
Related issues are as follows.
CPython versions tested on:
3.10, 3.11, 3.12, 3.13, CPython main branch
Operating systems tested on:
Linux, macOS