From c066b8b0a51bb6ea763c39088eab7ff3b9dd9a8d Mon Sep 17 00:00:00 2001 From: lecaros Date: Fri, 14 Aug 2026 14:38:14 -0400 Subject: [PATCH 1/2] in_winevtlog: fix crash when all channels are missing and ignored When ignore_missing_channels is enabled and every channel of an input instance fails to subscribe (e.g. a single configured channel that does not exist on the host), winevtlog_open_all() freed the channel list and returned NULL. The plugin tolerated the NULL on init and kept running with ctx->active_channel == NULL, so the first collection cycle dereferenced a NULL pointer inside mk_list_foreach() and crashed the whole process with no log output. The same NULL list was also passed to winevtlog_close_all() on exit/reload. Return the empty list instead when missing channels are tolerated, so collect/exit iterate it safely, and add NULL guards to in_winevtlog_collect() and winevtlog_close_all() as defense in depth. Co-Authored-By: Claude Fable 5 Signed-off-by: lecaros --- plugins/in_winevtlog/in_winevtlog.c | 4 ++++ plugins/in_winevtlog/winevtlog.c | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/plugins/in_winevtlog/in_winevtlog.c b/plugins/in_winevtlog/in_winevtlog.c index 233f259558c..9d8372786b4 100644 --- a/plugins/in_winevtlog/in_winevtlog.c +++ b/plugins/in_winevtlog/in_winevtlog.c @@ -444,6 +444,10 @@ static int in_winevtlog_collect(struct flb_input_instance *ins, struct mk_list *head; struct winevtlog_channel *ch; + if (!ctx->active_channel) { + return 0; + } + mk_list_foreach(head, ctx->active_channel) { ch = mk_list_entry(head, struct winevtlog_channel, _head); in_winevtlog_read_channel(ins, ctx, ch); diff --git a/plugins/in_winevtlog/winevtlog.c b/plugins/in_winevtlog/winevtlog.c index b902867c840..6dbf087f0fc 100644 --- a/plugins/in_winevtlog/winevtlog.c +++ b/plugins/in_winevtlog/winevtlog.c @@ -2222,6 +2222,13 @@ struct mk_list *winevtlog_open_all(const char *channels, struct winevtlog_config if (mk_list_size(list) == 0) { flb_free(tmp); + if (ctx->ignore_missing_channels) { + /* + * All channels are missing but tolerated: return the empty + * list so the caller can iterate it safely on collect/exit. + */ + return list; + } winevtlog_close_all(list); return NULL; } @@ -2236,6 +2243,10 @@ void winevtlog_close_all(struct mk_list *list) struct mk_list *head; struct mk_list *tmp; + if (!list) { + return; + } + mk_list_foreach_safe(head, tmp, list) { ch = mk_list_entry(head, struct winevtlog_channel, _head); mk_list_del(&ch->_head); From 713430241c476fb59450396482044b48c88c65d9 Mon Sep 17 00:00:00 2001 From: lecaros Date: Fri, 14 Aug 2026 15:27:41 -0400 Subject: [PATCH 2/2] tests: runtime: in_winevtlog: missing channels with ignore_missing_channels Add runtime tests for the winevtlog input (Windows-only build): - all channels of the instance missing with ignore_missing_channels The engine must start, survive collection cycles and stop cleanly (regression test for the NULL active_channel crash) - multiple missing channels in one instance - mixed existing ('Application') and missing channels - missing channel without ignore_missing_channels keeps failing initialization Co-Authored-By: Claude Fable 5 Signed-off-by: lecaros --- tests/runtime/CMakeLists.txt | 2 + tests/runtime/in_winevtlog.c | 184 +++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 tests/runtime/in_winevtlog.c diff --git a/tests/runtime/CMakeLists.txt b/tests/runtime/CMakeLists.txt index 6db28445ace..aac288885a8 100644 --- a/tests/runtime/CMakeLists.txt +++ b/tests/runtime/CMakeLists.txt @@ -37,6 +37,8 @@ FLB_RT_TEST(FLB_CHUNK_TRACE "core_chunk_trace.c") # Input Plugins FLB_RT_TEST(FLB_IN_EVENT_TEST "in_event_test.c") +# FLB_IN_WINEVTLOG is only enabled on Windows builds +FLB_RT_TEST(FLB_IN_WINEVTLOG "in_winevtlog.c") if(FLB_OUT_LIB) # These plugins works only on Linux diff --git a/tests/runtime/in_winevtlog.c b/tests/runtime/in_winevtlog.c new file mode 100644 index 00000000000..5aa38795df9 --- /dev/null +++ b/tests/runtime/in_winevtlog.c @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "flb_tests_runtime.h" + +/* + * Channels that must not exist on the host. The names are deliberately + * implausible; the tests rely on subscription to them failing with + * ERROR_EVT_CHANNEL_NOT_FOUND. + */ +#define MISSING_CHANNEL_A "FlbTestMissingChannelA" +#define MISSING_CHANNEL_B "FlbTestMissingChannelB" + +/* + * All channels of the instance are missing and tolerated: the engine must + * start, survive collection cycles and stop cleanly. Before the fix, + * ctx->active_channel was NULL and the first collection cycle crashed the + * process (NULL dereference in mk_list_foreach). + */ +void flb_test_winevtlog_all_channels_missing_ignored(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + TEST_CHECK(ctx != NULL); + + in_ffd = flb_input(ctx, (char *) "winevtlog", NULL); + TEST_CHECK(in_ffd >= 0); + ret = flb_input_set(ctx, in_ffd, + "channels", MISSING_CHANNEL_A, + "ignore_missing_channels", "true", + "interval_sec", "1", + NULL); + TEST_CHECK(ret == 0); + + out_ffd = flb_output(ctx, (char *) "null", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "*", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + /* Let at least two collection cycles run (interval_sec=1) */ + flb_time_msleep(2500); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* Same as above but with several missing channels in one instance */ +void flb_test_winevtlog_multiple_missing_channels_ignored(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + TEST_CHECK(ctx != NULL); + + in_ffd = flb_input(ctx, (char *) "winevtlog", NULL); + TEST_CHECK(in_ffd >= 0); + ret = flb_input_set(ctx, in_ffd, + "channels", MISSING_CHANNEL_A "," MISSING_CHANNEL_B, + "ignore_missing_channels", "true", + "interval_sec", "1", + NULL); + TEST_CHECK(ret == 0); + + out_ffd = flb_output(ctx, (char *) "null", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "*", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_time_msleep(2500); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* + * A mix of one existing channel ('Application' always exists on Windows) + * and one missing channel: the missing one is skipped, the instance keeps + * working. This was already the behavior before the fix and must not + * regress. + */ +void flb_test_winevtlog_mixed_channels_ignored(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + TEST_CHECK(ctx != NULL); + + in_ffd = flb_input(ctx, (char *) "winevtlog", NULL); + TEST_CHECK(in_ffd >= 0); + ret = flb_input_set(ctx, in_ffd, + "channels", "Application," MISSING_CHANNEL_A, + "ignore_missing_channels", "true", + "interval_sec", "1", + NULL); + TEST_CHECK(ret == 0); + + out_ffd = flb_output(ctx, (char *) "null", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "*", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret == 0); + + flb_time_msleep(2500); + + flb_stop(ctx); + flb_destroy(ctx); +} + +/* + * Without ignore_missing_channels, a missing channel must keep failing + * initialization (documented behavior: "Subscribe at least one"). + */ +void flb_test_winevtlog_missing_channel_fails_without_ignore(void) +{ + int ret; + flb_ctx_t *ctx; + int in_ffd; + int out_ffd; + + ctx = flb_create(); + TEST_CHECK(ctx != NULL); + + in_ffd = flb_input(ctx, (char *) "winevtlog", NULL); + TEST_CHECK(in_ffd >= 0); + ret = flb_input_set(ctx, in_ffd, + "channels", MISSING_CHANNEL_A, + NULL); + TEST_CHECK(ret == 0); + + out_ffd = flb_output(ctx, (char *) "null", NULL); + TEST_CHECK(out_ffd >= 0); + flb_output_set(ctx, out_ffd, "match", "*", NULL); + + ret = flb_start(ctx); + TEST_CHECK(ret != 0); + + flb_destroy(ctx); +} + +/* Test list */ +TEST_LIST = { + {"all_channels_missing_ignored", + flb_test_winevtlog_all_channels_missing_ignored}, + {"multiple_missing_channels_ignored", + flb_test_winevtlog_multiple_missing_channels_ignored}, + {"mixed_channels_ignored", + flb_test_winevtlog_mixed_channels_ignored}, + {"missing_channel_fails_without_ignore", + flb_test_winevtlog_missing_channel_fails_without_ignore}, + {NULL, NULL} +};