diff --git a/plugins/filter_kubernetes/kubernetes_aws.c b/plugins/filter_kubernetes/kubernetes_aws.c index 87b701f76e0..76af579951d 100644 --- a/plugins/filter_kubernetes/kubernetes_aws.c +++ b/plugins/filter_kubernetes/kubernetes_aws.c @@ -246,6 +246,11 @@ int fetch_pod_service_map(struct flb_kube *ctx, char *api_server_url, if (!c) { flb_error("[kubernetes] could not create HTTP client"); +#ifdef FLB_HAVE_TLS + if (u_conn->tls_session != NULL) { + flb_tls_session_destroy(u_conn->tls_session); + } +#endif flb_upstream_conn_recycle(u_conn, FLB_FALSE); flb_upstream_conn_release(u_conn); flb_upstream_destroy(ctx->aws_pod_association_upstream); @@ -267,6 +272,11 @@ int fetch_pod_service_map(struct flb_kube *ctx, char *api_server_url, c->resp.payload); } flb_http_client_destroy(c); +#ifdef FLB_HAVE_TLS + if (u_conn->tls_session != NULL) { + flb_tls_session_destroy(u_conn->tls_session); + } +#endif flb_upstream_conn_recycle(u_conn, FLB_FALSE); flb_upstream_conn_release(u_conn); return -1; @@ -279,8 +289,13 @@ int fetch_pod_service_map(struct flb_kube *ctx, char *api_server_url, parse_pod_service_map(ctx, c->resp.payload, c->resp.payload_size, mutex); } - /* Cleanup - mark connection as non-recyclable to prevent memory leak */ + /* Destroy TLS session explicitly; the background thread's event loop never drains the destroy_queue. */ flb_http_client_destroy(c); +#ifdef FLB_HAVE_TLS + if (u_conn->tls_session != NULL) { + flb_tls_session_destroy(u_conn->tls_session); + } +#endif flb_upstream_conn_recycle(u_conn, FLB_FALSE); flb_upstream_conn_release(u_conn); } diff --git a/tests/internal/upstream_tls.c b/tests/internal/upstream_tls.c index 4ec327deb46..b847b056b79 100644 --- a/tests/internal/upstream_tls.c +++ b/tests/internal/upstream_tls.c @@ -18,6 +18,7 @@ struct test_backend_ctx { int invalidate_calls; + int destroy_calls; }; static void test_session_invalidate(void *session) @@ -29,6 +30,46 @@ static void test_session_invalidate(void *session) } } +static int test_session_destroy(void *session) +{ + struct test_backend_ctx *ctx = session; + + if (ctx != NULL) { + ctx->destroy_calls++; + } + + return 0; +} + +static int setup_conn(struct flb_connection *conn, + struct flb_upstream *upstream, + struct flb_config *config, + flb_pipefd_t *socket_pair) +{ + if (flb_pipe_create(socket_pair) != 0) { + return -1; + } + + config->is_shutting_down = FLB_FALSE; + upstream->base.config = config; + upstream->base.net.keepalive = FLB_FALSE; + upstream->tcp_host = "example"; + upstream->tcp_port = 443; + flb_upstream_queue_init(&upstream->queue); + + conn->fd = socket_pair[0]; + conn->event.fd = conn->fd; + conn->event.status = 0; + conn->stream = (struct flb_stream *) upstream; + conn->net = &upstream->base.net; + conn->net_error = 0; + + mk_list_init(&conn->_head); + mk_list_add(&conn->_head, &upstream->queue.busy_queue); + + return 0; +} + void test_prepare_destroy_conn_marks_tls_session_stale(void) { struct test_backend_ctx backend_session = {0}; @@ -38,53 +79,28 @@ void test_prepare_destroy_conn_marks_tls_session_stale(void) struct flb_connection conn = {0}; struct flb_upstream upstream = {0}; struct flb_config config = {0}; - struct flb_upstream_queue *queue; flb_pipefd_t socket_pair[2]; - int ret; #ifdef FLB_SYSTEM_WINDOWS WSADATA wsa_data; - WSAStartup(0x0201, &wsa_data); #endif - ret = flb_pipe_create(socket_pair); - TEST_CHECK(ret == 0); + TEST_CHECK(setup_conn(&conn, &upstream, &config, socket_pair) == 0); backend_api.session_invalidate = test_session_invalidate; tls_context.api = &backend_api; - tls_session.ptr = &backend_session; tls_session.tls = &tls_context; tls_session.connection = &conn; - - config.is_shutting_down = FLB_FALSE; - upstream.base.config = &config; - upstream.base.net.keepalive = FLB_FALSE; - upstream.tcp_host = "example"; - upstream.tcp_port = 443; - - flb_upstream_queue_init(&upstream.queue); - - conn.fd = socket_pair[0]; - conn.event.fd = conn.fd; - conn.event.status = 0; - conn.stream = (struct flb_stream *) &upstream; - conn.net = &upstream.base.net; conn.tls_session = &tls_session; - conn.net_error = 0; - - mk_list_init(&conn._head); - queue = &upstream.queue; - mk_list_add(&conn._head, &queue->busy_queue); - ret = flb_upstream_conn_release(&conn); - TEST_CHECK(ret == 0); + TEST_CHECK(flb_upstream_conn_release(&conn) == 0); TEST_CHECK(backend_session.invalidate_calls == 1); TEST_CHECK(conn.fd == -1); TEST_CHECK(conn.event.fd == -1); - TEST_CHECK(mk_list_size(&queue->destroy_queue) == 1); + TEST_CHECK(mk_list_size(&upstream.queue.destroy_queue) == 1); TEST_CHECK(conn.shutdown_flag == FLB_TRUE); flb_pipe_close(socket_pair[1]); @@ -94,11 +110,63 @@ void test_prepare_destroy_conn_marks_tls_session_stale(void) #endif } +void test_tls_session_destroy_no_double_free(void) +{ + struct test_backend_ctx backend_session = {0}; + struct flb_tls_backend backend_api = {0}; + struct flb_tls tls_context = {0}; + struct flb_tls_session *tls_session; + struct flb_connection *conn; + struct flb_upstream upstream = {0}; + struct flb_config config = {0}; + flb_pipefd_t socket_pair[2]; + +#ifdef FLB_SYSTEM_WINDOWS + WSADATA wsa_data; + WSAStartup(0x0201, &wsa_data); +#endif + + /* heap-allocate conn to match production; pending_destroy calls flb_free on it */ + conn = flb_calloc(1, sizeof(struct flb_connection)); + TEST_CHECK(conn != NULL); + conn->dynamically_allocated = FLB_TRUE; + TEST_CHECK(setup_conn(conn, &upstream, &config, socket_pair) == 0); + + backend_api.session_invalidate = test_session_invalidate; + backend_api.session_destroy = test_session_destroy; + tls_context.api = &backend_api; + + /* heap-allocated to match production; flb_tls_session_destroy calls flb_free */ + tls_session = flb_calloc(1, sizeof(struct flb_tls_session)); + TEST_CHECK(tls_session != NULL); + tls_session->ptr = &backend_session; + tls_session->tls = &tls_context; + tls_session->connection = conn; + conn->tls_session = tls_session; + + /* explicit destroy before release — the fix */ + TEST_CHECK(flb_tls_session_destroy(tls_session) == 0); + TEST_CHECK(conn->tls_session == NULL); + + TEST_CHECK(flb_upstream_conn_release(conn) == 0); + + /* pending_destroy must not double-free the already-destroyed session */ + TEST_CHECK(flb_upstream_conn_pending_destroy(&upstream) == 0); + TEST_CHECK(backend_session.destroy_calls == 1); + + flb_pipe_close(socket_pair[1]); + +#ifdef FLB_SYSTEM_WINDOWS + WSACleanup(); +#endif +} + #endif TEST_LIST = { #ifdef FLB_HAVE_TLS {"prepare_destroy_conn_marks_tls_session_stale", test_prepare_destroy_conn_marks_tls_session_stale}, + {"tls_session_destroy_no_double_free", test_tls_session_destroy_no_double_free}, #endif {0} };