From 462371e7979b9f080f667676f967ae78aad04941 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 30 Jul 2026 17:06:15 -0600 Subject: [PATCH 1/4] tls: openssl: preserve syscall error details Signed-off-by: Eduardo Silva --- src/tls/openssl.c | 74 +++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/tls/openssl.c b/src/tls/openssl.c index 72e8379ac5f..e3bdf5d5fb1 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -1556,10 +1556,34 @@ static const char *tls_session_alpn_get(void *session_) return backend_session->alpn; } +static void tls_log_syscall_error(int io_ret, 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 (saved_errno != 0) { + flb_error("[tls] syscall error: %s", strerror(saved_errno)); + } + else if (io_ret == 0) { + flb_error("[tls] error: unexpected EOF"); + } + else { + flb_error("[tls] syscall error without error code"); + } +} + static int tls_net_read(struct flb_tls_session *session, void *buf, size_t len) { int ret; + int ssl_ret; + int saved_errno; unsigned long err_code; char err_buf[256]; struct tls_context *ctx; @@ -1579,40 +1603,32 @@ 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_syscall_error(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; + session->connection->net_error = saved_errno; 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,7 +1645,7 @@ static int tls_net_read(struct flb_tls_session *session, ret = -1; } - else if (ret < 0) { + else if (ssl_ret < 0) { err_code = ERR_get_error(); if (err_code != 0) { @@ -1637,7 +1653,7 @@ static int tls_net_read(struct flb_tls_session *session, flb_error("[tls] error: %s", err_buf); } else { - flb_error("[tls] error: %s", strerror(errno)); + flb_error("[tls] error: %s", strerror(saved_errno)); } } else { @@ -1654,6 +1670,7 @@ static int tls_net_write(struct flb_tls_session *session, { int ret; int ssl_ret; + int saved_errno; unsigned long err_code; char err_buf[256]; size_t total = 0; @@ -1673,9 +1690,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 +1706,14 @@ 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_syscall_error(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; + session->connection->net_error = saved_errno; ret = -1; } From 4afb9694e5b416790cc03f86d55e0d511a6789d6 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 30 Jul 2026 17:06:15 -0600 Subject: [PATCH 2/4] tests: integration: cover TLS syscall error reporting Signed-off-by: Eduardo Silva --- .../in_forward/tests/test_in_forward_001.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) 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..6c80053195d 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,22 @@ 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 _create_partial_forward_client(service, payload, use_tls): if use_tls: sock, tls, outgoing = _create_tls_memory_bio_client( @@ -1412,6 +1429,35 @@ 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" + + @pytest.mark.parametrize( "config_file,use_tls", [ From 1f06587da96072a2f73226d3eb77837b3c842d15 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 30 Jul 2026 17:28:41 -0600 Subject: [PATCH 3/4] tls: openssl: handle fatal I/O errors Signed-off-by: Eduardo Silva --- src/tls/openssl.c | 54 ++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/tls/openssl.c b/src/tls/openssl.c index e3bdf5d5fb1..16d95245c2d 100644 --- a/src/tls/openssl.c +++ b/src/tls/openssl.c @@ -1556,7 +1556,7 @@ static const char *tls_session_alpn_get(void *session_) return backend_session->alpn; } -static void tls_log_syscall_error(int io_ret, int saved_errno) +static void tls_log_io_error(int ssl_error, int saved_errno) { unsigned long err_code; char err_buf[256]; @@ -1567,14 +1567,14 @@ static void tls_log_syscall_error(int io_ret, int saved_errno) ERR_error_string_n(err_code, err_buf, sizeof(err_buf) - 1); flb_error("[tls] error: %s", err_buf); } - else if (saved_errno != 0) { + else if (ssl_error == SSL_ERROR_SYSCALL && saved_errno != 0) { flb_error("[tls] syscall error: %s", strerror(saved_errno)); } - else if (io_ret == 0) { + else if (ssl_error == SSL_ERROR_SYSCALL) { flb_error("[tls] error: unexpected EOF"); } else { - flb_error("[tls] syscall error without error code"); + flb_error("[tls] unknown error (ssl_error=%d)", ssl_error); } } @@ -1584,8 +1584,6 @@ static int tls_net_read(struct flb_tls_session *session, int ret; int ssl_ret; int saved_errno; - unsigned long err_code; - char err_buf[256]; struct tls_context *ctx; struct tls_session *backend_session; @@ -1617,14 +1615,19 @@ static int tls_net_read(struct flb_tls_session *session, ret = FLB_TLS_WANT_WRITE; } else if (ssl_ret == SSL_ERROR_SYSCALL) { - tls_log_syscall_error(ret, saved_errno); + 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 = saved_errno; + if (saved_errno != 0) { + session->connection->net_error = saved_errno; + } + else { + session->connection->net_error = ECONNRESET; + } ret = -1; } @@ -1645,18 +1648,10 @@ static int tls_net_read(struct flb_tls_session *session, ret = -1; } - else if (ssl_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(saved_errno)); - } - } else { + tls_log_io_error(ssl_ret, saved_errno); + session->connection->net_error = ECONNRESET; + ret = -1; } } @@ -1671,8 +1666,6 @@ static int tls_net_write(struct flb_tls_session *session, int ret; int ssl_ret; int saved_errno; - unsigned long err_code; - char err_buf[256]; size_t total = 0; struct tls_context *ctx; struct tls_session *backend_session; @@ -1706,14 +1699,19 @@ static int tls_net_write(struct flb_tls_session *session, ret = FLB_TLS_WANT_READ; } else if (ssl_ret == SSL_ERROR_SYSCALL) { - tls_log_syscall_error(ret, saved_errno); + 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 = saved_errno; + if (saved_errno != 0) { + session->connection->net_error = saved_errno; + } + else { + session->connection->net_error = ECONNRESET; + } ret = -1; } @@ -1735,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; } From fb64de6c76446b275170880fb51264ffd1fa64ed Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 30 Jul 2026 17:28:41 -0600 Subject: [PATCH 4/4] tests: integration: cover TLS protocol errors Signed-off-by: Eduardo Silva --- .../in_forward/tests/test_in_forward_001.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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 6c80053195d..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 @@ -726,6 +726,17 @@ def _reset_tls_connection(port, cafile): 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( @@ -1458,6 +1469,36 @@ def test_in_forward_tls_syscall_error_preserves_errno(): 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", [