def stop(self):
self.active = False
+class TestEOFServer(threading.Thread):
+ def __init__(self):
+ super().__init__()
+ self.listening = threading.Event()
+ self.address = None
+
+ def run(self):
+ context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ context.load_cert_chain(CERTFILE)
+ server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ with server_sock:
+ server_sock.settimeout(support.SHORT_TIMEOUT)
+ server_sock.bind((HOST, 0))
+ server_sock.listen(5)
+
+ self.address = server_sock.getsockname()
+ self.listening.set()
+
+ sock, addr = server_sock.accept()
+ sslconn = context.wrap_socket(sock, server_side=True)
+ with sslconn:
+ request = b''
+ while chunk := sslconn.recv(1024):
+ request += chunk
+ if b'\n' in chunk:
+ break
+
+ sslconn.sendall(b'server\n')
+ sslconn.shutdown(socket.SHUT_WR)
+
class AsyncoreEchoServer(threading.Thread):
# this one's based on asyncore.dispatcher
if cm.exc_value is not None:
raise cm.exc_value
+ def test_got_eof(self):
+ # gh-148292: Test that _ssl._SSLSocket behaves the same on all OpenSSL
+ # versions on calling methods after EOF (after the first SSLEOFError).
+
+ server = TestEOFServer()
+ server.start()
+ if not server.listening.wait(support.SHORT_TIMEOUT):
+ raise RuntimeError("server took too long")
+ self.addCleanup(server.join)
+
+ context = ssl.create_default_context(cafile=CERTFILE)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(support.SHORT_TIMEOUT)
+ sock.connect(server.address)
+ sslsock = context.wrap_socket(sock, server_hostname='localhost')
+ with sslsock:
+ sslsock.sendall(b'client\n')
+ # test the _ssl._SSLSocket object, not ssl.SSLSocket
+ sslobj = sslsock._sslobj
+
+ data = sslobj.read(1024)
+ self.assertEqual(data, b'server\n')
+
+ # The second read gets EOF error and sets got_eof_error to 1
+ with self.assertRaises(ssl.SSLEOFError):
+ sslobj.read(1024)
+
+ # Following read(), sendfile(), write() and do_handshake() calls
+ # must raise SSLEOFError
+ with self.assertRaises(ssl.SSLEOFError):
+ # The _SSLSocket remembers the previous EOF error
+ # and raises again SSLEOFError
+ sslobj.read(1024)
+ if hasattr(sslobj, 'sendfile'):
+ with open(__file__, "rb") as fp:
+ with self.assertRaises(ssl.SSLEOFError):
+ sslobj.sendfile(fp.fileno(), 0, 1)
+ with self.assertRaises(ssl.SSLEOFError):
+ sslobj.write(b'client2\n')
+ with self.assertRaises(ssl.SSLEOFError):
+ sslsock.do_handshake()
+
+ self.assertEqual(sslsock.pending(), 0)
+ try:
+ sslsock.shutdown(socket.SHUT_WR)
+ except OSError as exc:
+ self.assertEqual(exc.errno, errno.ENOTCONN)
+ else:
+ # On Windows and on OpenSSL 1.1.1, shutdown() doesn't
+ # raise an error
+ pass
+
@unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA,
"Test needs TLS 1.3 PHA")
enum py_ssl_server_or_client socket_type;
PyObject *owner; /* weakref to Python level "owner" passed to servername callback */
PyObject *server_hostname;
+ // gh-148292: If non-zero, read(), sendfile(), write() and do_handshake()
+ // methods raise SSLEOFError without calling the underlying OpenSSL
+ // function. Set to 1 on PY_SSL_ERROR_EOF error.
+ //
+ // On OpenSSL 4, if SSL_read_ex() fails with
+ // SSL_R_UNEXPECTED_EOF_WHILE_READING, the following SSL_read_ex() call
+ // fails with a generic protocol error (ERR_peek_last_error() returns 0).
+ // Use got_eof_error to have the same behavior on OpenSSL 4 and newer and
+ // on OpenSSL 3 and older.
+ int got_eof_error;
} PySSLSocket;
#define PySSLSocket_CAST(op) ((PySSLSocket *)(op))
PyObject *init_value, *msg, *key;
PyUnicodeWriter *writer = NULL;
+ if (ssl_errno == PY_SSL_ERROR_EOF && sslsock != NULL) {
+ sslsock->got_eof_error = 1;
+ }
+
if (errcode != 0) {
int lib, reason;
PyUnicodeWriter_Discard(writer);
}
+
+static void
+set_eof_error(PySSLSocket *sslsock)
+{
+ _sslmodulestate *state = get_state_sock(sslsock);
+ fill_and_set_sslerror(state, sslsock, state->PySSLEOFErrorObject,
+ PY_SSL_ERROR_EOF,
+ "EOF occurred in violation of protocol",
+ __LINE__, 0);
+}
+
+
// Set the appropriate SSL error exception.
// err - error information from SSL and libc
// exc - if not NULL, an exception from _debughelpers.c callback to be chained
self->shutdown_seen_zero = 0;
self->owner = NULL;
self->server_hostname = NULL;
+ self->got_eof_error = 0;
/* Make sure the SSL error state is initialized */
ERR_clear_error();
return NULL;
}
+ if (self->got_eof_error) {
+ set_eof_error(self);
+ goto error;
+ }
+
timeout = GET_SOCKET_TIMEOUT(sock);
has_timeout = (timeout > 0);
if (has_timeout) {
return NULL;
}
+ if (self->got_eof_error) {
+ set_eof_error(self);
+ goto error;
+ }
+
timeout = GET_SOCKET_TIMEOUT(sock);
has_timeout = (timeout > 0);
if (has_timeout) {
return NULL;
}
+ if (self->got_eof_error) {
+ set_eof_error(self);
+ goto error;
+ }
+
timeout = GET_SOCKET_TIMEOUT(sock);
has_timeout = (timeout > 0);
if (has_timeout) {
return NULL;
}
+ if (self->got_eof_error) {
+ set_eof_error(self);
+ goto error;
+ }
+
if (!group_right_1) {
if (len == 0) {
Py_XDECREF(sock);