diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 53e3574ea8a..75f3a0dabb0 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -1442,12 +1442,34 @@ static const char *tls_session_alpn_get(void *session_) return backend_session->alpn; } +static void tls_log_io_error(int ssl_error, int saved_errno) +{ + unsigned long err_code; + char err_buf[256]; + + err_code = ERR_get_error(); + + if (err_code != 0) { + ERR_error_string_n(err_code, err_buf, sizeof(err_buf) - 1); + flb_error("[tls] error: %s", err_buf); + } + else if (ssl_error == SSL_ERROR_SYSCALL && saved_errno != 0) { + flb_error("[tls] syscall error: %s", strerror(saved_errno)); + } + else if (ssl_error == SSL_ERROR_SYSCALL) { + flb_error("[tls] error: unexpected EOF"); + } + else { + flb_error("[tls] unknown error (ssl_error=%d)", ssl_error); + } +} + static int tls_net_read(struct flb_tls_session *session, void *buf, size_t len) { int ret; - unsigned long err_code; - char err_buf[256]; + int ssl_ret; + int saved_errno; struct tls_context *ctx; struct tls_session *backend_session; @@ -1465,51 +1487,43 @@ static int tls_net_read(struct flb_tls_session *session, ERR_clear_error(); + errno = 0; ret = SSL_read(backend_session->ssl, buf, len); + saved_errno = errno; if (ret <= 0) { - ret = SSL_get_error(backend_session->ssl, ret); + ssl_ret = SSL_get_error(backend_session->ssl, ret); - if (ret == SSL_ERROR_WANT_READ) { + if (ssl_ret == SSL_ERROR_WANT_READ) { ret = FLB_TLS_WANT_READ; } - else if (ret == SSL_ERROR_WANT_WRITE) { + else if (ssl_ret == SSL_ERROR_WANT_WRITE) { ret = FLB_TLS_WANT_WRITE; } - else if (ret == SSL_ERROR_SYSCALL) { - flb_errno(); - - err_code = ERR_get_error(); - - if (err_code != 0) { - ERR_error_string_n(err_code, err_buf, sizeof(err_buf)-1); - flb_error("[tls] syscall error: %s", err_buf); - } - else { - flb_error("[tls] syscall error: %s", strerror(errno)); - } + else if (ssl_ret == SSL_ERROR_ZERO_RETURN) { + ret = -1; + } + else if (ssl_ret == SSL_ERROR_SYSCALL) { + tls_log_io_error(ssl_ret, saved_errno); /* According to the documentation these are non-recoverable * errors so we don't need to screen them before saving them * to the net_error field. */ - session->connection->net_error = errno; - - ret = -1; - } - else if (ret < 0) { - err_code = ERR_get_error(); - - if (err_code != 0) { - ERR_error_string_n(err_code, err_buf, sizeof(err_buf)-1); - flb_error("[tls] error: %s", err_buf); + if (saved_errno != 0) { + session->connection->net_error = saved_errno; } else { - flb_error("[tls] error: %s", strerror(errno)); + session->connection->net_error = ECONNRESET; } + + ret = -1; } else { + tls_log_io_error(ssl_ret, saved_errno); + session->connection->net_error = ECONNRESET; + ret = -1; } } @@ -1523,8 +1537,7 @@ static int tls_net_write(struct flb_tls_session *session, { int ret; int ssl_ret; - unsigned long err_code; - char err_buf[256]; + int saved_errno; size_t total = 0; struct tls_context *ctx; struct tls_session *backend_session; @@ -1542,9 +1555,11 @@ static int tls_net_write(struct flb_tls_session *session, ERR_clear_error(); + errno = 0; ret = SSL_write(backend_session->ssl, (unsigned char *) data + total, len - total); + saved_errno = errno; if (ret <= 0) { ssl_ret = SSL_get_error(backend_session->ssl, ret); @@ -1555,40 +1570,29 @@ static int tls_net_write(struct flb_tls_session *session, else if (ssl_ret == SSL_ERROR_WANT_READ) { ret = FLB_TLS_WANT_READ; } + else if (ssl_ret == SSL_ERROR_ZERO_RETURN) { + ret = -1; + } else if (ssl_ret == SSL_ERROR_SYSCALL) { - err_code = ERR_get_error(); - - if (err_code == 0) { - if (ret == 0) { - flb_debug("[tls] connection closed"); - } - else { - flb_error("[tls] syscall error: %s", strerror(errno)); - } - } - else { - ERR_error_string_n(err_code, err_buf, sizeof(err_buf) - 1); - flb_error("[tls] syscall error: %s", err_buf); - } + tls_log_io_error(ssl_ret, saved_errno); /* According to the documentation these are non-recoverable * errors so we don't need to screen them before saving them * to the net_error field. */ - session->connection->net_error = errno; + if (saved_errno != 0) { + session->connection->net_error = saved_errno; + } + else { + session->connection->net_error = ECONNRESET; + } ret = -1; } else { - err_code = ERR_get_error(); - if (err_code == 0) { - flb_error("[tls] unknown error"); - } - else { - ERR_error_string_n(err_code, err_buf, sizeof(err_buf) - 1); - flb_error("[tls] error: %s", err_buf); - } + tls_log_io_error(ssl_ret, saved_errno); + session->connection->net_error = ECONNRESET; ret = -1; } diff --git a/tests/integration/scenarios/in_forward/tests/test_in_forward_001.py b/tests/integration/scenarios/in_forward/tests/test_in_forward_001.py index 0f5ef1db9e3..37ff24e360f 100644 --- a/tests/integration/scenarios/in_forward/tests/test_in_forward_001.py +++ b/tests/integration/scenarios/in_forward/tests/test_in_forward_001.py @@ -5,6 +5,7 @@ import shutil import socket import ssl +import struct import subprocess import sys import tempfile @@ -648,6 +649,78 @@ def _send_tls_payload(port, payload, cafile): tls_sock.sendall(payload) +def _create_tls_memory_bio_client(port, cafile): + context = ssl.create_default_context(cafile=cafile) + incoming = ssl.MemoryBIO() + outgoing = ssl.MemoryBIO() + tls = context.wrap_bio(incoming, outgoing, server_hostname="localhost") + raw_sock = socket.create_connection(("127.0.0.1", port), timeout=5) + + while True: + try: + tls.do_handshake() + break + except ssl.SSLWantReadError: + encrypted = outgoing.read() + if encrypted: + raw_sock.sendall(encrypted) + + encrypted = raw_sock.recv(65536) + assert encrypted + incoming.write(encrypted) + except ssl.SSLWantWriteError: + encrypted = outgoing.read() + if encrypted: + raw_sock.sendall(encrypted) + + encrypted = outgoing.read() + if encrypted: + raw_sock.sendall(encrypted) + + return raw_sock, tls, outgoing + + +def _reset_tls_connection(port, cafile): + raw_sock, tls, outgoing = _create_tls_memory_bio_client(port, cafile) + + tls.write(b"\x91") + wire_payload = outgoing.read() + raw_sock.sendall(wire_payload[:-1]) + time.sleep(1) + + raw_sock.setsockopt( + socket.SOL_SOCKET, + socket.SO_LINGER, + struct.pack("ii", 1, 0), + ) + raw_sock.close() + + +def _send_corrupted_tls_record(port, cafile): + raw_sock, tls, outgoing = _create_tls_memory_bio_client(port, cafile) + + tls.write(b"\x91") + wire_payload = bytearray(outgoing.read()) + wire_payload[-1] ^= 1 + raw_sock.sendall(wire_payload) + + return raw_sock + + +def _send_tls_close_notify(port, cafile): + raw_sock, tls, outgoing = _create_tls_memory_bio_client(port, cafile) + + try: + tls.unwrap() + except ssl.SSLWantReadError: + pass + + wire_payload = outgoing.read() + assert wire_payload + raw_sock.sendall(wire_payload) + raw_sock.close() + + def _recv_msgpack_value(sock): sock.settimeout(5) data = sock.recv(4096) @@ -1027,6 +1100,90 @@ def test_in_forward_tls_message_mode(): assert records[0]["message"] == "tls-message" +def test_in_forward_tls_syscall_error_preserves_errno(): + service = Service("in_forward_tls.yaml") + service.start() + + try: + _reset_tls_connection( + service.flb_listener_port, + service.tls_crt_file, + ) + log_text = service.wait_for_log_contains("[tls] syscall error:", timeout=10) + + payload = _message_mode_payload( + TEST_TAG, + {"message": "after-tls-reset"}, + ) + _send_tls_payload(service.flb_listener_port, payload, service.tls_crt_file) + records = service.wait_for_record_count(1, timeout=10) + finally: + service.stop() + + assert "Connection reset by peer" in log_text + assert "Inappropriate ioctl for device" not in log_text + assert not any( + "openssl.c:" in line and "errno=" in line + for line in log_text.splitlines() + ) + assert records[0]["message"] == "after-tls-reset" + + +def test_in_forward_tls_protocol_error_is_reported(): + service = Service("in_forward_tls.yaml") + protocol_sock = None + service.start() + + try: + protocol_sock = _send_corrupted_tls_record( + service.flb_listener_port, + service.tls_crt_file, + ) + log_text = service.wait_for_log_contains("[tls] error:", timeout=10) + protocol_sock.close() + protocol_sock = None + + payload = _message_mode_payload( + TEST_TAG, + {"message": "after-tls-protocol-error"}, + ) + _send_tls_payload(service.flb_listener_port, payload, service.tls_crt_file) + records = service.wait_for_record_count(1, timeout=10) + finally: + if protocol_sock is not None: + protocol_sock.close() + service.stop() + + assert "bad record mac" in log_text.lower() + assert "unknown error" not in log_text + assert records[0]["message"] == "after-tls-protocol-error" + + +def test_in_forward_tls_close_notify_is_not_an_error(): + service = Service("in_forward_tls.yaml") + service.start() + + try: + _send_tls_close_notify( + service.flb_listener_port, + service.tls_crt_file, + ) + time.sleep(1) + + payload = _message_mode_payload( + TEST_TAG, + {"message": "after-tls-close-notify"}, + ) + _send_tls_payload(service.flb_listener_port, payload, service.tls_crt_file) + records = service.wait_for_record_count(1, timeout=10) + log_text = read_file(service.service.flb.log_file) + finally: + service.stop() + + assert "[tls] unknown error (ssl_error=6)" not in log_text + assert records[0]["message"] == "after-tls-close-notify" + + def test_in_forward_tls_idle_connection_timeout_churn(monkeypatch): """ Regression test for issue #12025.