diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 72e8379ac5f..16d95245c2d 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -1556,12 +1556,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; @@ -1579,40 +1601,37 @@ 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_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; + if (saved_errno != 0) { + session->connection->net_error = saved_errno; + } + else { + session->connection->net_error = ECONNRESET; + } ret = -1; } - else if (ret == SSL_ERROR_ZERO_RETURN) { + else if (ssl_ret == SSL_ERROR_ZERO_RETURN) { flb_debug("[tls] connection closed by the remote peer " "(close_notify)"); @@ -1629,18 +1648,10 @@ static int tls_net_read(struct flb_tls_session *session, 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); - } - else { - flb_error("[tls] error: %s", strerror(errno)); - } - } else { + tls_log_io_error(ssl_ret, saved_errno); + session->connection->net_error = ECONNRESET; + ret = -1; } } @@ -1654,8 +1665,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; @@ -1673,9 +1683,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); @@ -1687,27 +1699,19 @@ static int tls_net_write(struct flb_tls_session *session, ret = FLB_TLS_WANT_READ; } 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; } @@ -1729,14 +1733,8 @@ static int tls_net_write(struct flb_tls_session *session, 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 ee2d797477d..f1ca1311d3b 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 @@ -6,6 +6,7 @@ import shutil import socket import ssl +import struct import subprocess import sys import tempfile @@ -709,6 +710,33 @@ def _create_tls_memory_bio_client(port, cafile): 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 _create_partial_forward_client(service, payload, use_tls): if use_tls: sock, tls, outgoing = _create_tls_memory_bio_client( @@ -1412,6 +1440,65 @@ def test_in_forward_downstream_coro_eof_and_error_keep_listener_responsive( assert records[0]["message"] == "after-io-errors" +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_forward_payload(service, payload, True) + 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_forward_payload(service, payload, True) + 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" + + @pytest.mark.parametrize( "config_file,use_tls", [