From 30dc4bf67d0d8102ea5396635e695e9ba964820e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 12:37:07 -0400 Subject: [PATCH] v1.1.5 - Fix CRITICAL segfault: Use wrapper nodes for no-kind-filter subscriptions The kind index optimization in v1.1.4 introduced a critical bug that caused segmentation faults in production. The bug was in add_subscription_to_kind_index() which directly assigned sub->next for no-kind-filter subscriptions, corrupting the main active_subscriptions linked list. Root Cause: - subscription_t has only ONE 'next' pointer used by active_subscriptions list - Code tried to reuse 'next' for no_kind_filter_subs list - This overwrote the active_subscriptions linkage, breaking list traversal - Result: segfaults when iterating subscriptions Fix: - Added no_kind_filter_node_t wrapper structure (like kind_subscription_node_t) - Changed no_kind_filter_subs from subscription_t* to no_kind_filter_node_t* - Updated add/remove functions to use wrapper nodes - Updated broadcast function to iterate through wrapper nodes This follows the same pattern already used for kind_index entries and prevents any corruption of the subscription structure's next pointer. --- relay.pid | 2 +- src/main.h | 4 ++-- src/subscriptions.c | 31 ++++++++++++++++++++----------- src/subscriptions.h | 8 +++++++- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/relay.pid b/relay.pid index af1b06f..fa96046 100644 --- a/relay.pid +++ b/relay.pid @@ -1 +1 @@ -1912734 +1931355 diff --git a/src/main.h b/src/main.h index 77302df..a3b5ea5 100644 --- a/src/main.h +++ b/src/main.h @@ -13,8 +13,8 @@ // Using CRELAY_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define CRELAY_VERSION_MAJOR 1 #define CRELAY_VERSION_MINOR 1 -#define CRELAY_VERSION_PATCH 4 -#define CRELAY_VERSION "v1.1.4" +#define CRELAY_VERSION_PATCH 5 +#define CRELAY_VERSION "v1.1.5" // Relay metadata (authoritative source for NIP-11 information) #define RELAY_NAME "C-Relay" diff --git a/src/subscriptions.c b/src/subscriptions.c index d806788..156ba13 100644 --- a/src/subscriptions.c +++ b/src/subscriptions.c @@ -100,10 +100,17 @@ void add_subscription_to_kind_index(subscription_t* sub) { filter = filter->next; } - // If subscription has no kind filter, add to no-kind-filter list + // If subscription has no kind filter, add to no-kind-filter list using wrapper node if (!has_kind_filter) { - sub->next = g_subscription_manager.no_kind_filter_subs; - g_subscription_manager.no_kind_filter_subs = sub; + no_kind_filter_node_t* node = malloc(sizeof(no_kind_filter_node_t)); + if (!node) { + DEBUG_ERROR("add_subscription_to_kind_index: failed to allocate no-kind-filter node"); + return; + } + + node->subscription = sub; + node->next = g_subscription_manager.no_kind_filter_subs; + g_subscription_manager.no_kind_filter_subs = node; DEBUG_TRACE("KIND_INDEX: Added subscription '%s' to no-kind-filter list", sub->id); } } @@ -130,11 +137,13 @@ void remove_subscription_from_kind_index(subscription_t* sub) { } } - // Remove from no-kind-filter list - subscription_t** current = &g_subscription_manager.no_kind_filter_subs; + // Remove from no-kind-filter list if present + no_kind_filter_node_t** current = &g_subscription_manager.no_kind_filter_subs; while (*current) { - if (*current == sub) { + if ((*current)->subscription == sub) { + no_kind_filter_node_t* to_free = *current; *current = (*current)->next; + free(to_free); DEBUG_TRACE("KIND_INDEX: Removed subscription '%s' from no-kind-filter list", sub->id); break; } @@ -797,12 +806,12 @@ int broadcast_event_to_subscriptions(cJSON* event) { } // Add subscriptions with no kind filter (must check against all events) - subscription_t* no_kind_sub = g_subscription_manager.no_kind_filter_subs; - while (no_kind_sub && candidate_count < MAX_TOTAL_SUBSCRIPTIONS) { - if (no_kind_sub->active) { - candidates_to_check[candidate_count++] = no_kind_sub; + no_kind_filter_node_t* no_kind_node = g_subscription_manager.no_kind_filter_subs; + while (no_kind_node && candidate_count < MAX_TOTAL_SUBSCRIPTIONS) { + if (no_kind_node->subscription && no_kind_node->subscription->active) { + candidates_to_check[candidate_count++] = no_kind_node->subscription; } - no_kind_sub = no_kind_sub->next; + no_kind_node = no_kind_node->next; } DEBUG_TRACE("BROADCAST: Checking %d candidate subscriptions (kind index optimization)", candidate_count); diff --git a/src/subscriptions.h b/src/subscriptions.h index c3d0260..fd6ed44 100644 --- a/src/subscriptions.h +++ b/src/subscriptions.h @@ -69,6 +69,12 @@ typedef struct kind_subscription_node { struct kind_subscription_node* next; // Next subscription for this kind } kind_subscription_node_t; +// No-kind-filter list entry - wrapper to avoid corrupting subscription->next pointer +typedef struct no_kind_filter_node { + subscription_t* subscription; // Pointer to subscription + struct no_kind_filter_node* next; // Next subscription in no-kind list +} no_kind_filter_node_t; + // Per-IP connection tracking typedef struct ip_connection_info { char ip_address[CLIENT_IP_MAX_LENGTH]; // IP address @@ -87,7 +93,7 @@ struct subscription_manager { // Kind-based index for fast subscription lookup (10x performance improvement) kind_subscription_node_t* kind_index[65536]; // Array of subscription lists, one per kind - subscription_t* no_kind_filter_subs; // Subscriptions with no kind filter (must check all events) + no_kind_filter_node_t* no_kind_filter_subs; // Subscriptions with no kind filter (wrapper nodes) // Configuration int max_subscriptions_per_client; // Default: 20