Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 57 additions & 53 deletions src/tls/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Comment on lines +1490 to +1492

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Capture the Winsock error for TLS syscalls

On Windows, socket failures are reported through WSAGetLastError() rather than errno, as reflected by the repository's Windows implementations of flb_socket_error() and flb_wsa_get_last_error(). Therefore a reset or timeout from SSL_read() leaves saved_errno at zero, causing SSL_ERROR_SYSCALL to be mislabeled as an unexpected EOF and recorded as generic ECONNRESET; the identical pattern affects SSL_write(). Capture the platform socket error immediately after both OpenSSL calls so Windows retains the actual failure details.

Useful? React with 👍 / 👎.


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;
}
}
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
157 changes: 157 additions & 0 deletions tests/integration/scenarios/in_forward/tests/test_in_forward_001.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shutil
import socket
import ssl
import struct
import subprocess
import sys
import tempfile
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
Loading