From 332be14bd6605724e93b570f0bfe8bd7521d2cb4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:22:30 -0700 Subject: [PATCH 001/255] F-15467 - https://fenrir.wolfssl.com/finding/15467 - Clear new head entry prev pointer in list insert before helper --- src/wolfsentry_ll.h | 4 +++- tests/unittests.c | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_ll.h b/src/wolfsentry_ll.h index 59ec7514..29d77526 100644 --- a/src/wolfsentry_ll.h +++ b/src/wolfsentry_ll.h @@ -71,8 +71,10 @@ static inline void wolfsentry_list_ent_insert_before(struct wolfsentry_list_head if (point_ent->prev) { new_ent->prev = point_ent->prev; new_ent->prev->next = new_ent; - } else + } else { + new_ent->prev = NULL; list->head = new_ent; + } point_ent->prev = new_ent; ++list->len; } diff --git a/tests/unittests.c b/tests/unittests.c index 7a756860..7cb07728 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1391,6 +1391,60 @@ TEST_SKIP(test_rw_locks) #ifdef TEST_STATIC_ROUTES +static wolfsentry_errcode_t test_ll_insert_before_head_links(void) { + struct wolfsentry_list_header list; + struct wolfsentry_list_ent_header a, b, c, d; + struct wolfsentry_list_ent_header *cursor; + wolfsentry_hitcount_t n; + + WOLFSENTRY_LIST_HEADER_RESET(list); + wolfsentry_list_ent_append(&list, &a); + wolfsentry_list_ent_append(&list, &b); + wolfsentry_list_ent_append(&list, &c); + + /* unlink a middle entry and reinsert it before the head, as purge-list + * refresh does; the unlink leaves its old links, so the head insertion + * must clear prev. + */ + wolfsentry_list_ent_delete(&list, &b); + wolfsentry_list_ent_insert_before(&list, list.head, &b); + + if (list.head != &b) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.head->prev != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.tail->next != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + wolfsentry_list_ent_insert_before(&list, list.head, &d); + if ((list.head != &d) || (list.head->prev != NULL)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.head; cursor != NULL; cursor = cursor->next) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->next == NULL) && (cursor != list.tail)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.tail; cursor != NULL; cursor = cursor->prev) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->prev == NULL) && (cursor != list.head)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t replace_rule_transactionally( WOLFSENTRY_CONTEXT_ARGS_IN, const char *event_label, @@ -1505,6 +1559,8 @@ static int test_static_routes(void) { WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(test_ll_insert_before_head_links()); + WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_init_ex( wolfsentry_build_settings, From 9bb2771c73e05c9005eba7528b21a6b4cac73b75 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:27:08 -0700 Subject: [PATCH 002/255] F-15466 - https://fenrir.wolfssl.com/finding/15466 - Set new tail entry next pointer in list insert after helper --- src/wolfsentry_ll.h | 4 +++- tests/unittests.c | 54 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_ll.h b/src/wolfsentry_ll.h index 29d77526..bc7bb82b 100644 --- a/src/wolfsentry_ll.h +++ b/src/wolfsentry_ll.h @@ -91,8 +91,10 @@ static inline void wolfsentry_list_ent_insert_after(struct wolfsentry_list_heade if (point_ent->next) { new_ent->next = point_ent->next; new_ent->next->prev = new_ent; - } else + } else { + new_ent->next = NULL; list->tail = new_ent; + } point_ent->next = new_ent; ++list->len; } diff --git a/tests/unittests.c b/tests/unittests.c index 7cb07728..83263153 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1509,6 +1509,59 @@ static wolfsentry_errcode_t replace_rule_transactionally( WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } +static wolfsentry_errcode_t test_ll_insert_after_tail_links(void) { + struct wolfsentry_list_header list; + struct wolfsentry_list_ent_header a, b, c; + struct wolfsentry_list_ent_header sentinel; + struct wolfsentry_list_ent_header *cursor; + wolfsentry_hitcount_t n; + + WOLFSENTRY_LIST_HEADER_RESET(list); + wolfsentry_list_ent_append(&list, &a); + wolfsentry_list_ent_append(&list, &b); + + /* a freshly allocated entry carries an unrelated non-NULL next, which a + * correct tail insertion must overwrite with NULL. + */ + sentinel.prev = NULL; + sentinel.next = NULL; + c.prev = &sentinel; + c.next = &sentinel; + + wolfsentry_list_ent_insert_after(&list, list.tail, &c); + + if (list.tail != &c) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.tail->next != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if (list.head->prev != NULL) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.head; cursor != NULL; cursor = cursor->next) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->next == NULL) && (cursor != list.tail)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + n = 0; + for (cursor = list.tail; cursor != NULL; cursor = cursor->prev) { + ++n; + if (n > list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + if ((cursor->prev == NULL) && (cursor != list.head)) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + } + if (n != list.len) + WOLFSENTRY_ERROR_RETURN(UNIT_TEST_FAILURE); + + WOLFSENTRY_RETURN_OK; +} + static int test_static_routes(void) { struct wolfsentry_context *wolfsentry; @@ -1560,6 +1613,7 @@ static int test_static_routes(void) { WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); WOLFSENTRY_EXIT_ON_FAILURE(test_ll_insert_before_head_links()); + WOLFSENTRY_EXIT_ON_FAILURE(test_ll_insert_after_tail_links()); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_init_ex( From cfdb725508c5df7a8a952e8e0388773d4de33632 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:56:10 -0700 Subject: [PATCH 003/255] F-15467 - https://fenrir.wolfssl.com/finding/15467 - Allow the list tests to call the inline list primitives under -Winline --- tests/unittests.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 83263153..750995bf 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -33,6 +33,11 @@ #define PRIVATE_DATA_ALIGNMENT 16 #endif +#ifdef __GNUC__ +/* the list tests call the inline list primitives directly from run-once code */ +#pragma GCC diagnostic ignored "-Winline" +#endif + #include "src/wolfsentry_internal.h" #if (defined(_POSIX_C_SOURCE) || defined(__MACH__)) && !defined(FREERTOS) From b99e488affe95983daa232f01bc060185fcf009c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:09:51 -0700 Subject: [PATCH 004/255] F-4820 - https://fenrir.wolfssl.com/finding/4820 - Reject notification datagrams with missing string fields --- examples/notification-demo/udp_to_dbus/udp_to_dbus.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c index ec7f9c9c..6f08165d 100644 --- a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c +++ b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c @@ -144,6 +144,13 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con local_port = json_value_int32(local_port_v); n_decisions = json_value_array_size(decision_array_v); + if ((action == NULL) || (af == NULL) || (remote_addr == NULL) || + (local_addr == NULL)) { + fprintf(stderr, "ERROR: ignoring malformed notification datagram " + "with missing string field(s)\n"); + break; + } + SNPRINTF_MSGPTR(msgbuf_ptr, msgbuf_len_left, "%s/%d from %s:%d to %s:%d\n", af, proto, From b182f10905ddcd6a02371844c09d932e8fa3d763 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:10:23 -0700 Subject: [PATCH 005/255] F-4701 - https://fenrir.wolfssl.com/finding/4701 - Escape notification body markup and keep the summary plain text --- .../notification-demo/udp_to_dbus/udp_to_dbus.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c index 6f08165d..f48c5d31 100644 --- a/examples/notification-demo/udp_to_dbus/udp_to_dbus.c +++ b/examples/notification-demo/udp_to_dbus/udp_to_dbus.c @@ -88,6 +88,7 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con GError *error = NULL; gboolean retval; int ret; + gchar *body_esc = NULL; struct wolfsentry_allocator *allocator = wolfsentry_get_allocator(wolfsentry); @@ -182,12 +183,14 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con SNPRINTF_MSGPTR(msgbuf_ptr, msgbuf_len_left, "]"); - /* xxx note that dbus notification daemons expect fragments of basic - * HTML as input, so msgbuf needs rewriting to escape HTML entities. - */ + /* escape the completed body once, so no entity can be split by the + * fixed-size buffer; the summary is plain text, not markup. */ + body_esc = g_markup_escape_text((const gchar *)msgbuf, -1); + if (body_esc == NULL) + break; summary = action; - body = msgbuf; + body = (const unsigned char *)body_esc; type = "type"; app_name = "wolfsentry_udp_to_dbus"; icon_str = "icon_str"; @@ -213,6 +216,8 @@ static int notify(WOLFSENTRY_CONTEXT_ARGS_IN, JSON_CONFIG *centijson_config, con WARN_ON_JSON_FAILURE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &p_root)); + g_free(body_esc); + if (notify == NULL) return -1; From 3f55b0de7b1dcbb338da6fe6366a31adb992aa1b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:43:56 -0700 Subject: [PATCH 006/255] F-8776 - https://fenrir.wolfssl.com/finding/8776 - Initialize address family entry header before the fallible allocation path --- src/addr_families.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/addr_families.c b/src/addr_families.c index ae58f5cb..3fed5c2a 100644 --- a/src/addr_families.c +++ b/src/addr_families.c @@ -116,6 +116,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( goto out; } + memset(bynumber, 0, sizeof *bynumber); + #ifdef WOLFSENTRY_PROTOCOL_NAMES byname_size = sizeof *byname + (size_t)family_byname_len + 1; if ((byname = (struct wolfsentry_addr_family_byname *)WOLFSENTRY_MALLOC(byname_size)) == NULL) { @@ -124,7 +126,6 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( } #endif - memset(bynumber, 0, sizeof *bynumber); bynumber->number = family_bynumber; bynumber->parser = parser; bynumber->formatter = formatter; From c629588c6e9b9616ed5b5c503eb684b9edc13834 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:44:35 -0700 Subject: [PATCH 007/255] F-8752 - https://fenrir.wolfssl.com/finding/8752 - Return without unlocking on error paths reached before the context lock is held --- src/addr_families.c | 26 ++++++++++++++------------ tests/unittests.c | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/addr_families.c b/src/addr_families.c index 3fed5c2a..9d212a69 100644 --- a/src/addr_families.c +++ b/src/addr_families.c @@ -105,24 +105,20 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( family_byname_len = (int)strlen(family_byname); if (family_byname_len == 0) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); - if (family_byname_len > WOLFSENTRY_MAX_LABEL_BYTES) { - ret = WOLFSENTRY_ERROR_ENCODE(STRING_ARG_TOO_LONG); - goto out; - } + if (family_byname_len > WOLFSENTRY_MAX_LABEL_BYTES) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); #endif - if ((bynumber = (struct wolfsentry_addr_family_bynumber *)WOLFSENTRY_MALLOC(sizeof *bynumber)) == NULL) { - ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); - goto out; - } + if ((bynumber = (struct wolfsentry_addr_family_bynumber *)WOLFSENTRY_MALLOC(sizeof *bynumber)) == NULL) + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); memset(bynumber, 0, sizeof *bynumber); #ifdef WOLFSENTRY_PROTOCOL_NAMES byname_size = sizeof *byname + (size_t)family_byname_len + 1; if ((byname = (struct wolfsentry_addr_family_byname *)WOLFSENTRY_MALLOC(byname_size)) == NULL) { - ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); - goto out; + WOLFSENTRY_FREE(bynumber); + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); } #endif @@ -143,8 +139,13 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( #endif ret = WOLFSENTRY_MUTEX_EX(wolfsentry); - if (ret < 0) - goto out; + if (ret < 0) { + WOLFSENTRY_FREE(bynumber); +#ifdef WOLFSENTRY_PROTOCOL_NAMES + WOLFSENTRY_FREE(byname); +#endif + WOLFSENTRY_ERROR_RERETURN(ret); + } if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &bynumber->header)) < 0) goto out; @@ -166,6 +167,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_addr_family_insert( out: + /* reached only after the context lock is held, so unlocking is correct. */ if (ret < 0) { if (bynumber != NULL) { if (bynumber->header.id != WOLFSENTRY_ENT_ID_NONE) diff --git a/tests/unittests.c b/tests/unittests.c index 750995bf..bf9346ec 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4071,6 +4071,9 @@ static int test_user_addr_families(void) { struct wolfsentry_context *wolfsentry; wolfsentry_action_res_t action_results; +#ifdef WOLFSENTRY_PROTOCOL_NAMES + char overlong_family_name[WOLFSENTRY_MAX_LABEL_BYTES + 2]; +#endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -4339,6 +4342,25 @@ static int test_user_addr_families(void) { } #endif /* WOLFSENTRY_PROTOCOL_NAMES */ +#ifdef WOLFSENTRY_PROTOCOL_NAMES + /* A family name longer than the maximum label length must propagate + * STRING_ARG_TOO_LONG rather than a lock-management error from an error + * path that returns before the context lock is acquired. + */ + memset(overlong_family_name, 'a', sizeof overlong_family_name - 1); + overlong_family_name[sizeof overlong_family_name - 1] = 0; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_USER_OFFSET + 1, + overlong_family_name, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + 24 /* max_addr_bits */)); +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 4d2462d7f018051d0273aab584798bd35b063480 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:44:48 -0700 Subject: [PATCH 008/255] F-8783 - https://fenrir.wolfssl.com/finding/8783 - Copy the matched address family number before releasing the shared lock --- src/addr_families.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/addr_families.c b/src/addr_families.c index 9d212a69..9386d844 100644 --- a/src/addr_families.c +++ b/src/addr_families.c @@ -500,6 +500,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_addr_family_pton( */ if (WOLFSENTRY_ATOMIC_LOAD(wolfsentry->addr_families_byname->header.n_ents) > 0) { struct wolfsentry_addr_family_bynumber *addr_family; + wolfsentry_addr_family_t found_number = WOLFSENTRY_AF_UNSPEC; WOLFSENTRY_SHARED_OR_RETURN(); ret = wolfsentry_addr_family_get_byname_1( WOLFSENTRY_CONTEXT_ARGS_OUT, @@ -507,9 +508,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_addr_family_pton( family_name, family_name_len, &addr_family); + if (ret >= 0) + found_number = addr_family->number; WOLFSENTRY_UNLOCK_FOR_RETURN(); if (ret >= 0) { - *family_number = addr_family->number; + *family_number = found_number; WOLFSENTRY_RETURN_OK; } else if (! WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) WOLFSENTRY_ERROR_RERETURN(ret); From 9e27eb4aff40acb8dd2138ccab9dedab6acd20f4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 13:45:13 -0700 Subject: [PATCH 009/255] F-2856 - https://fenrir.wolfssl.com/finding/2856 - Accept UNSPEC in pton to match the ntop formatter --- src/addr_families.c | 2 ++ tests/unittests.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/addr_families.c b/src/addr_families.c index 9386d844..3c48e702 100644 --- a/src/addr_families.c +++ b/src/addr_families.c @@ -518,6 +518,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_addr_family_pton( WOLFSENTRY_ERROR_RERETURN(ret); } + if (strcaseeq(family_name, "UNSPEC", (size_t)family_name_len)) + { *family_number = WOLFSENTRY_AF_UNSPEC; WOLFSENTRY_RETURN_OK; } if (strcaseeq(family_name, "UNIX", (size_t)family_name_len)) { *family_number = WOLFSENTRY_AF_UNIX; WOLFSENTRY_RETURN_OK; } if (strcaseeq(family_name, "LOCAL", (size_t)family_name_len)) diff --git a/tests/unittests.c b/tests/unittests.c index bf9346ec..dbf63d83 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4073,6 +4073,8 @@ static int test_user_addr_families(void) { wolfsentry_action_res_t action_results; #ifdef WOLFSENTRY_PROTOCOL_NAMES char overlong_family_name[WOLFSENTRY_MAX_LABEL_BYTES + 2]; + wolfsentry_addr_family_t unspec_family_number; + const char *unspec_family_name; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -4361,6 +4363,27 @@ static int test_user_addr_families(void) { 24 /* max_addr_bits */)); #endif /* WOLFSENTRY_PROTOCOL_NAMES */ +#ifdef WOLFSENTRY_PROTOCOL_NAMES + /* WOLFSENTRY_AF_UNSPEC formats to "UNSPEC" through ntop and must parse back + * to the same number through pton so the encode/decode roundtrip holds. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_ntop( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_UNSPEC, + NULL, + &unspec_family_name)); + WOLFSENTRY_EXIT_ON_FALSE((unspec_family_name != NULL) && + (! strcmp(unspec_family_name, "UNSPEC"))); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_pton( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "UNSPEC", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &unspec_family_number)); + WOLFSENTRY_EXIT_ON_FALSE(unspec_family_number == WOLFSENTRY_AF_UNSPEC); +#endif /* WOLFSENTRY_PROTOCOL_NAMES */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From e03865d230d19c70cdae6a5182224d5105e8b3f0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:12:54 -0700 Subject: [PATCH 010/255] F-8762 - https://fenrir.wolfssl.com/finding/8762 - Preserve DOM nesting for duplicate container values under USEFIRST --- src/json/centijson_dom.c | 68 ++++++++++++++++++++----------- tests/unittests.c | 86 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 23 deletions(-) diff --git a/src/json/centijson_dom.c b/src/json/centijson_dom.c index cf880d95..a04d02ca 100644 --- a/src/json/centijson_dom.c +++ b/src/json/centijson_dom.c @@ -141,6 +141,34 @@ init_number( } } +static int +json_dom_path_push(JSON_DOM_PARSER* dom_parser, JSON_VALUE* value) +{ + if(dom_parser->path_size >= dom_parser->path_alloc) { + JSON_VALUE** new_path; + size_t new_path_alloc; + + if(dom_parser->path_alloc == 0) { + new_path_alloc = 32; + } + else if(dom_parser->path_alloc > SIZE_MAX / 2 / sizeof(JSON_VALUE*)) { + return JSON_ERR_OUTOFMEMORY; + } + else { + new_path_alloc = dom_parser->path_alloc * 2; + } + new_path = (JSON_VALUE**) realloc((void *)dom_parser->path, new_path_alloc * sizeof(JSON_VALUE*)); + if(new_path == NULL) + return JSON_ERR_OUTOFMEMORY; + + dom_parser->path = new_path; + dom_parser->path_alloc = new_path_alloc; + } + + dom_parser->path[dom_parser->path_size++] = value; + return 0; +} + int json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, void* user_data) { @@ -154,6 +182,16 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo return 0; } + if(dom_parser->path_size > 0 && + dom_parser->path[dom_parser->path_size - 1] == NULL) + { + /* A NULL path entry marks a duplicate container dropped by USEFIRST; + * swallow its contents so the enclosing nesting state stays correct. */ + if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) + return json_dom_path_push(dom_parser, NULL); + return 0; + } + if(type == JSON_KEY) { /* Object key: We just store it until we get the json_value to use it with. */ if(json_value_init_string_( @@ -201,7 +239,10 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo if(!json_value_is_new(new_json_value)) { /* We have already set json_value for this key. */ switch(dom_parser->flags & JSON_DOM_DUPKEY_MASK) { - case JSON_DOM_DUPKEY_USEFIRST: return 0; + case JSON_DOM_DUPKEY_USEFIRST: + if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) + return json_dom_path_push(dom_parser, NULL); + return 0; case JSON_DOM_DUPKEY_USELAST: ret = json_value_fini( #ifdef WOLFSENTRY @@ -264,28 +305,9 @@ json_dom_process(JSON_TYPE type, const unsigned char* data, size_t data_size, vo if(type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG) { /* Push the array or object to the path, so we know where to * append their json_values. */ - if(dom_parser->path_size >= dom_parser->path_alloc) { - JSON_VALUE** new_path; - size_t new_path_alloc; - - if(dom_parser->path_alloc == 0) { - new_path_alloc = 32; - } - else if(dom_parser->path_alloc > SIZE_MAX / 2 / sizeof(JSON_VALUE*)) { - return JSON_ERR_OUTOFMEMORY; - } - else { - new_path_alloc = dom_parser->path_alloc * 2; - } - new_path = (JSON_VALUE**) realloc((void *)dom_parser->path, new_path_alloc * sizeof(JSON_VALUE*)); - if(new_path == NULL) - return JSON_ERR_OUTOFMEMORY; - - dom_parser->path = new_path; - dom_parser->path_alloc = new_path_alloc; - } - - dom_parser->path[dom_parser->path_size++] = new_json_value; + ret = json_dom_path_push(dom_parser, new_json_value); + if(ret < 0) + return ret; } return 0; diff --git a/tests/unittests.c b/tests/unittests.c index dbf63d83..aee0af60 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5263,6 +5263,92 @@ static int test_json(const char *fname, const char *extra_fname) { } #endif /* WOLFSENTRY_HAVE_JSON_DOM */ +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* A duplicate key whose value is a container, parsed with USEFIRST, must + * leave the DOM nesting intact: the first value wins and the discarded + * container must not leak its children into the enclosing object. */ + { + static const char dupkey_doc[] = + "{ \"k\" : 1, \"k\" : { \"x\" : 2 }, \"after\" : 3 }"; + static const JSON_CONFIG dupkey_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + JSON_VALUE dupkey_root; + JSON_VALUE *dupkey_v; + JSON_INPUT_POS dupkey_pos; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)dupkey_doc, strlen(dupkey_doc), &dupkey_config, + JSON_DOM_DUPKEY_USEFIRST, &dupkey_root, &dupkey_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&dupkey_root) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&dupkey_root) == 2); + WOLFSENTRY_EXIT_ON_TRUE((dupkey_v = json_value_path(&dupkey_root, "k")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupkey_v) == 1); + WOLFSENTRY_EXIT_ON_TRUE((dupkey_v = json_value_path(&dupkey_root, "after")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupkey_v) == 3); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupkey_root, "x") == NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &dupkey_root))); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* USEFIRST must also drop duplicate containers whose value is a nested + * object or an array: the first value wins, the duplicate (and anything + * nested in it) is dropped whole, and the surrounding keys stay intact. */ + { + static const char dupnest_doc[] = + "{ \"k\" : { \"a\" : 1 }," + " \"k\" : { \"b\" : { \"c\" : 2 } }," + " \"arr\" : [ 10, 20 ]," + " \"arr\" : [ 30, 40, 50 ]," + " \"after\" : 7 }"; + static const JSON_CONFIG dupnest_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + JSON_VALUE dupnest_root; + JSON_VALUE *dupnest_v; + JSON_INPUT_POS dupnest_pos; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)dupnest_doc, strlen(dupnest_doc), &dupnest_config, + JSON_DOM_DUPKEY_USEFIRST, &dupnest_root, &dupnest_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&dupnest_root) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&dupnest_root) == 3); + + /* first (object) value of "k" wins; the nested duplicate is dropped whole */ + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "k")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(dupnest_v) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(dupnest_v) == 1); + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "k/a")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupnest_v) == 1); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "k/b") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "b") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&dupnest_root, "c") == NULL); + + /* first (array) value of "arr" wins; the longer duplicate is dropped */ + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "arr")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(dupnest_v) == JSON_VALUE_ARRAY); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(dupnest_v) == 2); + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_get(dupnest_v, 0) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(dupnest_v, 0)) == 10); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(dupnest_v, 1)) == 20); + + WOLFSENTRY_EXIT_ON_TRUE((dupnest_v = json_value_path(&dupnest_root, "after")) == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(dupnest_v) == 7); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_centijson_errcode_translate( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), &dupnest_root))); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + /* test aux event, flag bit filters & frobbing, and good/bad route insertion by * wolfsentry_builtin_action_track_peer() From d908dcc81fd9bcbc15b4ab96984fce2791591206 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:21 -0700 Subject: [PATCH 011/255] F-4098 - https://fenrir.wolfssl.com/finding/4098 - Pass the allocator context to the DOM malloc callback --- src/json/centijson_dom.c | 2 +- tests/unittests.c | 68 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/json/centijson_dom.c b/src/json/centijson_dom.c index a04d02ca..9bc4a69e 100644 --- a/src/json/centijson_dom.c +++ b/src/json/centijson_dom.c @@ -61,7 +61,7 @@ static void *json_malloc(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator), size_t size) { if (allocator) - return allocator->malloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), size); + return allocator->malloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator->context), size); else return NULL; } diff --git a/tests/unittests.c b/tests/unittests.c index aee0af60..8e077a6e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4398,6 +4398,38 @@ static int test_user_addr_families(void) { #include "wolfsentry/wolfsentry_json.h" #ifdef WOLFSENTRY_HAVE_JSON_DOM #include + +static int test_dom_allocator_marker; +static int test_dom_allocator_context_ok; +static int test_dom_allocator_malloc_calls; + +static void *test_dom_allocator_malloc( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), size_t size) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + ++test_dom_allocator_malloc_calls; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + return malloc(size); +} + +static void test_dom_allocator_free( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), void *ptr) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + free(ptr); +} + +static void *test_dom_allocator_realloc( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(void *context), void *ptr, size_t size) +{ + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + if (context != (void *)&test_dom_allocator_marker) + test_dom_allocator_context_ok = 0; + return realloc(ptr, size); +} #endif static int test_json(const char *fname, const char *extra_fname) { @@ -5349,6 +5381,42 @@ static int test_json(const char *fname, const char *extra_fname) { } #endif /* WOLFSENTRY_HAVE_JSON_DOM */ +#ifdef WOLFSENTRY_HAVE_JSON_DOM + /* A custom allocator must receive its own context pointer, not the + * allocator object, as the first argument to its callbacks. */ + { + static const char ctx_doc[] = "{ \"a\" : [ 1, 2, 3 ] }"; + static const JSON_CONFIG ctx_config = { + 65536, 1000, 20, WOLFSENTRY_KV_MAX_VALUE_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT + }; + struct wolfsentry_allocator ctx_allocator; + JSON_VALUE ctx_root; + JSON_INPUT_POS ctx_pos; + + ctx_allocator.context = &test_dom_allocator_marker; + ctx_allocator.malloc = test_dom_allocator_malloc; + ctx_allocator.free = test_dom_allocator_free; + ctx_allocator.realloc = test_dom_allocator_realloc; + ctx_allocator.memalign = NULL; + ctx_allocator.free_aligned = NULL; + + test_dom_allocator_context_ok = 1; + test_dom_allocator_malloc_calls = 0; + + WOLFSENTRY_EXIT_ON_TRUE( + json_dom_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_allocator), + (const unsigned char *)ctx_doc, strlen(ctx_doc), &ctx_config, + 0, &ctx_root, &ctx_pos) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(test_dom_allocator_malloc_calls > 0); + WOLFSENTRY_EXIT_ON_FALSE(test_dom_allocator_context_ok == 1); + + WOLFSENTRY_EXIT_ON_TRUE( + json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_allocator), &ctx_root) < 0); + } +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + /* test aux event, flag bit filters & frobbing, and good/bad route insertion by * wolfsentry_builtin_action_track_peer() From 2ff5fe8ee3c36ebb40fe704c97af0138217a3122 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:06 -0700 Subject: [PATCH 012/255] F-4706 - https://fenrir.wolfssl.com/finding/4706 - Propagate wolfSentry init and config load errors from sentry_init --- examples/Linux-LWIP/sentry.c | 194 ++++++++++++++++++++--------------- 1 file changed, 112 insertions(+), 82 deletions(-) diff --git a/examples/Linux-LWIP/sentry.c b/examples/Linux-LWIP/sentry.c index acc24c7d..1094c5b8 100644 --- a/examples/Linux-LWIP/sentry.c +++ b/examples/Linux-LWIP/sentry.c @@ -45,113 +45,143 @@ static wolfsentry_errcode_t test_action( int sentry_init() { wolfsentry_errcode_t ret; + wolfsentry_errcode_t fini_ret; wolfsentry_ent_id_t id; - + char buf[512], err_buf[512]; + struct wolfsentry_json_process_state *jps = NULL; + FILE *f = NULL; + size_t n; struct wolfsentry_eventconfig config = { .route_private_data_size = 32, .route_private_data_alignment = 16 }; + ret = wolfsentry_init(wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(NULL /* hpi */, NULL /* thread */), &config, &wolfsentry); if (ret < 0) { fprintf(stderr, "wolfsentry_init() returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fprintf(stderr, "unable to initialize wolfSentry"); + return ret; } - /* Insert the possible actions into wolfSentry */ - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-insert", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-delete", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "notify-on-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect2", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - - char buf[512], err_buf[512]; - struct wolfsentry_json_process_state *jps; + /* Insert the possible actions into wolfSentry. On any failure fall through + * to the cleanup path at the bottom so the context is not leaked. */ + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-delete", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "notify-on-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-connect", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret >= 0) + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + "handle-connect2", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); /* Open the config file */ - FILE *f = fopen(wolfsentry_config_path, "r"); - - if (f == NULL) { - fprintf(stderr, "fopen(%s): %s\n",wolfsentry_config_path,strerror(errno)); - fprintf(stderr, "unable to open wolfSentry config file"); + if (ret >= 0) { + f = fopen(wolfsentry_config_path, "r"); + if (f == NULL) { + fprintf(stderr, "fopen(%s): %s\n",wolfsentry_config_path,strerror(errno)); + fprintf(stderr, "unable to open wolfSentry config file"); + ret = -1; + } } /* Initalize the wolfSentry JSON parser */ - if ((ret = wolfsentry_config_json_init( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, - &jps)) < 0) { - fprintf(stderr, "wolfsentry_config_json_init() returned " - WOLFSENTRY_ERROR_FMT "\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret)); - fprintf(stderr, "error while initializing wolfSentry config parser"); + if (ret >= 0) { + ret = wolfsentry_config_json_init( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + &jps); + if (ret < 0) { + fprintf(stderr, "wolfsentry_config_json_init() returned " + WOLFSENTRY_ERROR_FMT "\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret)); + fprintf(stderr, "error while initializing wolfSentry config parser"); + } } /* wolfSentry uses a streaming reader/parser for the config file */ - for (;;) { - /* Read some data from the config file */ - size_t n = fread(buf, 1, sizeof buf, f); - if ((n < sizeof buf) && ferror(f)) { - fprintf(stderr, "fread(%s): %s\n",wolfsentry_config_path, strerror(errno)); - fprintf(stderr, "error while reading wolfSentry config file"); + if (ret >= 0) { + for (;;) { + /* Read some data from the config file */ + n = fread(buf, 1, sizeof buf, f); + if ((n < sizeof buf) && ferror(f)) { + fprintf(stderr, "fread(%s): %s\n",wolfsentry_config_path, strerror(errno)); + fprintf(stderr, "error while reading wolfSentry config file"); + ret = -1; + break; + } + + /* Send the read data into the JSON parser */ + ret = wolfsentry_config_json_feed(jps, buf, n, err_buf, sizeof err_buf); + if (ret < 0) { + fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); + fprintf(stderr, "error while loading wolfSentry config file"); + break; + } + if ((n < sizeof buf) && feof(f)) + break; } + } - /* Send the read data into the JSON parser */ - ret = wolfsentry_config_json_feed(jps, buf, n, err_buf, sizeof err_buf); - if (ret < 0) { + /* Clean up the JSON parser. This also commits the parsed config, so its + * result matters on the success path. */ + if (jps != NULL) { + fini_ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf); + if (fini_ret < 0) { fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); fprintf(stderr, "error while loading wolfSentry config file"); + if (ret >= 0) + ret = fini_ret; } - if ((n < sizeof buf) && feof(f)) - break; } - fclose(f); - /* Clean up the JSON parser */ - if ((ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf)) < 0) { - fprintf(stderr, "%.*s\n", (int)sizeof err_buf, err_buf); - fprintf(stderr, "error while loading wolfSentry config file"); + if (f != NULL) + fclose(f); + + /* Any failure after wolfsentry_init() must release the context. wolfSentry + * success codes are nonnegative but not necessarily zero, so normalize. */ + if (ret < 0) { + if (wolfsentry != NULL) + (void)wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&wolfsentry, NULL)); + return ret; } return 0; From 81e296085c3053dd5c3b1a0056670e94bbd03961 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:06 -0700 Subject: [PATCH 013/255] F-5706 - https://fenrir.wolfssl.com/finding/5706 - Deny TCP connection when wolfSentry dispatch returns an error --- examples/Linux-LWIP/sentry.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/Linux-LWIP/sentry.c b/examples/Linux-LWIP/sentry.c index 1094c5b8..ba347185 100644 --- a/examples/Linux-LWIP/sentry.c +++ b/examples/Linux-LWIP/sentry.c @@ -248,6 +248,11 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port fprintf(stderr, "TCP Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the connection */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } + /* Check the result, if it contains "reject" then notify the caller */ if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { From d1082b93e22836e32dcf3e6705d23dfdc59b446c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:06 -0700 Subject: [PATCH 014/255] F-5705 - https://fenrir.wolfssl.com/finding/5705 - Deny ICMP packet when wolfSentry dispatch returns an error --- examples/Linux-LWIP/sentry.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Linux-LWIP/sentry.c b/examples/Linux-LWIP/sentry.c index ba347185..778034a8 100644 --- a/examples/Linux-LWIP/sentry.c +++ b/examples/Linux-LWIP/sentry.c @@ -296,6 +296,10 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) &action_results); fprintf(stderr, "PING Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the packet */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { return -1; From 20b3da88359d6b1c2636feeced8e6669c384d678 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:06 -0700 Subject: [PATCH 015/255] F-5704 - https://fenrir.wolfssl.com/finding/5704 - Deny Ethernet frame when wolfSentry dispatch returns an error --- examples/Linux-LWIP/sentry.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/Linux-LWIP/sentry.c b/examples/Linux-LWIP/sentry.c index 778034a8..82489783 100644 --- a/examples/Linux-LWIP/sentry.c +++ b/examples/Linux-LWIP/sentry.c @@ -341,6 +341,10 @@ int sentry_action_mac(struct eth_addr *addr) fprintf(stderr, "MAC Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + /* Fail closed: any dispatch error denies the frame */ + if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) < 0) { + return -1; + } if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { return -1; From d2662d27cf332d5d6538db8735b80c65f23436d8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:07 -0700 Subject: [PATCH 016/255] F-4822 - https://fenrir.wolfssl.com/finding/4822 - Halt echo init and release the PCB when lwIP TCP setup fails --- examples/Linux-LWIP/echo.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/Linux-LWIP/echo.c b/examples/Linux-LWIP/echo.c index 95e88816..288ed165 100644 --- a/examples/Linux-LWIP/echo.c +++ b/examples/Linux-LWIP/echo.c @@ -95,16 +95,32 @@ static err_t echo_msgaccept(void *arg, struct tcp_pcb *pcb, err_t err) int echo_init(void) { struct tcp_pcb *pcb; + struct tcp_pcb *listen_pcb; + int r; /* Create lwIP TCP instance */ pcb = tcp_new(); LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: pcb: %x\n", pcb)); + if (pcb == NULL) + return -1; /* Bind port 11111 */ - int r = tcp_bind(pcb, IP_ADDR_ANY, 11111); + r = tcp_bind(pcb, IP_ADDR_ANY, 11111); LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: tcp_bind: %d\n", r)); - /* Enable listening */ - pcb = tcp_listen(pcb); - LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: listen-pcb: %x\n", pcb)); + if (r != ERR_OK) { + if (tcp_close(pcb) != ERR_OK) + tcp_abort(pcb); + return -1; + } + /* Enable listening. tcp_listen() frees the original pcb only on success; + * on failure it returns NULL and leaves the original allocated. */ + listen_pcb = tcp_listen(pcb); + LWIP_DEBUGF(ECHO_DEBUG, ("echo_init: listen-pcb: %x\n", listen_pcb)); + if (listen_pcb == NULL) { + if (tcp_close(pcb) != ERR_OK) + tcp_abort(pcb); + return -1; + } + pcb = listen_pcb; /* Set accept connection callback */ tcp_accept(pcb, echo_msgaccept); From 6b8703d85d951cf34e60283c33cd5056ceaa8aa3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:13:07 -0700 Subject: [PATCH 017/255] F-4823 - https://fenrir.wolfssl.com/finding/4823 - Halt ping init when raw ICMP PCB allocation fails --- examples/Linux-LWIP/ping.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/Linux-LWIP/ping.c b/examples/Linux-LWIP/ping.c index ed497eba..ad03fea5 100644 --- a/examples/Linux-LWIP/ping.c +++ b/examples/Linux-LWIP/ping.c @@ -40,7 +40,8 @@ int ping_init(void) struct raw_pcb *ping_pcb; /* Create a new listener instance for ICMP messages */ ping_pcb = raw_new(IP_PROTO_ICMP); - LWIP_ASSERT("ping_pcb != NULL", ping_pcb != NULL); + if (ping_pcb == NULL) + return -1; /* Callback to ping_recv in ICMP message received */ raw_recv(ping_pcb, ping_recv, NULL); From f4c4472b821ac541514d9f6ea115a8143dd85235 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:40:47 -0700 Subject: [PATCH 018/255] F-4760 - https://fenrir.wolfssl.com/finding/4760 - Take the KV reference while the shared lock is still held --- src/kv.c | 20 +++++++++------ tests/unittests.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/kv.c b/src/kv.c index d921ad5d..fef29042 100644 --- a/src/kv.c +++ b/src/kv.c @@ -257,6 +257,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_1( WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_kv_table *kv_table, const struct wolfsentry_kv_pair_internal *kv_template, + int take_reference, struct wolfsentry_kv_pair_internal **kv) { wolfsentry_errcode_t ret; @@ -272,6 +273,13 @@ static wolfsentry_errcode_t wolfsentry_kv_get_1( else if ((WOLFSENTRY_KV_TYPE(&kv_template->kv) != WOLFSENTRY_KV_NONE) && (WOLFSENTRY_KV_TYPE(&kv_template->kv) != WOLFSENTRY_KV_TYPE(&ret_kv->kv))) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(WRONG_TYPE); + /* take the reference while the shared lock is still held, so a concurrent + * delete cannot free the entry between lookup and increment. + */ + if (take_reference) { + WOLFSENTRY_REFCOUNT_INCREMENT(ret_kv->header.refcount, ret); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + } *kv = ret_kv; WOLFSENTRY_UNLOCK_AND_RETURN_OK; } @@ -289,7 +297,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_2( if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); kv_template->kv.v_type = type; - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_ERROR_RERETURN(ret); } @@ -300,11 +308,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_reference_1( const struct wolfsentry_kv_pair_internal *kv_template, struct wolfsentry_kv_pair_internal **kv) { - wolfsentry_errcode_t ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_REFCOUNT_INCREMENT((*kv)->header.refcount, ret); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, kv)); } WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_reference( @@ -338,7 +342,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_type( struct wolfsentry_kv_pair_internal *kv = NULL; if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, &kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, &kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_RERETURN_IF_ERROR(ret); *type = WOLFSENTRY_KV_TYPE(&kv->kv); @@ -545,7 +549,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_delete( if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, &old); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, &old); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); if (old->kv.v_type & WOLFSENTRY_KV_FLAG_READONLY) diff --git a/tests/unittests.c b/tests/unittests.c index 8e077a6e..d6b163b7 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4063,6 +4063,65 @@ static int test_user_values(void) { WOLFSENTRY_RETURN_OK; } +static int test_kv_reference_lifetime(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + const char *value = NULL; + int value_len = -1; + static const char key[] = "lifetime_key"; + static const char str[] = "lifetime_value"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + str, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + + /* delete the key while the reference returned by get_string is still held; + * the held reference must keep the entry alive and readable. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(str)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, str) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record( + WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); + + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6071,6 +6130,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_user_values failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_reference_lifetime(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_reference_lifetime failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_ADDR_FAMILIES From c185a682887a5deba58871ef8138d750b57b237c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:42:33 -0700 Subject: [PATCH 019/255] F-8774 - https://fenrir.wolfssl.com/finding/8774 - Hold a KV reference across the unlocked dereference in the value getters --- src/kv.c | 27 ++++++++++++-------- tests/unittests.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/kv.c b/src/kv.c index fef29042..a2da19e3 100644 --- a/src/kv.c +++ b/src/kv.c @@ -297,7 +297,7 @@ static wolfsentry_errcode_t wolfsentry_kv_get_2( if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); kv_template->kv.v_type = type; - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_ERROR_RERETURN(ret); } @@ -342,11 +342,11 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_type( struct wolfsentry_kv_pair_internal *kv = NULL; if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); - ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 0 /* take_reference */, &kv); + ret = wolfsentry_kv_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, 1 /* take_reference */, &kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); WOLFSENTRY_RERETURN_IF_ERROR(ret); *type = WOLFSENTRY_KV_TYPE(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_type_to_string( @@ -717,7 +717,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_set_mutability( struct wolfsentry_kv_pair_internal *kv = NULL; if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); - WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_set_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv, mutable)); + ret = wolfsentry_kv_set_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv, mutable); + if (ret < 0) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); + else + ret = wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL); + WOLFSENTRY_ERROR_RERETURN(ret); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_mutability( @@ -731,7 +736,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_mutability( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *mutable = wolfsentry_kv_get_mutability(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_type( @@ -809,10 +814,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_bool( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_NONE, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); if ((WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_TRUE) && - (WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_FALSE)) + (WOLFSENTRY_KV_TYPE(&kv->kv) != WOLFSENTRY_KV_FALSE)) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); WOLFSENTRY_ERROR_RETURN(WRONG_TYPE); + } *value = WOLFSENTRY_KV_TYPE(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_uint( @@ -849,7 +856,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_uint( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_UINT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_UINT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_sint( @@ -887,7 +894,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_sint( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_SINT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_SINT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_double( @@ -925,7 +932,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_float( if ((ret = wolfsentry_kv_get_2(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->user_values, key, key_len, WOLFSENTRY_KV_FLOAT, &kv)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); *value = WOLFSENTRY_KV_V_FLOAT(&kv->kv); - WOLFSENTRY_RETURN_OK; + WOLFSENTRY_ERROR_RERETURN(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv, NULL)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( diff --git a/tests/unittests.c b/tests/unittests.c index d6b163b7..1a7574f3 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4122,6 +4122,65 @@ static int test_kv_reference_lifetime(void) { WOLFSENTRY_RETURN_OK; } +static int test_kv_get2_reference_balance(void) { + struct wolfsentry_context *wolfsentry; + static const char key[] = "balance_key"; + uint64_t uint_value = 0; + int mutable = -1; + int i; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 42, 0)); + + /* the non-reference getters take and release an internal reference each + * call; repeated calls must leave the refcount balanced so the entry can + * still be deleted afterwards. + */ + for (i = 0; i < 100; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &uint_value)); + WOLFSENTRY_EXIT_ON_FALSE(uint_value == 42); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &mutable)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_get_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &uint_value)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6135,6 +6194,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_reference_lifetime failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_get2_reference_balance(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_get2_reference_balance failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_ADDR_FAMILIES From 4a85f6db44305197fb6dba6138e302514f8b5a15 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:43:33 -0700 Subject: [PATCH 020/255] F-5559 - https://fenrir.wolfssl.com/finding/5559 - Return the key validation error without unlocking an unheld lock --- src/kv.c | 2 +- tests/unittests.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/kv.c b/src/kv.c index a2da19e3..038ba860 100644 --- a/src/kv.c +++ b/src/kv.c @@ -322,7 +322,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_get_reference( struct wolfsentry_kv_pair_internal *kv_template; wolfsentry_errcode_t ret; if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, 0 /* data_len */, &kv_template)) < 0) - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + WOLFSENTRY_ERROR_RERETURN(ret); kv_template->kv.v_type = type; ret = wolfsentry_kv_get_reference_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv_template, kv); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_template, NULL)); diff --git a/tests/unittests.c b/tests/unittests.c index 1a7574f3..198c2d21 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4181,6 +4181,39 @@ static int test_kv_get2_reference_balance(void) { WOLFSENTRY_RETURN_OK; } +static int test_kv_get_bad_key_errcode(void) { + struct wolfsentry_context *wolfsentry; + const char *value = NULL; + int value_len = -1; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* a rejected key must surface the validation error itself, not an error + * from an unlock attempted without a held lock. + */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "unused_key", 0 /* key_len */, + &value, &value_len, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6199,6 +6232,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_get2_reference_balance failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_get_bad_key_errcode(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_get_bad_key_errcode failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_ADDR_FAMILIES From f684e40646d67fe9665d9c0ce020b8abafe0fca7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:44:50 -0700 Subject: [PATCH 021/255] F-3442 - https://fenrir.wolfssl.com/finding/3442 - Render 64-bit KV integers without truncating on 32-bit-long targets --- src/kv.c | 8 ++++++ tests/unittests.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/kv.c b/src/kv.c index 038ba860..dbc7933b 100644 --- a/src/kv.c +++ b/src/kv.c @@ -429,10 +429,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( *out_len = snprintf(out, out_space, "false"); break; case WOLFSENTRY_KV_UINT: +#ifdef PRIu64 + *out_len = snprintf(out, out_space, "%" PRIu64, WOLFSENTRY_KV_V_UINT(kv)); +#else *out_len = snprintf(out, out_space, "%lu", (long unsigned int)WOLFSENTRY_KV_V_UINT(kv)); +#endif break; case WOLFSENTRY_KV_SINT: +#ifdef PRId64 + *out_len = snprintf(out, out_space, "%" PRId64, WOLFSENTRY_KV_V_SINT(kv)); +#else *out_len = snprintf(out, out_space, "%ld", (long int)WOLFSENTRY_KV_V_SINT(kv)); +#endif break; case WOLFSENTRY_KV_FLOAT: *out_len = snprintf(out, out_space, "%.10f", WOLFSENTRY_KV_V_FLOAT(kv)); diff --git a/tests/unittests.c b/tests/unittests.c index 198c2d21..22ad1bd9 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4214,6 +4214,73 @@ static int test_kv_get_bad_key_errcode(void) { WOLFSENTRY_RETURN_OK; } +static int render_only_user_value( + WOLFSENTRY_CONTEXT_ARGS_IN, + char *out, + int out_space) +{ + struct wolfsentry_cursor *cursor; + struct wolfsentry_kv_pair_internal *kv_ref; + const struct wolfsentry_kv_pair *kv_exports; + int render_space = out_space; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_current(WOLFSENTRY_CONTEXT_ARGS_OUT, cursor, &kv_ref)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_pair_export(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_ref, &kv_exports)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_kv_render_value(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_exports, out, &render_space)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_values_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_kv_render_large_int(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + /* 0x1234567890ABCDEF and -(2^33), built without long-long literals so the + * test stays legal under WOLFSENTRY_C89; compared against the exact decimal + * rendering to avoid strtoull()/strtoll(). + */ + uint64_t stored_uint = ((uint64_t)0x12345678u << 32) | (uint64_t)0x90abcdefu; + int64_t stored_sint = -((int64_t)1 << 33); + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_uint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "big_uint", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored_uint, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(buf, "1311768467294899695") == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "big_uint", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "big_sint", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored_sint, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(buf, "-8589934592") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6237,6 +6304,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_get_bad_key_errcode failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_render_large_int(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_large_int failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_ADDR_FAMILIES From 0976ce437422ece6e5714358407a0852c47cf0a3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:45:50 -0700 Subject: [PATCH 022/255] F-3195 - https://fenrir.wolfssl.com/finding/3195 - Render KV floats with round-trip precision --- src/kv.c | 2 +- tests/unittests.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/kv.c b/src/kv.c index dbc7933b..2aae09d5 100644 --- a/src/kv.c +++ b/src/kv.c @@ -443,7 +443,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( #endif break; case WOLFSENTRY_KV_FLOAT: - *out_len = snprintf(out, out_space, "%.10f", WOLFSENTRY_KV_V_FLOAT(kv)); + *out_len = snprintf(out, out_space, "%.17g", WOLFSENTRY_KV_V_FLOAT(kv)); break; case WOLFSENTRY_KV_STRING: { #ifndef WOLFSENTRY_HAVE_JSON_DOM diff --git a/tests/unittests.c b/tests/unittests.c index 22ad1bd9..2896780a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4281,6 +4281,39 @@ static int test_kv_render_large_int(void) { WOLFSENTRY_RETURN_OK; } +static int test_kv_render_float_roundtrip(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + double stored = 3.141592653589793; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "pi", WOLFSENTRY_LENGTH_NULL_TERMINATED, + stored, 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + + /* the rendered form must reparse to the exact same double. */ + WOLFSENTRY_EXIT_ON_FALSE(strtod(buf, NULL) == stored); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6309,6 +6342,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_render_large_int failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_render_float_roundtrip(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_float_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_ADDR_FAMILIES From 21324e39614f0ff107e85ec24f4569a51a47bf8b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:47:48 -0700 Subject: [PATCH 023/255] F-6511 - https://fenrir.wolfssl.com/finding/6511 - Render non-finite KV floats as re-parseable quoted tokens --- src/kv.c | 20 ++++++++++++-- tests/unittests.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/kv.c b/src/kv.c index 2aae09d5..edd57ab0 100644 --- a/src/kv.c +++ b/src/kv.c @@ -26,6 +26,8 @@ #include #endif +#include + #define WOLFSENTRY_SOURCE_ID WOLFSENTRY_SOURCE_ID_KV_C static inline int wolfsentry_kv_key_cmp_1(const char *left_label, const unsigned int left_label_len, const char *right_label, const unsigned int right_label_len) { @@ -442,9 +444,23 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( *out_len = snprintf(out, out_space, "%ld", (long int)WOLFSENTRY_KV_V_SINT(kv)); #endif break; - case WOLFSENTRY_KV_FLOAT: - *out_len = snprintf(out, out_space, "%.17g", WOLFSENTRY_KV_V_FLOAT(kv)); + case WOLFSENTRY_KV_FLOAT: { + double v_float = WOLFSENTRY_KV_V_FLOAT(kv); + /* non-finite values have no bare JSON token, so emit a quoted string + * that the loader's strtod path re-reads to the same value. finiteness + * is tested with DBL_MAX comparisons to stay legal under C89 (no C99 + * isnan()/isinf()). + */ + if (!(v_float <= DBL_MAX) && !(v_float >= -DBL_MAX)) /* NaN */ + *out_len = snprintf(out, out_space, "\"nan\""); + else if (!(v_float <= DBL_MAX)) /* +Inf */ + *out_len = snprintf(out, out_space, "\"inf\""); + else if (!(v_float >= -DBL_MAX)) /* -Inf */ + *out_len = snprintf(out, out_space, "\"-inf\""); + else + *out_len = snprintf(out, out_space, "%.17g", v_float); break; + } case WOLFSENTRY_KV_STRING: { #ifndef WOLFSENTRY_HAVE_JSON_DOM *out_len = snprintf(out, out_space, "\"%.*s\"", (int)WOLFSENTRY_KV_V_STRING_LEN(kv), WOLFSENTRY_KV_V_STRING(kv)); diff --git a/tests/unittests.c b/tests/unittests.c index 2896780a..684c580d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4314,6 +4314,65 @@ static int test_kv_render_float_roundtrip(void) { WOLFSENTRY_RETURN_OK; } +#ifndef WOLFSENTRY_C89 +static int test_kv_render_nonfinite_float(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + (double)NAN, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + /* a bare nan/inf is not valid JSON; the rendered value must be quoted and + * must reparse to the same non-finite value. + */ + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + WOLFSENTRY_EXIT_ON_FALSE(isnan(strtod(buf + 1, NULL))); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + (double)INFINITY, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + WOLFSENTRY_EXIT_ON_FALSE(isinf(strtod(buf + 1, NULL)) && (strtod(buf + 1, NULL) > 0.0)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_double( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ninf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, + -(double)INFINITY, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); + WOLFSENTRY_EXIT_ON_FALSE(isinf(strtod(buf + 1, NULL)) && (strtod(buf + 1, NULL) < 0.0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_C89 */ + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6347,6 +6406,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_render_float_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifndef WOLFSENTRY_C89 + ret = test_kv_render_nonfinite_float(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_nonfinite_float failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif /* !WOLFSENTRY_C89 */ #endif #ifdef TEST_USER_ADDR_FAMILIES From 781d3947b4c8c1ed0c90a571c89b0825056a2448 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:50:17 -0700 Subject: [PATCH 024/255] F-4281 - https://fenrir.wolfssl.com/finding/4281 - Escape KV strings when rendering without JSON DOM support --- src/kv.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++- tests/unittests.c | 48 +++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/kv.c b/src/kv.c index edd57ab0..7a30f54f 100644 --- a/src/kv.c +++ b/src/kv.c @@ -408,6 +408,86 @@ static int _json_value_dump_writer(const unsigned char* str, size_t size, void * } #endif +#ifndef WOLFSENTRY_HAVE_JSON_DOM + +#define WOLFSENTRY_KV_EMIT_CHAR(c) do { if (needed < out_space) out[needed] = (char)(c); ++needed; } while (0) + +/* escape a KV string into a JSON string literal, mirroring json_dump_string in + * the DOM path, and return the snprintf-style length (excluding the null). + */ +static int wolfsentry_kv_render_string_escaped( + const char *in, + size_t in_len, + char *out, + size_t out_space) +{ + static const char hexdigits[] = "0123456789abcdef"; + size_t in_i; + size_t needed = 0; + unsigned char c; + + WOLFSENTRY_KV_EMIT_CHAR('"'); + for (in_i = 0; in_i < in_len; ++in_i) { + c = (unsigned char)in[in_i]; + switch (c) { + case '"': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('"'); + break; + case '\\': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('\\'); + break; + case '\n': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('n'); + break; + case '\r': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('r'); + break; + case '\t': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('t'); + break; + case '\b': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('b'); + break; + case '\f': + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('f'); + break; + default: + if (c < 0x20) { + WOLFSENTRY_KV_EMIT_CHAR('\\'); + WOLFSENTRY_KV_EMIT_CHAR('u'); + WOLFSENTRY_KV_EMIT_CHAR('0'); + WOLFSENTRY_KV_EMIT_CHAR('0'); + WOLFSENTRY_KV_EMIT_CHAR(hexdigits[(c >> 4) & 0xf]); + WOLFSENTRY_KV_EMIT_CHAR(hexdigits[c & 0xf]); + } else { + WOLFSENTRY_KV_EMIT_CHAR(c); + } + break; + } + } + WOLFSENTRY_KV_EMIT_CHAR('"'); + + if (out_space > 0) { + if (needed < out_space) + out[needed] = 0; + else + out[out_space - 1] = 0; + } + + return (int)needed; +} + +#undef WOLFSENTRY_KV_EMIT_CHAR + +#endif /* !WOLFSENTRY_HAVE_JSON_DOM */ + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_kv_pair *kv, @@ -463,7 +543,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( } case WOLFSENTRY_KV_STRING: { #ifndef WOLFSENTRY_HAVE_JSON_DOM - *out_len = snprintf(out, out_space, "\"%.*s\"", (int)WOLFSENTRY_KV_V_STRING_LEN(kv), WOLFSENTRY_KV_V_STRING(kv)); + *out_len = wolfsentry_kv_render_string_escaped(WOLFSENTRY_KV_V_STRING(kv), WOLFSENTRY_KV_V_STRING_LEN(kv), out, out_space); break; #else struct dump_buf_state dbs; diff --git a/tests/unittests.c b/tests/unittests.c index 684c580d..2067326f 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4373,6 +4373,47 @@ static int test_kv_render_nonfinite_float(void) { } #endif /* !WOLFSENTRY_C89 */ +#ifndef WOLFSENTRY_HAVE_JSON_DOM +static int test_kv_render_string_escape(void) { + struct wolfsentry_context *wolfsentry; + char buf[64]; + static const char raw[] = "a\"b\\c\nd"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "esc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + raw, (int)(sizeof raw - 1), + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + + /* the rendered JSON string must not contain a raw control byte, and the + * quote, backslash and newline must all appear escaped. + */ + WOLFSENTRY_EXIT_ON_FALSE(strchr(buf + 1, '\n') == NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\\"") != NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\\\") != NULL); + WOLFSENTRY_EXIT_ON_FALSE(strstr(buf, "\\n") != NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_HAVE_JSON_DOM */ + #endif /* TEST_USER_VALUES */ #ifdef TEST_USER_ADDR_FAMILIES @@ -6413,6 +6454,13 @@ int main(int argc, char* argv[]) { err = 1; } #endif /* !WOLFSENTRY_C89 */ +#ifndef WOLFSENTRY_HAVE_JSON_DOM + ret = test_kv_render_string_escape(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_render_string_escape failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_USER_ADDR_FAMILIES From de38f7136889e5a52f76c05e984c36817c022fdd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:50:56 -0700 Subject: [PATCH 025/255] F-3869 - https://fenrir.wolfssl.com/finding/3869 - Cover rejection of overwriting a read-only user value --- tests/unittests.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 2067326f..8a0f416a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4373,6 +4373,65 @@ static int test_kv_render_nonfinite_float(void) { } #endif /* !WOLFSENTRY_C89 */ +static int test_kv_readonly_overwrite_rejected(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_kv_pair_internal *kv_ref = NULL; + const char *value = NULL; + int value_len = -1; + wolfsentry_errcode_t ret; + static const char key[] = "ro_key"; + static const char orig[] = "orig"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + orig, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_set_mutability( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* not mutable */)); + + /* overwriting a read-only value must be rejected. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + ret = wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + "different", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 1 /* overwrite_p */)); + + /* and the value must be unchanged. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + key, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &value, &value_len, &kv_ref)); + WOLFSENTRY_EXIT_ON_FALSE(value_len == (int)strlen(orig)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(value, orig) == 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_release_record(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_ref)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #ifndef WOLFSENTRY_HAVE_JSON_DOM static int test_kv_render_string_escape(void) { struct wolfsentry_context *wolfsentry; @@ -6454,6 +6513,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif /* !WOLFSENTRY_C89 */ + ret = test_kv_readonly_overwrite_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_readonly_overwrite_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #ifndef WOLFSENTRY_HAVE_JSON_DOM ret = test_kv_render_string_escape(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { From 29c5aaa7708137d3d4ceb3424245bf9e7d5f8108 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:52:20 -0700 Subject: [PATCH 026/255] F-12517 - https://fenrir.wolfssl.com/finding/12517 - Reject empty user value keys for both explicit and null-terminated lengths --- src/kv.c | 4 +++- tests/unittests.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/kv.c b/src/kv.c index 7a30f54f..0caa4984 100644 --- a/src/kv.c +++ b/src/kv.c @@ -62,7 +62,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_new( struct wolfsentry_kv_pair_internal **kv) { size_t kv_size; - if (key_len == 0) + if (key == NULL) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (data_len < 0) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); @@ -70,6 +70,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_new( WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); if (key_len < 0) key_len = (int)strlen(key); + if (key_len <= 0) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (key_len > WOLFSENTRY_MAX_LABEL_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); kv_size = sizeof **kv + (size_t)key_len + 1 + (size_t)data_len; diff --git a/tests/unittests.c b/tests/unittests.c index 8a0f416a..53bf7097 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4373,6 +4373,45 @@ static int test_kv_render_nonfinite_float(void) { } #endif /* !WOLFSENTRY_C89 */ +static int test_kv_empty_key_rejected(void) { + struct wolfsentry_context *wolfsentry; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* an empty key must be rejected consistently, whether supplied as an + * explicit zero length or as a null-terminated empty string. + */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "", 0 /* key_len */, + "v", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_user_value_store_string( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "", WOLFSENTRY_LENGTH_NULL_TERMINATED, + "v", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + static int test_kv_readonly_overwrite_rejected(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_kv_pair_internal *kv_ref = NULL; @@ -6518,6 +6557,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_kv_readonly_overwrite_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_kv_empty_key_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_empty_key_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #ifndef WOLFSENTRY_HAVE_JSON_DOM ret = test_kv_render_string_escape(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { From c5e8e9fc608451fb8d1126007fe377b7a7cc8b2f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:53:16 -0700 Subject: [PATCH 027/255] F-12515 - https://fenrir.wolfssl.com/finding/12515 - Keep the previous user value in place when a replacement fails --- src/kv.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/kv.c b/src/kv.c index 0caa4984..8755f652 100644 --- a/src/kv.c +++ b/src/kv.c @@ -246,11 +246,22 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set( WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } - if (((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) || - ((ret = wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)) < 0)) - { + if ((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + /* insert the replacement before dropping the old entry, so a failure + * on either step never leaves the key absent; restore the old entry if + * the insert fails. + */ + if ((ret = wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv)) < 0) { + /* re-insert with a fresh id, since the delete released the old one; + * drop it if even that fails, so the detached record is not leaked. + */ + if (wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, old) < 0) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)); + WOLFSENTRY_UNLOCK_AND_RETURN_OK; } ret = wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv); From 03bf394fbffb34d67d87d24ca8cdc6ba7dc6f794 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:53:53 -0700 Subject: [PATCH 028/255] F-10509 - https://fenrir.wolfssl.com/finding/10509 - Access the user value validator atomically to avoid racing the setter --- src/kv.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/kv.c b/src/kv.c index 8755f652..67f03342 100644 --- a/src/kv.c +++ b/src/kv.c @@ -174,9 +174,14 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_insert( struct wolfsentry_kv_pair_internal *kv) { wolfsentry_errcode_t ret; + wolfsentry_kv_validator_t validator = WOLFSENTRY_ATOMIC_LOAD(kv_table->validator); - if (kv_table->validator) { - ret = kv_table->validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); + /* read the validator pointer atomically (paired with the atomic store in + * the setter) but call it without the context lock held, to avoid a + * lock-order inversion with locks the application callback may take. + */ + if (validator) { + ret = validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); WOLFSENTRY_RERETURN_IF_ERROR(ret); } @@ -228,9 +233,14 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set( { struct wolfsentry_kv_pair_internal *old; wolfsentry_errcode_t ret; + wolfsentry_kv_validator_t validator = WOLFSENTRY_ATOMIC_LOAD(kv_table->validator); - if (kv_table->validator) { - ret = kv_table->validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); + /* read the validator pointer atomically (paired with the atomic store in + * the setter) but call it without the context lock held, to avoid a + * lock-order inversion with locks the application callback may take. + */ + if (validator) { + ret = validator(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->kv); WOLFSENTRY_RERETURN_IF_ERROR(ret); } @@ -712,7 +722,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set_validator( ret = wolfsentry_table_map(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_table->header, (wolfsentry_map_function_t)apply_validator, (void *)&context, action_results); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } - wolfsentry->user_values->validator = validator; + WOLFSENTRY_ATOMIC_STORE(wolfsentry->user_values->validator, validator); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } From c577ba54396785da7bb2a83b20a42df662b818e3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:54:12 -0700 Subject: [PATCH 029/255] F-13288 - https://fenrir.wolfssl.com/finding/13288 - Store the user value validator on its own table --- src/kv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kv.c b/src/kv.c index 67f03342..e0f86a2e 100644 --- a/src/kv.c +++ b/src/kv.c @@ -722,7 +722,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set_validator( ret = wolfsentry_table_map(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv_table->header, (wolfsentry_map_function_t)apply_validator, (void *)&context, action_results); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } - WOLFSENTRY_ATOMIC_STORE(wolfsentry->user_values->validator, validator); + WOLFSENTRY_ATOMIC_STORE(kv_table->validator, validator); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } From 55ce1b0493dd497a0149f7e2439fbe5eb5893238 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:55:07 -0700 Subject: [PATCH 030/255] F-12553 - https://fenrir.wolfssl.com/finding/12553 - Bound sentinel string lengths in size_t before narrowing to int --- src/kv.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/kv.c b/src/kv.c index e0f86a2e..4568df0f 100644 --- a/src/kv.c +++ b/src/kv.c @@ -27,6 +27,7 @@ #endif #include +#include #define WOLFSENTRY_SOURCE_ID WOLFSENTRY_SOURCE_ID_KV_C @@ -1072,8 +1073,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( { wolfsentry_errcode_t ret; struct wolfsentry_kv_pair_internal *kv; - if (value_len < 0) - value_len = (int)strlen(value); + if (value_len < 0) { + size_t value_strlen = strlen(value); + if (value_strlen > (size_t)WOLFSENTRY_KV_MAX_VALUE_BYTES) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + value_len = (int)value_strlen; + } if (value_len > WOLFSENTRY_KV_MAX_VALUE_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); if ((ret = wolfsentry_kv_new(WOLFSENTRY_CONTEXT_ARGS_OUT, key, key_len, value_len + 1, &kv)) < 0) @@ -1146,8 +1151,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_bytes_base64( wolfsentry_errcode_t ret; int decoded_len; struct wolfsentry_kv_pair_internal *kv; - if (value_len < 0) - value_len = (int)strlen(value); + if (value_len < 0) { + size_t value_strlen = strlen(value); + if (value_strlen > (size_t)INT_MAX) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + value_len = (int)value_strlen; + } decoded_len = WOLFSENTRY_BASE64_DECODED_BUFSPC(value, value_len); if (decoded_len > WOLFSENTRY_KV_MAX_VALUE_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); From 486b220098b89cd70eff5bef8ce9e9fd99cec0c7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:48:01 -0700 Subject: [PATCH 031/255] F-12553 - https://fenrir.wolfssl.com/finding/12553 - Bound base64 value lengths so the decoded size computation cannot overflow --- src/kv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/kv.c b/src/kv.c index 4568df0f..bfcd355b 100644 --- a/src/kv.c +++ b/src/kv.c @@ -1151,12 +1151,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_bytes_base64( wolfsentry_errcode_t ret; int decoded_len; struct wolfsentry_kv_pair_internal *kv; + /* the decoded size computation adds 3 to the length */ if (value_len < 0) { size_t value_strlen = strlen(value); - if (value_strlen > (size_t)INT_MAX) + if (value_strlen > (size_t)(INT_MAX - 3)) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); value_len = (int)value_strlen; - } + } else if (value_len > INT_MAX - 3) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); decoded_len = WOLFSENTRY_BASE64_DECODED_BUFSPC(value, value_len); if (decoded_len > WOLFSENTRY_KV_MAX_VALUE_BYTES) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); From 881f3b5b084e4e5ec1db68949f3c7a4e4df5895b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:23:04 -0700 Subject: [PATCH 032/255] F-3195 - https://fenrir.wolfssl.com/finding/3195 - Accept the longest round-trip double rendering in the JSON loader --- src/json/load_config.c | 7 ++++--- tests/unittests.c | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 732f17a9..16ad3d67 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -327,7 +327,8 @@ static wolfsentry_errcode_t convert_uint8(JSON_TYPE type, const unsigned char *d } static wolfsentry_errcode_t convert_double(JSON_TYPE type, const unsigned char *data, size_t data_size, double *out) { - char buf[24]; + /* fits the longest %.17g rendering, 24 characters */ + char buf[25]; char *endptr; double conv; @@ -1735,7 +1736,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_init_ex( #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS .max_total_len = WOLFSENTRY_MAX_JSON_TOTAL_LEN, .max_total_values = 0, - .max_number_len = 20, + .max_number_len = 24, .max_string_len = WOLFSENTRY_KV_MAX_VALUE_BYTES, .max_key_len = WOLFSENTRY_MAX_LABEL_BYTES, .max_nesting_level = WOLFSENTRY_MAX_JSON_NESTING, @@ -1743,7 +1744,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_init_ex( #else WOLFSENTRY_MAX_JSON_TOTAL_LEN, 0, - 20, + 24, WOLFSENTRY_KV_MAX_VALUE_BYTES, WOLFSENTRY_MAX_LABEL_BYTES, WOLFSENTRY_MAX_JSON_NESTING, diff --git a/tests/unittests.c b/tests/unittests.c index 53bf7097..5308f47d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4285,6 +4285,11 @@ static int test_kv_render_float_roundtrip(void) { struct wolfsentry_context *wolfsentry; char buf[64]; double stored = 3.141592653589793; +#ifndef WOLFSENTRY_NO_JSON + char json[128]; + int json_len; + double reloaded; +#endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -4307,6 +4312,21 @@ static int test_kv_render_float_roundtrip(void) { /* the rendered form must reparse to the exact same double. */ WOLFSENTRY_EXIT_ON_FALSE(strtod(buf, NULL) == stored); +#ifndef WOLFSENTRY_NO_JSON + /* the longest %.17g form, 24 characters, must reload through the JSON loader */ + stored = -2.2250738585072014e-308; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "pi", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_store_double(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED, stored, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); + WOLFSENTRY_EXIT_ON_FALSE(strlen(buf) == 24); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + json_len = snprintf(json, sizeof json, "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"tiny\" : %s } }", buf); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_config_json_oneshot(WOLFSENTRY_CONTEXT_ARGS_OUT, (const unsigned char *)json, (size_t)json_len, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_value_get_float(WOLFSENTRY_CONTEXT_ARGS_OUT, "tiny", WOLFSENTRY_LENGTH_NULL_TERMINATED, &reloaded)); + WOLFSENTRY_EXIT_ON_FALSE(reloaded == stored); +#endif + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 25f0f7e61d1d766af743277dc583bac8bae8d3f5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:24:12 -0700 Subject: [PATCH 033/255] F-11599 - https://fenrir.wolfssl.com/finding/11599 - parse documented attached and negative array index path forms --- src/json/centijson_value.c | 57 ++++++++++-- tests/unittests.c | 181 +++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 10 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 51cf4059..c3044e6b 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -320,30 +320,67 @@ json_value_path(JSON_VALUE* root, const char* path) { const unsigned char* token_beg = (const unsigned char *)path; const unsigned char* token_end; + const unsigned char* p; JSON_VALUE* v = root; + size_t path_index; + size_t arr_size; + int negative; + int have_digit; while(1) { token_end = token_beg; while(*token_end != '\0' && *token_end != '/') token_end++; - if(token_end - token_beg > 2 && token_beg[0] == '[' && token_end[-1] == ']') { - size_t path_index = 0; + p = token_beg; - token_beg++; - while('0' <= *token_beg && *token_beg <= '9') { - path_index = (path_index * 10U) + (*token_beg - (unsigned)'0'); - token_beg++; + /* Optional leading dictionary key, up to the first '[' or token end. */ + if(p < token_end && *p != '[') { + const unsigned char* key_end = p; + while(key_end < token_end && *key_end != '[') + key_end++; + v = json_value_dict_get_(v, p, (size_t)(key_end - p)); + if(v == NULL) + return NULL; + p = key_end; + } + + /* Zero or more attached array indexes "[n]", where n may be negative + * (relative to the array length). */ + while(p < token_end && *p == '[') { + p++; + negative = 0; + if(*p == '-') { + negative = 1; + p++; } - if(*token_beg != ']') + path_index = 0; + have_digit = 0; + while('0' <= *p && *p <= '9') { + size_t digit = (size_t)(*p - (unsigned char)'0'); + if(path_index > (SIZE_MAX - digit) / 10U) + return NULL; + path_index = (path_index * 10U) + digit; + have_digit = 1; + p++; + } + if((! have_digit) || (*p != ']')) return NULL; + p++; + if(negative) { + arr_size = json_value_array_size(v); + if((path_index == 0) || (path_index > arr_size)) + return NULL; + path_index = arr_size - path_index; + } v = json_value_array_get(v, path_index); - } else if(token_end - token_beg > 0) { - v = json_value_dict_get_(v, token_beg, (size_t)(token_end - token_beg)); + if(v == NULL) + return NULL; } - if(v == NULL) + /* The token must be fully consumed; reject trailing bytes. */ + if(p != token_end) return NULL; if(*token_end == '\0') diff --git a/tests/unittests.c b/tests/unittests.c index 5308f47d..658d1f2e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6249,6 +6249,180 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_HAVE_JSON_DOM + +/* Regression coverage for the centijson JSON_VALUE (DOM) public API. Uses a + * controllable test allocator so the error paths can be exercised through the + * public interface. */ + +static size_t test_cjv_live = 0; /* count of live allocations */ +static size_t test_cjv_fail_big = 0; /* if nonzero, fail allocations >= this size */ +static int test_cjv_zero_null = 0; /* if set, zero-size requests return NULL */ +static long test_cjv_budget = -1; /* if >= 0, allow this many mallocs then fail */ + +static void test_cjv_reset(void) { + test_cjv_live = 0; + test_cjv_fail_big = 0; + test_cjv_zero_null = 0; + test_cjv_budget = -1; +} + +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_malloc(void *context, struct wolfsentry_thread_context *thread, size_t size) +#else +static void *test_cjv_malloc(void *context, size_t size) +#endif +{ + void *p; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) + return NULL; + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + if (test_cjv_budget >= 0) { + if (test_cjv_budget == 0) + return NULL; + --test_cjv_budget; + } + p = malloc(size); + if (p != NULL) + ++test_cjv_live; + return p; +} + +#ifdef WOLFSENTRY_THREADSAFE +static void test_cjv_free(void *context, struct wolfsentry_thread_context *thread, void *ptr) +#else +static void test_cjv_free(void *context, void *ptr) +#endif +{ + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (ptr != NULL) + --test_cjv_live; + free(ptr); +} + +#ifdef WOLFSENTRY_THREADSAFE +static void *test_cjv_realloc(void *context, struct wolfsentry_thread_context *thread, void *ptr, size_t size) +#else +static void *test_cjv_realloc(void *context, void *ptr, size_t size) +#endif +{ + void *p; + int was_null; + (void)context; +#ifdef WOLFSENTRY_THREADSAFE + (void)thread; +#endif + if (test_cjv_zero_null && (size == 0)) { + if (ptr != NULL) + --test_cjv_live; + free(ptr); + return NULL; + } + if ((test_cjv_fail_big != 0) && (size >= test_cjv_fail_big)) + return NULL; + was_null = (ptr == NULL); + p = realloc(ptr, size); + if (p != NULL) { + if (was_null) + ++test_cjv_live; + } else if ((size == 0) && (! was_null)) { + --test_cjv_live; + } + return p; +} + +static int test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE root; + JSON_VALUE *list; + JSON_VALUE *elem; + JSON_VALUE *found; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + + list = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root, (const unsigned char *)"list", 4); + WOLFSENTRY_EXIT_ON_TRUE(list == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list) < 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 10) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 20) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), list); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, 30) < 0); + + /* Attached array index (key followed immediately by a bracket). */ + found = json_value_path(&root, "list[0]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + found = json_value_path(&root, "list[2]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + /* Slash-separated index is a documented equivalent of the attached form. */ + found = json_value_path(&root, "list/[1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 20); + + /* Negative indexes are relative to the array length. */ + found = json_value_path(&root, "list[-1]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 30); + + found = json_value_path(&root, "list[-3]"); + WOLFSENTRY_EXIT_ON_TRUE(found == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(found) == 10); + + /* Out-of-range indexes resolve to NULL. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[3]") == NULL); + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list[-4]") == NULL); + + /* Trailing bytes after a bracket index must be rejected. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[0]garbage]") == NULL); + + /* An index that overflows size_t must be rejected, not wrapped. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "list/[18446744073709551616]") == NULL); + + /* An empty path returns the root. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_path(&root, "") == &root); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static wolfsentry_errcode_t test_cjvalue_fixes(void) { + struct wolfsentry_allocator test_allocator; + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&test_allocator, 0, sizeof test_allocator); + test_allocator.malloc = test_cjv_malloc; + test_allocator.free = test_cjv_free; + test_allocator.realloc = test_cjv_realloc; + + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* WOLFSENTRY_HAVE_JSON_DOM */ + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -6616,6 +6790,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json failed for " TEST_NUMERIC_JSON_CONFIG_PATH ", " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_HAVE_JSON_DOM + ret = test_cjvalue_fixes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_cjvalue_fixes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_JSON_CORPUS From 4e92507bd1354ed4ea22f531fdee7d2389f317fb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:24:52 -0700 Subject: [PATCH 034/255] F-8777 - https://fenrir.wolfssl.com/finding/8777 - reject wrapping index and count in array range removal --- src/json/centijson_value.c | 14 +++++++++----- tests/unittests.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index c3044e6b..9b7e1e7f 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1027,12 +1027,16 @@ json_value_array_remove_range( { ARRAY* a = json_value_array_payload(v); size_t i; + size_t end; int ret; - if(a == NULL || index + count > a->size) + /* Validate without adding, so index + count cannot wrap. */ + if(a == NULL || index > a->size || count > a->size - index) return -1; - for(i = index; i < index + count; i++) { + end = index + count; + + for(i = index; i < end; i++) { ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), @@ -1042,9 +1046,9 @@ json_value_array_remove_range( return ret; } - if(index + count < a->size) { - memmove(a->json_value_buf + index, a->json_value_buf + index + count, - (a->size - (index + count)) * sizeof(JSON_VALUE)); + if(end < a->size) { + memmove(a->json_value_buf + index, a->json_value_buf + end, + (a->size - end) * sizeof(JSON_VALUE)); } a->size -= count; diff --git a/tests/unittests.c b/tests/unittests.c index 658d1f2e..95b5e64b 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6404,6 +6404,36 @@ static int test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_all return 0; } +static int test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + for (i = 0; i < 3; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); + } + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); + + /* index + count must not wrap past the array size. */ + ret = json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 3, SIZE_MAX); + WOLFSENTRY_EXIT_ON_FALSE(ret == -1); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 3); + + /* a legitimate removal still works. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 1, 1) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 2); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 1)) == 2); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6416,6 +6446,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 2f4dc49ee254376a727e55ee0d6cf3210f180c0d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:27:19 -0700 Subject: [PATCH 035/255] F-8763 - https://fenrir.wolfssl.com/finding/8763 - finalize cloned array on failure and track only initialized elements --- src/json/centijson_value.c | 49 ++++++++++++++++------------ tests/unittests.c | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 9b7e1e7f..fa5e5096 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1917,38 +1917,45 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo const ARRAY* src_a = json_value_array_payload((JSON_VALUE *)node); size_t n = json_value_array_size(node); const JSON_VALUE* src_values = json_value_array_get_all(node); - ARRAY* dest_a; - JSON_VALUE* dest_values; + ARRAY* dest_a = NULL; + JSON_VALUE* dest_values = NULL; size_t i; ret = json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); - if (ret < 0) - break; - dest_a = json_value_array_payload(clone); - if (! dest_a) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_a = json_value_array_payload(clone); + if (dest_a == NULL) + ret = JSON_ERR_INTERNAL; } - ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), - dest_a, src_a->alloc); - if (ret < 0) - break; + if (ret == 0) + ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), + dest_a, src_a->alloc); - dest_values = json_value_array_get_all(clone); - if (! dest_values) { - ret = JSON_ERR_INTERNAL; - break; + if (ret == 0) { + dest_values = json_value_array_get_all(clone); + if (dest_values == NULL) + ret = JSON_ERR_INTERNAL; } - for(i = 0; i < n; i++) { - ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); - if (ret < 0) - break; + if (ret == 0) { + for(i = 0; i < n; i++) { + json_value_init_null(&dest_values[i]); + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); + if (ret < 0) + break; + dest_a->size = i + 1; + } } - dest_a->size = src_a->size; + /* On any failure finalize the clone so the initialized prefix and + * the array allocation are released, not leaked. */ + if (ret < 0) { + int ret2 = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), clone); + if (ret2 < 0) + WOLFSENTRY_WARN("json_value_fini: %s\n", json_error_str(ret2)); + } break; } diff --git a/tests/unittests.c b/tests/unittests.c index 95b5e64b..67030ac6 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6434,6 +6434,69 @@ static int test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct return 0; } +static int test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + JSON_VALUE *elem; + JSON_VALUE *inner; + unsigned char small_str[50]; + unsigned char big_str[256]; + int i; + int ret; + + memset(small_str, 'a', sizeof small_str); + memset(big_str, 'b', sizeof big_str); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, small_str, sizeof small_str) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_string_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, big_str, sizeof big_str) < 0); + + json_value_init_null(&clone); + + /* Force the clone of the second (large) element to fail partway. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* The failed clone must finalize the already-cloned prefix, leaking nothing. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + /* Also exercise failure on a partially-built nested aggregate element: a + * nested array whose element buffer allocation fails after its payload was + * allocated must not leak that payload. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + inner = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner) < 0); + for (i = 0; i < 16; ++i) { + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), inner); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, i) < 0); + inner = json_value_array_get(&src, 0); + WOLFSENTRY_EXIT_ON_TRUE(inner == NULL); + } + + json_value_init_null(&clone); + /* The clone of the 16-slot inner array needs a 256-byte element buffer, + * which fails after the inner array payload has been allocated. */ + test_cjv_fail_big = 200; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_fail_big = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6449,6 +6512,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_remove_range(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 825a906760846e4a9e79a76f40170e6500754cf9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:28:03 -0700 Subject: [PATCH 036/255] F-13882 - https://fenrir.wolfssl.com/finding/13882 - clone empty array without a zero-size allocation --- src/json/centijson_value.c | 6 ++++-- tests/unittests.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index fa5e5096..5b4259e6 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1929,13 +1929,15 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo ret = JSON_ERR_INTERNAL; } - if (ret == 0) + /* Skip the element buffer entirely for an empty array; a zero-size + * allocation may legitimately return NULL. */ + if ((ret == 0) && (src_a->alloc > 0)) ret = json_value_array_realloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), dest_a, src_a->alloc); if (ret == 0) { dest_values = json_value_array_get_all(clone); - if (dest_values == NULL) + if ((n > 0) && (dest_values == NULL)) ret = JSON_ERR_INTERNAL; } diff --git a/tests/unittests.c b/tests/unittests.c index 67030ac6..29a76c65 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6497,6 +6497,31 @@ static int test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct return 0; } +static int test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE src; + JSON_VALUE clone; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + json_value_init_null(&clone); + + /* With an allocator that returns NULL for zero-size requests, cloning an + * empty array must still succeed (it needs no element storage). */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_ARRAY); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&clone) == 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6515,6 +6540,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_array_partial(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 64f958f0d1f7c95601ceb402bcc8edfced81a19f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:29:34 -0700 Subject: [PATCH 037/255] F-10491 - https://fenrir.wolfssl.com/finding/10491 - reject lossy numeric conversions in compatibility predicate --- src/json/centijson_value.c | 36 +++++++++++++++++++++++---- tests/unittests.c | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 5b4259e6..4d3110fb 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -56,6 +56,7 @@ #endif #include +#include #ifndef WOLFSENTRY_NO_ALLOCA #include #endif @@ -255,6 +256,27 @@ json_value_type(const JSON_VALUE* v) return (JSON_VALUE_TYPE)(v->data.data_bytes[0] & TYPE_MASK); } +static uint64_t json_int_magnitude(int64_t x) +{ + return (x < 0) ? (~(uint64_t)x + 1U) : (uint64_t)x; +} + +/* An integer is exactly representable in a binary floating type with + * mantissa_bits of precision when its significant bits (ignoring trailing + * zeros) fit in that precision. */ +static int json_int_fits_mantissa(uint64_t u, int mantissa_bits) +{ + int bits = 0; + + while((u != 0) && ((u & 1U) == 0)) + u >>= 1; + while(u != 0) { + ++bits; + u >>= 1; + } + return bits <= mantissa_bits; +} + WOLFSENTRY_API int json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) { @@ -265,22 +287,26 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) * long as the conversion does not loose much information. */ switch(json_value_type(v)) { case JSON_VALUE_INT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int32(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_UINT32 && json_value_int32(v) >= 0) || (type == JSON_VALUE_UINT64 && json_value_int32(v) >= 0); case JSON_VALUE_UINT32: - return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_INT64 || type == JSON_VALUE_UINT64 || type == JSON_VALUE_DOUBLE) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa((uint64_t)json_value_uint32(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint32(v) <= INT32_MAX); case JSON_VALUE_INT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_int_magnitude(json_value_int64(v)), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_int64(v) >= INT32_MIN && json_value_int64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_int64(v) >= 0 && json_value_int64(v) <= UINT32_MAX) || (type == JSON_VALUE_UINT64 && json_value_int64(v) >= 0); case JSON_VALUE_UINT64: - return (type == JSON_VALUE_FLOAT || type == JSON_VALUE_DOUBLE) || + return (type == JSON_VALUE_DOUBLE && json_int_fits_mantissa(json_value_uint64(v), DBL_MANT_DIG)) || + (type == JSON_VALUE_FLOAT && json_int_fits_mantissa(json_value_uint64(v), FLT_MANT_DIG)) || (type == JSON_VALUE_INT32 && json_value_uint64(v) <= INT32_MAX) || (type == JSON_VALUE_UINT32 && json_value_uint64(v) <= UINT32_MAX) || (type == JSON_VALUE_INT64 && json_value_uint64(v) <= INT64_MAX); @@ -293,7 +319,7 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) (type == JSON_VALUE_UINT64 && json_value_float(v) == (float)json_value_uint64(v)); case JSON_VALUE_DOUBLE: - return (type == JSON_VALUE_FLOAT) || + return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || (type == JSON_VALUE_INT32 && json_value_double(v) == (double)json_value_int32(v)) || (type == JSON_VALUE_UINT32 && json_value_double(v) == (double)json_value_uint32(v)) || (type == JSON_VALUE_INT64 && json_value_double(v) == (double)json_value_int64(v)) || diff --git a/tests/unittests.c b/tests/unittests.c index 29a76c65..0380fbc5 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6522,6 +6522,53 @@ static int test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct w return 0; } +static int test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* A finite double outside the float range is not float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e308) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small double is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 2.0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 1 is not exactly representable in a float. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777217) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^24 + 2 is exactly representable in a float and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int64_t)16777218) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is float-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)1000) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_FLOAT)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 1 is not exactly representable in a double. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (uint64_t)9007199254740993ULL) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* 2^53 + 2 is exactly representable in a double and must stay compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (uint64_t)9007199254740994ULL) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A small integer is double-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (int32_t)5) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6543,6 +6590,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_clone_empty_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 4ca95898336e989b3da14feabcd2477ee244ec2e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:30:49 -0700 Subject: [PATCH 038/255] F-13888 - https://fenrir.wolfssl.com/finding/13888 - range check floating values before casting in compatibility predicate --- src/json/centijson_value.c | 16 ++++++++-------- tests/unittests.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 4d3110fb..458fc481 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -313,17 +313,17 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) case JSON_VALUE_FLOAT: return (type == JSON_VALUE_DOUBLE) || - (type == JSON_VALUE_INT32 && json_value_float(v) == (float)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_float(v) == (float)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_float(v) == (float)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_float(v) == (float)json_value_uint64(v)); + (type == JSON_VALUE_INT32 && (double)json_value_float(v) >= -2147483648.0 && (double)json_value_float(v) <= 2147483647.0 && json_value_float(v) == (float)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) <= 4294967295.0 && json_value_float(v) == (float)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && (double)json_value_float(v) >= -9223372036854775808.0 && (double)json_value_float(v) < 9223372036854775808.0 && json_value_float(v) == (float)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) < 18446744073709551616.0 && json_value_float(v) == (float)json_value_uint64(v)); case JSON_VALUE_DOUBLE: return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || - (type == JSON_VALUE_INT32 && json_value_double(v) == (double)json_value_int32(v)) || - (type == JSON_VALUE_UINT32 && json_value_double(v) == (double)json_value_uint32(v)) || - (type == JSON_VALUE_INT64 && json_value_double(v) == (double)json_value_int64(v)) || - (type == JSON_VALUE_UINT64 && json_value_double(v) == (double)json_value_uint64(v)); + (type == JSON_VALUE_INT32 && json_value_double(v) >= -2147483648.0 && json_value_double(v) <= 2147483647.0 && json_value_double(v) == (double)json_value_int32(v)) || + (type == JSON_VALUE_UINT32 && json_value_double(v) >= 0.0 && json_value_double(v) <= 4294967295.0 && json_value_double(v) == (double)json_value_uint32(v)) || + (type == JSON_VALUE_INT64 && json_value_double(v) >= -9223372036854775808.0 && json_value_double(v) < 9223372036854775808.0 && json_value_double(v) == (double)json_value_int64(v)) || + (type == JSON_VALUE_UINT64 && json_value_double(v) >= 0.0 && json_value_double(v) < 18446744073709551616.0 && json_value_double(v) == (double)json_value_uint64(v)); default: break; diff --git a/tests/unittests.c b/tests/unittests.c index 0380fbc5..e1fee486 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6569,6 +6569,37 @@ static int test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wo return 0; } +static int test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE v; + + /* Out-of-range float/double values must not be treated as integer-compatible, + * and the predicate must not perform an out-of-range floating-to-integer cast. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e30F) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1e300) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_UINT64)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* An in-range integral float is integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_float(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 42.0F) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + /* A non-integral double is not integer-compatible. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_double(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, 1.5) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_INT32)); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6593,6 +6624,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 8e18e8e2f849766053c0f3e66b1fc6170c875165 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:32:18 -0700 Subject: [PATCH 039/255] F-11604 - https://fenrir.wolfssl.com/finding/11604 - free removed dictionary node and keep size consistent on finalize failure --- src/json/centijson_value.c | 13 ++++----- tests/unittests.c | 55 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 458fc481..bc01a318 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1623,6 +1623,7 @@ json_value_dict_remove_( int path_len = 0; int cmp; int ret; + int ret2; if (node == NULL) return -1; @@ -1740,24 +1741,24 @@ json_value_dict_remove_( else d->order_tail = node->order_prev; } + /* The node is already unlinked, so finalize both members best-effort, then + * always free it and decrement the size to keep the dictionary consistent. */ ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->key); - if (ret < 0) - return ret; - ret = json_value_fini( + ret2 = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->json_value); - if (ret < 0) - return ret; + if (ret == 0) + ret = ret2; free(node); d->size--; - return 0; + return ret; } WOLFSENTRY_API int diff --git a/tests/unittests.c b/tests/unittests.c index e1fee486..47fe16b2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6600,6 +6600,58 @@ static int test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_IN_EX(st return 0; } +static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE nested_copy; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"b", 1); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)1) < 0); + + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 2); + + /* Keep an alias to the nested dictionary so the test can reclaim it after + * removal frees the owning node (see below). */ + nested = json_value_dict_get(&outer, (const unsigned char *)"a"); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + memcpy(&nested_copy, nested, sizeof nested_copy); + + /* Force value finalization (nested dictionary cleanup) to fail during removal. */ + test_cjv_budget = 0; + ret = json_value_dict_remove_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* The removed node must not remain counted in the dictionary. */ + WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 1); + + /* Reclaim the nested dictionary, whose finalization the fault interrupted. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &nested_copy) < 0); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6627,6 +6679,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 0ed9c689fd1dceb192db72f341f5e8cf3db40178 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:33:04 -0700 Subject: [PATCH 040/255] F-11603 - https://fenrir.wolfssl.com/finding/11603 - keep dictionary retryable by not freeing a still-linked node on cleanup failure --- src/json/centijson_value.c | 2 -- tests/unittests.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index bc01a318..9f7c4a17 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1863,7 +1863,6 @@ json_value_dict_clean( #endif &node->key); if (ret < 0) { - free(node); free((void *)stack); return ret; } @@ -1873,7 +1872,6 @@ json_value_dict_clean( #endif &node->json_value); if (ret < 0) { - free(node); free((void *)stack); return ret; } diff --git a/tests/unittests.c b/tests/unittests.c index 47fe16b2..b375966f 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6652,6 +6652,40 @@ static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct return 0; } +static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + + /* First cleanup allocates the outer traversal stack, then fails on the + * nested dictionary's stack allocation. */ + test_cjv_budget = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* A retry must succeed: no node may have been freed while still linked. */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6682,6 +6716,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 69b49ab327889ee664c57ccdcb08a00d4305e66d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:33:47 -0700 Subject: [PATCH 041/255] F-10499 - https://fenrir.wolfssl.com/finding/10499 - skip zero-size stack allocation when cleaning or cloning an empty dictionary --- src/json/centijson_value.c | 21 ++++++++++++++++----- tests/unittests.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 9f7c4a17..f47621cb 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1847,9 +1847,15 @@ json_value_dict_clean( if(d == NULL) return 0; - stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); - if (stack == NULL) - return JSON_ERR_OUTOFMEMORY; + /* An empty tree needs no stack; a conforming allocator may return NULL for a + * zero-size request, so do not treat that as an error. */ + if(d->root != NULL) { + stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); + if (stack == NULL) + return JSON_ERR_OUTOFMEMORY; + } else { + stack = NULL; + } stack_size = json_value_dict_leftmost_path(stack, d->root); @@ -2015,8 +2021,13 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo } } else { int stack_size; - RBTREE **stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); /* put this on the heap to avoid runaway growth of stack on deep JSON trees. */ - if (! stack) { + RBTREE **stack = NULL; + /* put the stack on the heap to avoid runaway growth of the C + * stack on deep JSON trees; an empty tree needs none, and a + * zero-size allocation may legitimately return NULL. */ + if (src_dict->root != NULL) + stack = (RBTREE **)malloc(rbtree_stack_size_needed(src_dict)); + if ((src_dict->root != NULL) && (stack == NULL)) { ret = JSON_ERR_OUTOFMEMORY; } else { stack_size = json_value_dict_leftmost_path(stack, src_dict->root); diff --git a/tests/unittests.c b/tests/unittests.c index b375966f..4006e5e5 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6686,6 +6686,34 @@ static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wo return 0; } +static int test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE d; + JSON_VALUE clone; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d) < 0); + json_value_init_null(&clone); + + /* Cloning an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d, &clone); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_type(&clone) == JSON_VALUE_DICT); + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + + /* Cleaning up an empty dictionary must not require a zero-size allocation. */ + test_cjv_zero_null = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &d); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(ret >= 0); + + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6719,6 +6747,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 2a9a5bcce7411e2f2d95951b5d0bf224457811d5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 15:34:30 -0700 Subject: [PATCH 042/255] F-8754 - https://fenrir.wolfssl.com/finding/8754 - free array buffer directly when shrinking to zero capacity --- src/json/centijson_value.c | 9 +++++++++ tests/unittests.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index f47621cb..f3c4c6cc 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -944,6 +944,15 @@ json_value_array_realloc( { JSON_VALUE* json_value_buf; + /* Shrinking to zero: free the buffer directly rather than via realloc(p, 0), + * which is allowed to free and return NULL and would leave a dangling buffer. */ + if(alloc == 0) { + free(a->json_value_buf); + a->json_value_buf = NULL; + a->alloc = 0; + return 0; + } + json_value_buf = (JSON_VALUE*) realloc(a->json_value_buf, alloc * sizeof(JSON_VALUE)); if(json_value_buf == NULL) return -1; diff --git a/tests/unittests.c b/tests/unittests.c index 4006e5e5..59625b3e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6714,6 +6714,36 @@ static int test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsent return 0; } +static int test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE arr; + JSON_VALUE *elem; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)7) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 1); + + /* Model an allocator for which realloc(ptr, 0) frees and returns NULL. + * Removing the last element shrinks the backing store to zero capacity; the + * array must stay usable (no dangling buffer) and finalize cleanly. */ + test_cjv_zero_null = 1; + WOLFSENTRY_EXIT_ON_TRUE(json_value_array_remove(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr, 0) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_array_size(&arr) == 0); + + elem = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr); + WOLFSENTRY_EXIT_ON_TRUE(elem == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), elem, (int32_t)9) < 0); + WOLFSENTRY_EXIT_ON_FALSE(json_value_int32(json_value_array_get(&arr, 0)) == 9); + + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &arr) < 0); + test_cjv_zero_null = 0; + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static wolfsentry_errcode_t test_cjvalue_fixes(void) { struct wolfsentry_allocator test_allocator; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -6750,6 +6780,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_array_shrink_to_zero(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 18a674b16790f44ea6f9b67a2939468b6aec8979 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 14:01:28 -0700 Subject: [PATCH 043/255] F-10491 - https://fenrir.wolfssl.com/finding/10491 - Build the 2^53 test constants without C99 long long literals --- tests/unittests.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 59625b3e..0c4f5a9d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6552,12 +6552,12 @@ static int test_cjvalue_compatible_lossy(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wo WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); /* 2^53 + 1 is not exactly representable in a double. */ - WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (uint64_t)9007199254740993ULL) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 1)) < 0); WOLFSENTRY_EXIT_ON_TRUE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); /* 2^53 + 2 is exactly representable in a double and must stay compatible. */ - WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (uint64_t)9007199254740994ULL) < 0); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_uint64(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v, (((uint64_t)1 << 53) + 2)) < 0); WOLFSENTRY_EXIT_ON_FALSE(json_value_is_compatible(&v, JSON_VALUE_DOUBLE)); WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &v) < 0); From 4e38af29bbc6a06b147f8a666fc2e83db5e99f78 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 14:01:34 -0700 Subject: [PATCH 044/255] F-8754 - https://fenrir.wolfssl.com/finding/8754 - Refuse an array growth whose doubled capacity wraps instead of shrinking to zero --- src/json/centijson_value.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index f3c4c6cc..baf28b6c 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1017,16 +1017,21 @@ json_value_array_insert( JSON_VALUE* v, size_t index) { ARRAY* a = json_value_array_payload(v); + size_t new_alloc; if(a == NULL || index > a->size) return NULL; if(a->size >= a->alloc) { + new_alloc = (a->alloc > 0) ? a->alloc * 2 : 1; + /* a wrapped doubling must not reach the shrink-to-zero path */ + if(new_alloc <= a->size) + return NULL; if(json_value_array_realloc( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif - a, (a->alloc > 0) ? a->alloc * 2 : 1) != 0) + a, new_alloc) != 0) return NULL; } From 25af7ce25f00a2de9db1f5ef6d67f265e9c9b000 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 14:01:39 -0700 Subject: [PATCH 045/255] F-10499 - https://fenrir.wolfssl.com/finding/10499 - Skip the dictionary stack walk when no stack was allocated for an empty tree --- src/json/centijson_value.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index baf28b6c..a155b315 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1871,7 +1871,7 @@ json_value_dict_clean( stack = NULL; } - stack_size = json_value_dict_leftmost_path(stack, d->root); + stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, d->root) : 0; while(stack_size > 0) { node = stack[--stack_size]; @@ -2044,7 +2044,7 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo if ((src_dict->root != NULL) && (stack == NULL)) { ret = JSON_ERR_OUTOFMEMORY; } else { - stack_size = json_value_dict_leftmost_path(stack, src_dict->root); + stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, src_dict->root) : 0; while(stack_size > 0) { src_dict_node = stack[--stack_size]; From c5525a2440d9c42a306cecde8342bbae75389c08 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 14:01:40 -0700 Subject: [PATCH 046/255] Move the documented analyzer false positives to their new centijson_value lines --- tests/filter-clang-analyzer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/filter-clang-analyzer.py b/tests/filter-clang-analyzer.py index f4e199fa..72bdbea1 100644 --- a/tests/filter-clang-analyzer.py +++ b/tests/filter-clang-analyzer.py @@ -8,9 +8,9 @@ ALLOWED_RESULTS = { - ("core.uninitialized.Assign", "src/json/centijson_value.c", 1285), - ("core.NullDereference", "src/json/centijson_value.c", 1483), - ("core.NullDereference", "src/json/centijson_value.c", 1520), + ("core.uninitialized.Assign", "src/json/centijson_value.c", 1366), + ("core.NullDereference", "src/json/centijson_value.c", 1564), + ("core.NullDereference", "src/json/centijson_value.c", 1601), } From 9288ce2b6b48a19f9fe9cf641cd2dbfd54f40be9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:23:03 -0700 Subject: [PATCH 047/255] F-11603 - https://fenrir.wolfssl.com/finding/11603 - Unlink each dictionary node before freeing it so a failed clean can be retried --- src/json/centijson_value.c | 16 +++++++++++++++ tests/unittests.c | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index a155b315..27d9e619 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1895,6 +1895,22 @@ json_value_dict_clean( free((void *)stack); return ret; } + /* unlink before freeing so a failed clean leaves only live nodes for a + * retry; size is kept, as it still bounds the retry's stack */ + if(stack_size > 0) + stack[stack_size - 1]->left = right; + else + d->root = right; + if(v->data.data_bytes[0] & HAS_ORDERLIST) { + if(node->order_prev != NULL) + node->order_prev->order_next = node->order_next; + else + d->order_head = node->order_next; + if(node->order_next != NULL) + node->order_next->order_prev = node->order_prev; + else + d->order_tail = node->order_prev; + } free(node); stack_size += json_value_dict_leftmost_path(stack + stack_size, right); diff --git a/tests/unittests.c b/tests/unittests.c index 0c4f5a9d..46cccb98 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6652,6 +6652,43 @@ static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct return 0; } +static int test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE *nested; + JSON_VALUE *slot; + char keybuf[8]; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + for (i = 0; i < 3; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); + } + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"zz", 2); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + slot = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)"in", 2); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, 7) < 0); + + /* the earlier keys are freed before the nested dictionary's stack + * allocation fails on the last key. */ + test_cjv_budget = 1; + ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + + /* a retry must not reach the nodes freed before the failure */ + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) { JSON_VALUE outer; @@ -6777,6 +6814,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_empty_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); From 6b03f64ab269f7192c38530f63c867dd157a6ecc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:52 -0700 Subject: [PATCH 048/255] F-13286 - https://fenrir.wolfssl.com/finding/13286 - Check action inhibition before marking insert and delete actions called --- src/actions.c | 41 +++++------ tests/unittests.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 20 deletions(-) diff --git a/src/actions.c b/src/actions.c index 755c80e0..3764160d 100644 --- a/src/actions.c +++ b/src/actions.c @@ -581,16 +581,6 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( if (WOLFSENTRY_CHECK_BITS(rule_route->flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)) WOLFSENTRY_ERROR_RETURN(ALREADY); - else { - wolfsentry_route_flags_t flags_before; - wolfsentry_route_flags_t flags_after; - WOLFSENTRY_ATOMIC_UPDATE_FLAGS( - rule_route->flags, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, - &flags_before, - &flags_after); - } break; case WOLFSENTRY_ACTION_TYPE_MATCH: @@ -604,16 +594,6 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( if (WOLFSENTRY_CHECK_BITS(rule_route->flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)) WOLFSENTRY_ERROR_RETURN(ALREADY); - else { - wolfsentry_route_flags_t flags_before; - wolfsentry_route_flags_t flags_after; - WOLFSENTRY_ATOMIC_UPDATE_FLAGS( - rule_route->flags, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED, - (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, - &flags_before, - &flags_after); - } break; case WOLFSENTRY_ACTION_TYPE_DECISION: @@ -630,6 +610,27 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( (action_event->config && WOLFSENTRY_CHECK_BITS(action_event->config->config.flags, WOLFSENTRY_EVENTCONFIG_FLAG_INHIBIT_ACTIONS))) WOLFSENTRY_RETURN_OK; + if (action_type == WOLFSENTRY_ACTION_TYPE_INSERT) { + wolfsentry_route_flags_t flags_before; + wolfsentry_route_flags_t flags_after; + WOLFSENTRY_ATOMIC_UPDATE_FLAGS( + rule_route->flags, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, + &flags_after); + } + else if (action_type == WOLFSENTRY_ACTION_TYPE_DELETE) { + wolfsentry_route_flags_t flags_before; + wolfsentry_route_flags_t flags_after; + WOLFSENTRY_ATOMIC_UPDATE_FLAGS( + rule_route->flags, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED, + (wolfsentry_route_flags_t)WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, + &flags_after); + } + for (i = (struct wolfsentry_action_list_ent *)w_a_l->header.head; i; i = (struct wolfsentry_action_list_ent *)i->header.next) diff --git a/tests/unittests.c b/tests/unittests.c index 46cccb98..95352871 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3584,6 +3584,173 @@ static int test_dynamic_rules(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t action_dispatch_counter_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_RETURN_OK; +} + +static void dynamic_rules_fill_sockaddrs( + struct wolfsentry_sockaddr *remote, + struct wolfsentry_sockaddr *local, + size_t addr_bytes) +{ + remote->sa_family = local->sa_family = AF_INET; + remote->sa_proto = local->sa_proto = IPPROTO_TCP; + remote->sa_port = 12345; + local->sa_port = 443; + remote->addr_len = local->addr_len = (wolfsentry_addr_bits_t)(addr_bytes * BITS_PER_BYTE); + remote->interface = local->interface = 1; + memcpy(remote->addr, "\1\2\3\4", addr_bytes); + memcpy(local->addr, "\4\3\2\1", addr_bytes); +} + +static int test_dispatch_inhibited_insert_actions(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t route_flags; + int insert_calls = 0; + int delete_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &insert_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "test-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "test-delete-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &delete_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_DELETE, + "test-delete-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-insert", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &route, + &action_results)); + + /* actions were inhibited at insert time, so the handler must not have run, + * and the route must not be marked as having run its insert actions. + */ + WOLFSENTRY_EXIT_ON_FALSE(insert_calls == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + /* re-enable actions and run the deferred insert actions; the handler must + * now run exactly once for the previously inhibited route. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_enable_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(insert_calls == 1); + + /* with actions inhibited again, deleting the route must not run its delete + * handler nor mark the route as having run its delete actions. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route), + NULL /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(delete_calls == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -7113,6 +7280,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dynamic_rules failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_inhibited_insert_actions(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_inhibited_insert_actions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 6f78675e043b4d18e42f20c382ce121e48a38fb6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:52 -0700 Subject: [PATCH 049/255] F-11617 - https://fenrir.wolfssl.com/finding/11617 - Bar reentrant action list mutation during dispatch to prevent use-after-free --- src/actions.c | 33 ++- src/wolfsentry_internal.h | 1 + src/wolfsentry_util.c | 8 + tests/unittests.c | 540 ++++++++++++++++++++++++++++++++++++++ wolfsentry/wolfsentry.h | 5 + 5 files changed, 585 insertions(+), 2 deletions(-) diff --git a/src/actions.c b/src/actions.c index 3764160d..567de0a5 100644 --- a/src/actions.c +++ b/src/actions.c @@ -504,6 +504,9 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_delete( WOLFSENTRY_MUTEX_OR_RETURN(); + if (wolfsentry->action_dispatch_depth != 0) + WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(BUSY); + for (wolfsentry_list_ent_get_first(&action_list->header, &i); i; wolfsentry_list_ent_get_next(&action_list->header, &i)) @@ -534,6 +537,11 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_delete_all( WOLFSENTRY_MUTEX_OR_RETURN(); + /* no dispatch-in-progress guard here: this runs only from event teardown + * (at refcount zero) and clone error cleanup, never on a list being + * iterated, so it must not fail while an unrelated dispatch is active. + */ + for (wolfsentry_list_ent_get_first(&action_list->header, &i); i; i = next) @@ -610,6 +618,12 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( (action_event->config && WOLFSENTRY_CHECK_BITS(action_event->config->config.flags, WOLFSENTRY_EVENTCONFIG_FLAG_INHIBIT_ACTIONS))) WOLFSENTRY_RETURN_OK; + /* hold a reference on the event before marking lifecycle state, so a failed + * acquisition cannot leave the route flagged with its handlers never run. + */ + WOLFSENTRY_REFCOUNT_INCREMENT(action_event->header.refcount, ret); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (action_type == WOLFSENTRY_ACTION_TYPE_INSERT) { wolfsentry_route_flags_t flags_before; wolfsentry_route_flags_t flags_after; @@ -631,6 +645,11 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( &flags_after); } + /* mark a dispatch in progress, so a reentrant handler cannot free a live + * list entry (the single-entry mutator returns BUSY). + */ + WOLFSENTRY_ATOMIC_INCREMENT(wolfsentry->action_dispatch_depth, 1); + for (i = (struct wolfsentry_action_list_ent *)w_a_l->header.head; i; i = (struct wolfsentry_action_list_ent *)i->header.next) @@ -642,11 +661,21 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_list_dispatch( #ifdef WOLFSENTRY_DEBUG_ACTIONS fprintf(stderr,"calling action %s for event %s and action type %u\n", wolfsentry_action_get_label(i->action), wolfsentry_event_get_label(trigger_event), (unsigned)action_type); #endif - if ((ret = i->action->handler(WOLFSENTRY_CONTEXT_ARGS_OUT, i->action, i->action->handler_arg, caller_arg, trigger_event, action_type, target_route, route_table, rule_route, action_results)) < 0) + if ((ret = i->action->handler(WOLFSENTRY_CONTEXT_ARGS_OUT, i->action, i->action->handler_arg, caller_arg, trigger_event, action_type, target_route, route_table, rule_route, action_results)) < 0) { + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); WOLFSENTRY_ERROR_RERETURN(ret); - if (WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP)) + } + if (WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP)) { + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); WOLFSENTRY_RETURN_OK; + } } + + WOLFSENTRY_ATOMIC_DECREMENT(wolfsentry->action_dispatch_depth, 1); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action_event, NULL /* action_results */)); + WOLFSENTRY_RETURN_OK; } diff --git a/src/wolfsentry_internal.h b/src/wolfsentry_internal.h index 484c5911..9984d052 100644 --- a/src/wolfsentry_internal.h +++ b/src/wolfsentry_internal.h @@ -494,6 +494,7 @@ struct wolfsentry_context { #endif struct wolfsentry_table_header ents_by_id; struct wolfsentry_list_header cleanup_hooks; + int action_dispatch_depth; /* nonzero while an action dispatch loop is active, so list mutators refuse to free a live entry */ }; #ifdef WOLFSENTRY_THREADSAFE diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index fc590a01..6ecc1e78 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4249,6 +4249,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_exchange(WOLFSENTRY_CONTE } #endif + /* refuse to swap tables out from under an action dispatch in progress on + * either context, which would strand the live list's reentrancy guard. + */ + if ((wolfsentry->action_dispatch_depth != 0) || (wolfsentry2->action_dispatch_depth != 0)) { + ret = WOLFSENTRY_ERROR_ENCODE(BUSY); + goto out; + } + /* now that we have a mutex on both contexts, coherently copy the route * metadata from the current context to the new one to be swapped in. */ diff --git a/tests/unittests.c b/tests/unittests.c index 95352871..6802b2d4 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3751,6 +3751,526 @@ static int test_dispatch_inhibited_insert_actions(void) { WOLFSENTRY_RETURN_OK; } +struct reentrant_delete_ctx { + const char *event_label; + const char *action_label; + wolfsentry_action_type_t which_action_list; + int call_count; + wolfsentry_errcode_t delete_ret; +}; + +static wolfsentry_errcode_t action_reentrant_delete_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct reentrant_delete_ctx *c = (struct reentrant_delete_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->delete_ret = wolfsentry_event_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + c->event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + c->which_action_list, + c->action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED); + } + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_reentrant_action_delete(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct reentrant_delete_ctx sdctx; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&sdctx, 0, sizeof sdctx); + sdctx.event_label = "ev-match"; + sdctx.action_label = "self-del-action"; + sdctx.which_action_list = WOLFSENTRY_ACTION_TYPE_MATCH; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self-del-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_reentrant_delete_handler, + &sdctx, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "after-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &after_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* a self-deleting action followed by another action in the same list: the + * dispatch loop must survive the first handler deleting its own entry and + * still reach the second action. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "self-del-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "after-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + + /* a handler's attempt to delete its own live entry is refused, and + * iteration still reaches the following action. + */ + WOLFSENTRY_EXIT_ON_FALSE(sdctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(sdctx.delete_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + /* now the first handler targets the SUCCESSOR entry: that delete must also + * be refused, and the successor must still run. + */ + sdctx.call_count = 0; + sdctx.action_label = "after-action"; + after_calls = 0; + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(sdctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(sdctx.delete_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +struct event_del_ctx { + const char *event_label; + int call_count; + wolfsentry_errcode_t del_ret; +}; + +static wolfsentry_errcode_t action_delete_own_event_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct event_del_ctx *c = (struct event_del_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->del_ret = wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + c->event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */); + } + + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t action_error_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); +} + +static int test_dispatch_handler_deletes_own_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct event_del_ctx edctx; + struct wolfsentry_event *gone_ref = NULL; + int after_calls = 0; + int probe_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&edctx, 0, sizeof edctx); + edctx.event_label = "ev-selfev"; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "evdel-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_delete_own_event_handler, &edctx, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-ev-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &probe_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "evdel-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-ev-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* a separate event used only to prove the dispatch guard is released. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-selfev", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the handler deleted its own event mid-dispatch; the delete succeeded and + * the event is gone from the table, yet the held event reference kept the + * list valid so iteration reached the following action. + */ + WOLFSENTRY_EXIT_ON_FALSE(edctx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_IS_SUCCESS(edctx.del_ret)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-selfev", + WOLFSENTRY_LENGTH_NULL_TERMINATED, &gone_ref), + ITEM_NOT_FOUND)); + + /* the dispatch released its guard, so an unrelated list is now mutable. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-probe", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "probe-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_handler_error(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int err_calls = 0; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_error_handler, &err_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the first handler returned an error, taking the loop's error-cleanup + * exit, so the following action did not run. + */ + WOLFSENTRY_EXIT_ON_FALSE(err_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 0); + + /* the error exit still released the dispatch guard, so the list is mutable. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-err", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "err-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + +struct ctx_exchange_ctx { + struct wolfsentry_context *peer; + int call_count; + wolfsentry_errcode_t exchange_ret; +}; + +static wolfsentry_errcode_t action_context_exchange_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + struct ctx_exchange_ctx *c = (struct ctx_exchange_ctx *)handler_arg; + + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + if (c != NULL) { + ++c->call_count; + c->exchange_ret = wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, c->peer); + } + + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_context_exchange_during_dispatch(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *peer = NULL; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct ctx_exchange_ctx cectx; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + memset(&cectx, 0, sizeof cectx); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, &wolfsentry, WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_context_exchange_handler, &cectx, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "after-exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append(WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-exch-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-exch", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + /* a peer context to attempt to swap in from the handler. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &peer, WOLFSENTRY_CLONE_FLAG_NONE)); + cectx.peer = peer; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* a context exchange attempted mid-dispatch must be refused, and the + * following action must still run. + */ + WOLFSENTRY_EXIT_ON_FALSE(cectx.call_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(cectx.exchange_ret, BUSY)); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&peer))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -7285,6 +7805,26 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_inhibited_insert_actions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_handler_deletes_own_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_handler_deletes_own_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_handler_error(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_handler_error failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_context_exchange_during_dispatch(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_context_exchange_during_dispatch failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_dispatch_reentrant_action_delete(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_reentrant_action_delete failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 225980b6..e81f6f28 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2921,6 +2921,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_action_insert_after( \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. + An action handler invoked during event dispatch must not call this from + within the dispatch; it returns #WOLFSENTRY_ERROR_ID_BUSY while any dispatch + is in progress. Perform action-list deletions outside dispatch; + append/prepend/insert remain permitted. + \sa WOLFSENTRY_CONTEXT_ARGS_IN */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_action_delete( From 00cb84b1d108d49d7fba8637937105601aa98346 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:53 -0700 Subject: [PATCH 050/255] F-13289 - https://fenrir.wolfssl.com/finding/13289 - Clear caller action results before dropping an action reference --- src/actions.c | 2 ++ tests/unittests.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/actions.c b/src/actions.c index 567de0a5..89eef45a 100644 --- a/src/actions.c +++ b/src/actions.c @@ -256,6 +256,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_action_drop_reference(WOLFSENTRY_ if ((action->header.parent_table != NULL) && (action->header.parent_table->ent_type != WOLFSENTRY_OBJECT_TYPE_ACTION)) WOLFSENTRY_ERROR_RETURN(WRONG_OBJECT); + if (action_results) + WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_REFCOUNT_DECREMENT(action->header.refcount, refs_left, ret); WOLFSENTRY_RERETURN_IF_ERROR(ret); if (refs_left > 0) diff --git a/tests/unittests.c b/tests/unittests.c index 6802b2d4..4e9d9df2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4271,6 +4271,54 @@ static int test_dispatch_context_exchange_during_dispatch(void) { WOLFSENTRY_RETURN_OK; } +static int test_action_drop_reference_clears_results(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_action *action = NULL; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "drop-ref-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + NULL, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "drop-ref-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &action)); + + /* with a reference still held, a non-final drop must clear stale caller + * bits rather than leaving them, matching the event and route drop APIs. + */ + action_results = WOLFSENTRY_ACTION_RES_DEALLOCATED | WOLFSENTRY_ACTION_RES_REJECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, action, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(action_results == WOLFSENTRY_ACTION_RES_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -7825,6 +7873,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_reentrant_action_delete failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_action_drop_reference_clears_results(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_action_drop_reference_clears_results failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 042b2c004a8ae7935c055fc9a0cddda3f740da65 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:53 -0700 Subject: [PATCH 051/255] F-2458 - https://fenrir.wolfssl.com/finding/2458 - Add coverage for the action inhibit flag suppressing dispatch --- tests/unittests.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 4e9d9df2..2ae5de56 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4271,6 +4271,101 @@ static int test_dispatch_context_exchange_during_dispatch(void) { WOLFSENTRY_RETURN_OK; } +static int test_dispatch_inhibit_actions_flag(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int match_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "inh-match-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + action_dispatch_counter_handler, + &match_calls, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "inh-match-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "ev-inh", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + /* with actions inhibited, the matched route's handler must not fire. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_inhibit_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(match_calls == 0); + + /* with actions re-enabled, the same dispatch must fire the handler. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_enable_actions(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(match_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + static int test_action_drop_reference_clears_results(void) { struct wolfsentry_context *wolfsentry; wolfsentry_ent_id_t id; @@ -7878,6 +7973,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_action_drop_reference_clears_results failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_inhibit_actions_flag(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_inhibit_actions_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 2ce10c325227706ff437de602d8bb10c658b4f65 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:53 -0700 Subject: [PATCH 052/255] F-3662 - https://fenrir.wolfssl.com/finding/3662 - Add coverage for action chain stop early-exit during dispatch --- tests/unittests.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 2ae5de56..a144b112 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4414,6 +4414,107 @@ static int test_action_drop_reference_clears_results(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t action_set_stop_handler( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + if (handler_arg != NULL) + ++*(int *)handler_arg; + WOLFSENTRY_SET_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); + WOLFSENTRY_RETURN_OK; +} + +static int test_dispatch_action_stop(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + int stop_calls = 0; + int after_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_set_stop_handler, &stop_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "after-stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &after_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "after-stop-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-stop", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the first match action signals stop, so the second must be skipped. */ + WOLFSENTRY_EXIT_ON_FALSE(stop_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(after_calls == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -7978,6 +8079,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_inhibit_actions_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_action_stop(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_action_stop failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From b14986e5720645862aa2171f8812f8cd77d6dec4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:53 -0700 Subject: [PATCH 053/255] F-3439 - https://fenrir.wolfssl.com/finding/3439 - Add coverage for disabled actions being skipped during dispatch --- tests/unittests.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index a144b112..4f31d22e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4515,6 +4515,107 @@ static int test_dispatch_action_stop(void) { WOLFSENTRY_RETURN_OK; } +static int test_dispatch_disabled_action(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct wolfsentry_action *disabled_action = NULL; + wolfsentry_action_flags_t flags_before, flags_after; + int disabled_calls = 0; + int enabled_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &disabled_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "enabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &enabled_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "enabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, "disabled-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, &disabled_action)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + disabled_action, WOLFSENTRY_ACTION_FLAG_DISABLED, WOLFSENTRY_ACTION_FLAG_NONE, &flags_before, &flags_after)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-dis", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + /* the disabled action must be skipped while the enabled one fires. */ + WOLFSENTRY_EXIT_ON_FALSE(disabled_calls == 0); + WOLFSENTRY_EXIT_ON_FALSE(enabled_calls == 1); + + /* clearing the disabled flag must let the action run again. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_update_flags( + disabled_action, WOLFSENTRY_ACTION_FLAG_NONE, WOLFSENTRY_ACTION_FLAG_DISABLED, &flags_before, &flags_after)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(disabled_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(enabled_calls == 2); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, disabled_action, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -8084,6 +8185,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_action_stop failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_disabled_action(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_disabled_action failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 5ba30b8c4dfbae938b5dacfba7922a46775ecafb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:53 -0700 Subject: [PATCH 054/255] F-2467 - https://fenrir.wolfssl.com/finding/2467 - Add coverage for DONT_COUNT_HITS suppressing action hit counting --- tests/unittests.c | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 4f31d22e..72e52800 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4616,6 +4616,99 @@ static int test_dispatch_disabled_action(void) { WOLFSENTRY_RETURN_OK; } +static int test_dispatch_dont_count_hits(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id; + struct wolfsentry_action *hc_action = NULL; + int hc_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t counted_flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + wolfsentry_route_flags_t uncounted_flags = counted_flags | WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_HITS; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &hc_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, "hc-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, &hc_action)); + + /* a route with DONT_COUNT_HITS: its matched action's handler runs but its + * hitcount must not advance. + */ + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + uncounted_flags, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, uncounted_flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(hc_calls == 1); + WOLFSENTRY_EXIT_ON_FALSE(hc_action->header.hitcount == 0); + + /* a route without the flag advances the hitcount on dispatch. */ + memcpy(remote.sa.addr, "\5\6\7\10", sizeof remote.addr_buf); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + counted_flags, "ev-hc", WOLFSENTRY_LENGTH_NULL_TERMINATED, &id, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, &remote.sa, &local.sa, counted_flags, + NULL, 0, NULL, &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(hc_calls == 2); + WOLFSENTRY_EXIT_ON_FALSE(hc_action->header.hitcount == 1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, hc_action, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -8190,6 +8283,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_disabled_action failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_dont_count_hits(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_dont_count_hits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 02663950c861b313bf2aa855a3630a44a1b9ca1a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 17:01:54 -0700 Subject: [PATCH 055/255] F-3872 - https://fenrir.wolfssl.com/finding/3872 - Add coverage for insert and delete action lifecycle guards --- tests/unittests.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 72e52800..d0415ad0 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4709,6 +4709,106 @@ static int test_dispatch_dont_count_hits(void) { WOLFSENTRY_RETURN_OK; } +static int test_dispatch_insert_delete_already_guard(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t route_flags; + int ins_calls = 0; + int del_calls = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ins-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &ins_calls, &id)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "del-action", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, action_dispatch_counter_handler, &del_calls, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, "ins-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_DELETE, "del-action", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + dynamic_rules_fill_sockaddrs(&remote.sa, &local.sa, sizeof remote.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, + flags, "ev-ad", WOLFSENTRY_LENGTH_NULL_TERMINATED, &route, &action_results)); + + /* insert actions run once and the route is marked accordingly. */ + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + /* a repeated insert pass must not re-run them while the flag is set. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 1); + + /* clearing the status reopens the insert actions for that route. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_clear_insert_action_status(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_INSERT_ACTIONS_CALLED)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_bulk_insert_actions(WOLFSENTRY_CONTEXT_ARGS_OUT, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(ins_calls == 2); + + /* deleting the route runs its delete actions exactly once and marks the + * route so the delete-side guard would reject a repeat dispatch. + */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route), + NULL /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(del_calls == 1); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, &route_flags)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_DELETE_ACTIONS_CALLED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -8288,6 +8388,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_dont_count_hits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dispatch_insert_delete_already_guard(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dispatch_insert_delete_already_guard failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 593652cbdf8582141e12802150a7bc5682a6e2ff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:35:00 -0700 Subject: [PATCH 056/255] F-10486 - https://fenrir.wolfssl.com/finding/10486 - Normalize TCP lifecycle mask in combined wolfIP filter installer --- src/wolfip/packet_filter_glue.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index d3411ab6..1dcec2ec 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -623,6 +623,19 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_icmp_callba WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } +static uint32_t wolfip_normalize_tcp_mask(uint32_t tcp_mask) +{ + /* connection-count increments and decrements are balanced only when the + * accept, close, and reset lifecycle events are delivered together. */ + if (tcp_mask & (WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | + WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | + WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET))) + tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | + WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | + WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET); + return tcp_mask; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_tcp_callback( WOLFSENTRY_CONTEXT_ARGS_IN, uint32_t tcp_mask) @@ -631,13 +644,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_tcp_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - if (tcp_mask & (WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | - WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET))) - tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | - WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET); - wolfip_mask_tcp = tcp_mask; + wolfip_mask_tcp = wolfip_normalize_tcp_mask(tcp_mask); ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -670,7 +677,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_callbacks( wolfip_mask_eth = ethernet_mask; wolfip_mask_ip = ip_mask; wolfip_mask_icmp = icmp_mask; - wolfip_mask_tcp = tcp_mask; + wolfip_mask_tcp = wolfip_normalize_tcp_mask(tcp_mask); wolfip_mask_udp = udp_mask; ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); From 70ffa5ef90e4b08ac012256899270ec818a77708 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:36:15 -0700 Subject: [PATCH 057/255] F-8767 - https://fenrir.wolfssl.com/finding/8767 - Validate wolfIP filter ownership before committing mask changes --- src/wolfip/packet_filter_glue.c | 73 ++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 1dcec2ec..68f1ac0a 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -53,7 +53,13 @@ static int wolfip_cleanup_registered; static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_event *event); -static wolfsentry_errcode_t wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_IN); +static wolfsentry_errcode_t wolfip_apply_masks( + WOLFSENTRY_CONTEXT_ARGS_IN, + uint32_t new_mask_eth, + uint32_t new_mask_ip, + uint32_t new_mask_icmp, + uint32_t new_mask_tcp, + uint32_t new_mask_udp); static byte wolfip_if_idx_to_byte(unsigned int if_idx) { @@ -537,21 +543,40 @@ static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_e } } -static wolfsentry_errcode_t wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_IN) +static wolfsentry_errcode_t wolfip_apply_masks( + WOLFSENTRY_CONTEXT_ARGS_IN, + uint32_t new_mask_eth, + uint32_t new_mask_ip, + uint32_t new_mask_icmp, + uint32_t new_mask_tcp, + uint32_t new_mask_udp) { - uint32_t combined = wolfip_mask_eth | wolfip_mask_ip | wolfip_mask_icmp | - wolfip_mask_tcp | wolfip_mask_udp; + uint32_t combined = new_mask_eth | new_mask_ip | new_mask_icmp | + new_mask_tcp | new_mask_udp; wolfsentry_errcode_t ret = WOLFSENTRY_ERROR_ENCODE(OK); + /* validate ownership before mutating any shared state, so a rejected call + * cannot perturb the owning context's masks or callback (fail-open). the + * check applies on the disable path too. */ + if ((wolfip_filter_context != NULL) && (wolfip_filter_context != wolfsentry)) + return WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); + if (combined) { - if (wolfip_filter_context && (wolfip_filter_context != wolfsentry)) - return WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); - if (!wolfip_cleanup_registered) { + if (! wolfip_cleanup_registered) { ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_wolfip_filter_callbacks, NULL); if (ret < 0) return ret; wolfip_cleanup_registered = 1; } + } + + wolfip_mask_eth = new_mask_eth; + wolfip_mask_ip = new_mask_ip; + wolfip_mask_icmp = new_mask_icmp; + wolfip_mask_tcp = new_mask_tcp; + wolfip_mask_udp = new_mask_udp; + + if (combined) { wolfip_filter_context = wolfsentry; wolfIP_filter_set_callback(wolfip_filter_with_wolfsentry, wolfsentry); } else { @@ -592,8 +617,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_ethernet_ca WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_eth = ethernet_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + ethernet_mask, wolfip_mask_ip, wolfip_mask_icmp, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -605,8 +631,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_ip4_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_ip = ip_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, ip_mask, wolfip_mask_icmp, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -618,8 +645,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_icmp_callba WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_icmp = icmp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, icmp_mask, wolfip_mask_tcp, + wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -644,8 +672,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_tcp_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_tcp = wolfip_normalize_tcp_mask(tcp_mask); - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, wolfip_mask_icmp, + wolfip_normalize_tcp_mask(tcp_mask), wolfip_mask_udp); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -657,8 +686,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_udp_callbac WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_udp = udp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + wolfip_mask_eth, wolfip_mask_ip, wolfip_mask_icmp, wolfip_mask_tcp, + udp_mask); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } @@ -674,12 +704,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_callbacks( WOLFSENTRY_MUTEX_OR_RETURN(); WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; - wolfip_mask_eth = ethernet_mask; - wolfip_mask_ip = ip_mask; - wolfip_mask_icmp = icmp_mask; - wolfip_mask_tcp = wolfip_normalize_tcp_mask(tcp_mask); - wolfip_mask_udp = udp_mask; - ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT); + ret = wolfip_apply_masks(WOLFSENTRY_CONTEXT_ARGS_OUT, + ethernet_mask, ip_mask, icmp_mask, wolfip_normalize_tcp_mask(tcp_mask), + udp_mask); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } From 93b9bfea670edcbbf68d082e1c24a54a1ccc5a25 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:36:26 -0700 Subject: [PATCH 058/255] F-8784 - https://fenrir.wolfssl.com/finding/8784 - Reset wolfIP cleanup registration when filters are disabled --- src/wolfip/packet_filter_glue.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 68f1ac0a..f6784de1 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -582,6 +582,10 @@ static wolfsentry_errcode_t wolfip_apply_masks( } else { wolfIP_filter_set_callback(NULL, NULL); wolfip_filter_context = NULL; + /* release ownership of the cleanup registration so the next context to + * install receives its own cleanup hook rather than relying on a stale + * flag bound to a context that may since be torn down. */ + wolfip_cleanup_registered = 0; } wolfIP_filter_set_eth_mask(wolfip_mask_eth); From 3e54f9a01ca4c73d872f0217bfcb01130c60475b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:36:42 -0700 Subject: [PATCH 059/255] F-5006 - https://fenrir.wolfssl.com/finding/5006 - Only clear wolfIP filter state when the owning context is destroyed --- src/wolfip/packet_filter_glue.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index f6784de1..9937e116 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -599,9 +599,13 @@ static wolfsentry_errcode_t wolfip_apply_masks( WOLFSENTRY_API_VOID wolfsentry_cleanup_wolfip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_IN, void *cleanup_arg) { - (void)wolfsentry; WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; (void)cleanup_arg; + /* a cleanup hook fires when its registering context is destroyed; only tear + * down the shared filter state if that context still owns it, so destroying + * a non-owner cannot silently disable a surviving owner's filtering. */ + if ((wolfip_filter_context != NULL) && (wolfip_filter_context != wolfsentry)) + return; wolfip_mask_eth = wolfip_mask_ip = wolfip_mask_icmp = wolfip_mask_tcp = wolfip_mask_udp = 0; wolfip_cleanup_registered = 0; wolfIP_filter_set_callback(NULL, NULL); From 3bb0a2f9d0bcbfa3f1b38fe5164608d09c6868b4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:36:54 -0700 Subject: [PATCH 060/255] F-10501 - https://fenrir.wolfssl.com/finding/10501 - Set CLOSE_WAIT action result for wolfIP TCP close-wait events --- src/wolfip/packet_filter_glue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 9937e116..8136aaf2 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -318,6 +318,7 @@ static int wolfip_filter_tcp( case WOLFIP_FILT_CLOSE_WAIT: route_flags |= outbound ? WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT : WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + action_results = WOLFSENTRY_ACTION_RES_CLOSE_WAIT; break; case WOLFIP_FILT_PORT_UNREACHABLE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; From 135a267a7f5735be40427d3240425808e490dec1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:37:04 -0700 Subject: [PATCH 061/255] F-10504 - https://fenrir.wolfssl.com/finding/10504 - Map wolfIP TCP dissociate to a neutral teardown result --- src/wolfip/packet_filter_glue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 8136aaf2..65326f26 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -364,7 +364,7 @@ static int wolfip_filter_tcp( break; case WOLFIP_FILT_DISSOCIATE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; - action_results = WOLFSENTRY_ACTION_RES_DEROGATORY; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; break; default: return 0; From 56ead9c0e125747f1f0c8e684a26e3e19b0cbe0c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:51:49 -0700 Subject: [PATCH 062/255] F-13885 - https://fenrir.wolfssl.com/finding/13885 - Handle wolfIP UDP and ICMP socket lifecycle reasons in the filter glue --- src/wolfip/packet_filter_glue.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 65326f26..28feb879 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -448,6 +448,10 @@ static int wolfip_filter_udp( action_results = WOLFSENTRY_ACTION_RES_STOPPED_LISTENING | WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES; break; + case WOLFIP_FILT_DISSOCIATE: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; + break; default: return 0; } @@ -510,6 +514,21 @@ static int wolfip_filter_icmp( route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; + case WOLFIP_FILT_BINDING: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD; + action_results = WOLFSENTRY_ACTION_RES_BINDING | + WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES; + /* socket lifecycle events carry the local endpoint in src_ip; orient + * the sockaddrs as locally originated so the bound address lands in + * local, while route_flags keeps the inbound binding semantics. */ + outbound = 1; + break; + case WOLFIP_FILT_DISSOCIATE: + route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSED; + break; default: return 0; } From 66008d0b85ef9b4c192cc09441a27a2ce7465d3e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:51:49 -0700 Subject: [PATCH 063/255] F-2837 - https://fenrir.wolfssl.com/finding/2837 - Deny wolfIP packets with unmapped protocols or event reasons --- src/wolfip/packet_filter_glue.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 28feb879..afcd54b4 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -215,7 +215,9 @@ static int wolfip_filter_ethernet( action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_link_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -267,7 +269,9 @@ static int wolfip_filter_ipv4( action_results = WOLFSENTRY_ACTION_RES_SOCK_ERROR; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -367,7 +371,9 @@ static int wolfip_filter_tcp( action_results = WOLFSENTRY_ACTION_RES_CLOSED; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -453,7 +459,9 @@ static int wolfip_filter_udp( action_results = WOLFSENTRY_ACTION_RES_CLOSED; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -530,7 +538,9 @@ static int wolfip_filter_icmp( action_results = WOLFSENTRY_ACTION_RES_CLOSED; break; default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped event reason %d\n", + (int)event->reason); + return -WOLFIP_EACCES; } wolfip_set_ipv4_sockaddrs(&remote.remote, &local.local, event, outbound); @@ -544,8 +554,10 @@ static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_e { struct wolfsentry_context *wolfsentry = (struct wolfsentry_context *)arg; - if ((wolfsentry == NULL) || (event == NULL)) + if (event == NULL) return 0; + if (wolfsentry == NULL) + return -WOLFIP_EACCES; switch (event->meta.ip_proto) { case WOLFIP_FILTER_PROTO_ETH: @@ -559,7 +571,9 @@ static int wolfip_filter_with_wolfsentry(void *arg, const struct wolfIP_filter_e case WOLFIP_FILTER_PROTO_ICMP: return wolfip_filter_icmp(wolfsentry, event); default: - return 0; + WOLFSENTRY_WARN("wolfIP filter: denying unmapped protocol %d\n", + (int)event->meta.ip_proto); + return -WOLFIP_EACCES; } } From 374ddf2a8d921570e9bb011e4e28d0889e0bb495 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:06 -0700 Subject: [PATCH 064/255] F-2438 - https://fenrir.wolfssl.com/finding/2438 - Deny wolfIP packets when the wolfSentry dispatch fails --- src/wolfip/packet_filter_glue.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index afcd54b4..9d471591 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -169,6 +169,10 @@ static int wolfip_dispatch_event( if (WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE) < 0) return -WOLFIP_EACCES; + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + return -WOLFIP_EACCES; + if (wolfip_action_rejects(*action_results)) return -WOLFIP_EACCES; From 415c3c869725fa3af13f22f2c0d34dbea565b569 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 10:14:20 -0700 Subject: [PATCH 065/255] F-10486 - https://fenrir.wolfssl.com/finding/10486 - Normalize the wolfIP TCP mask only when accept events are requested --- src/wolfip/packet_filter_glue.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/wolfip/packet_filter_glue.c b/src/wolfip/packet_filter_glue.c index 9d471591..c2860f3a 100644 --- a/src/wolfip/packet_filter_glue.c +++ b/src/wolfip/packet_filter_glue.c @@ -699,13 +699,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_wolfip_filter_icmp_callba static uint32_t wolfip_normalize_tcp_mask(uint32_t tcp_mask) { - /* connection-count increments and decrements are balanced only when the - * accept, close, and reset lifecycle events are delivered together. */ - if (tcp_mask & (WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | - WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET))) - tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING) | - WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | + /* an accept is counted as a connection, so when accepts are requested also + * deliver the close and reset events that release it; teardown-only masks + * must not turn on accept counting. */ + if (tcp_mask & WOLFIP_FILT_MASK(WOLFIP_FILT_ACCEPTING)) + tcp_mask |= WOLFIP_FILT_MASK(WOLFIP_FILT_CLOSED) | WOLFIP_FILT_MASK(WOLFIP_FILT_REMOTE_RESET); return tcp_mask; } From f8f83d340919f831e5f94dc74738b789bf3b26e7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:44:47 -0700 Subject: [PATCH 066/255] F-5002 - https://fenrir.wolfssl.com/finding/5002 - Reject handshake when issuer name rendering returns NULL --- examples/notification-demo/log_server/log_server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index c90d2812..9f36dd84 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -473,6 +473,10 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { issuerHash = wolfSSL_X509_issuer_name_hash(store->current_cert); issuerName = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_issuer_name(store->current_cert), 0, 0); + if (issuerName == NULL) { + fprintf(stderr, "ERROR: %s L%d null issuer name.\n", __FILE__,__LINE__); + return WOLFSSL_FAILURE; + } #ifdef DEBUG_TLS subjectCN = wolfSSL_X509_get_subjectCN(store->current_cert); From 59a18f7b8ab567c11853ce135a437271749323dc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:45:57 -0700 Subject: [PATCH 067/255] F-5779 - https://fenrir.wolfssl.com/finding/5779 - Free X509 name strings on every verify callback return path --- .../notification-demo/log_server/log_server.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index 9f36dd84..b9874b14 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -420,11 +420,11 @@ struct log_server_data { static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { struct log_server_data *app_data = NULL; - const char* issuerName = NULL; + char* issuerName = NULL; unsigned long issuerHash = 0; #if defined(DEBUG_TLS) && defined(OPENSSL_EXTRA) const char *subjectCN; - const char* subjectName; + char* subjectName = NULL; unsigned long subjectHash = 0; #endif struct ca_cert *ca_certs_i; @@ -501,11 +501,23 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { } if (app_data->issuer_cert == NULL) { fprintf(stderr, "ERROR: %s L%d app_data->issuer_cert NULL.\n",__FILE__,__LINE__); +#ifdef OPENSSL_EXTRA + XFREE(issuerName, NULL, DYNAMIC_TYPE_OPENSSL); + #ifdef DEBUG_TLS + XFREE(subjectName, NULL, DYNAMIC_TYPE_OPENSSL); + #endif +#endif return WOLFSSL_FAILURE; } app_data->error_code = 0; +#ifdef OPENSSL_EXTRA + XFREE(issuerName, NULL, DYNAMIC_TYPE_OPENSSL); + #ifdef DEBUG_TLS + XFREE(subjectName, NULL, DYNAMIC_TYPE_OPENSSL); + #endif +#endif return WOLFSSL_SUCCESS; } From 55b4188019d1a3468e3d8e6d3a6fb72a2e898cfd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:46:28 -0700 Subject: [PATCH 068/255] F-5712 - https://fenrir.wolfssl.com/finding/5712 - Strip control bytes from peer certificate strings before logging --- .../notification-demo/log_server/log_server.c | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index b9874b14..2ed4aa0a 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -417,6 +417,27 @@ struct log_server_data { int error_code; }; +#if defined(DEBUG_TLS) && defined(OPENSSL_EXTRA) +/* certificate DN strings are peer-controlled; drop control bytes before logging. */ +static const char *sanitize_log_str(const char *in, char *out, size_t outsz) { + size_t i; + unsigned char c; + + if (in == NULL) + return "(null)"; + if (outsz == 0) + return out; + for (i = 0; (i + 1 < outsz) && (in[i] != '\0'); ++i) { + c = (unsigned char)in[i]; + if ((c < 0x20) || (c == 0x7f)) + out[i] = '?'; + else + out[i] = (char)c; + } + out[i] = '\0'; + return out; +} +#endif static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { struct log_server_data *app_data = NULL; @@ -426,6 +447,7 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { const char *subjectCN; char* subjectName = NULL; unsigned long subjectHash = 0; + char subjectCN_buf[256], subjectName_buf[256], issuerName_buf[256]; #endif struct ca_cert *ca_certs_i; @@ -484,8 +506,11 @@ static int myVerifyCheck(int preverify, WOLFSSL_X509_STORE_CTX* store) { subjectName = wolfSSL_X509_NAME_oneline( wolfSSL_X509_get_subject_name(store->current_cert), 0, 0); fprintf(stderr, "CN=%s subjectName->name=\"%s\" subject_hash=%lu\n", - subjectCN, subjectName, subjectHash); - fprintf(stderr, "issuerName->name=\"%s\" issuer_hash=%lu\n", issuerName, issuerHash); + sanitize_log_str(subjectCN, subjectCN_buf, sizeof subjectCN_buf), + sanitize_log_str(subjectName, subjectName_buf, sizeof subjectName_buf), + subjectHash); + fprintf(stderr, "issuerName->name=\"%s\" issuer_hash=%lu\n", + sanitize_log_str(issuerName, issuerName_buf, sizeof issuerName_buf), issuerHash); #endif #endif From a9698560ae25ab75b039c52a85ec6388eecd0e23 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:48:00 -0700 Subject: [PATCH 069/255] F-5778 - https://fenrir.wolfssl.com/finding/5778 - Evict the entry at offset zero when the circular log wraps --- .../notification-demo/log_server/log_server.c | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index 2ed4aa0a..3e7b1d6f 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -315,6 +315,10 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { if (new_ent_size > circlog_size) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + /* reset a fully drained queue so a wrap cannot strand a stale head. */ + if (circlog_head == circlog_tail) + circlog_head = circlog_tail = 0; + if (circlog_tail + new_ent_size > circlog_size) { /* if the new ent will wrap, old ents need to be cleared out to the end, and a skip ent inserted. */ if (circlog_tail + circlog_ent_size_from_msg_len(0) < circlog_size) { @@ -342,7 +346,8 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { * the =, circlog_tail could be incremented below to equal circlog_head, * which is defined to be an empty queue. */ - while ((circlog_head > new_msg_start) && + while ((circlog_nents > 0) && + (circlog_head >= new_msg_start) && (circlog_head <= new_msg_start + new_ent_size)) { ret = circlog_dequeue_one(NULL); @@ -350,6 +355,10 @@ wolfsentry_errcode_t circlog_enqueue_one(size_t msg_len, char **msg_buf) { return ret; } + /* if eviction drained the queue, the new entry alone defines the head. */ + if (circlog_nents == 0) + circlog_head = new_msg_start; + new_ent = (struct circlog_message *)(void *)(circlog + new_msg_start); ret = wolfsentry_time_now_plus_delta(global_wolfsentry, 0 /* td */, &new_ent->when); new_ent->len = (uint16_t)msg_len; @@ -1518,6 +1527,47 @@ int main(int argc, char **argv) { fprintf(stderr,"circlog_head=%zu circlog_tail=%zu\n",circlog_head,circlog_tail); } + + if (circlog != NULL) { + char *wrap_buf; + struct circlog_message *wrap_msg; + size_t wrap_filled, wrap_reachable; + static const char wrap_s[] = "wrap eviction regression probe entry."; + size_t wrap_ent = circlog_ent_size_from_msg_len(strlen(wrap_s)); + + if ((circlog_reset() >= 0) && ((wrap_ent * 2) <= circlog_size)) { + wrap_filled = 0; + while ((circlog_tail + wrap_ent) <= circlog_size) { + ret = circlog_enqueue_one(strlen(wrap_s), &wrap_buf); + if (ret < 0) { + fprintf(stderr, "ERROR: %s L%d circlog failed: " WOLFSENTRY_ERROR_FMT "\n", __FILE__, __LINE__, WOLFSENTRY_ERROR_FMT_ARGS(ret)); + exit(1); + } + memcpy(wrap_buf, wrap_s, strlen(wrap_s)); + ++wrap_filled; + } + + ret = circlog_enqueue_one(strlen(wrap_s), &wrap_buf); + if (ret < 0) { + fprintf(stderr, "ERROR: %s L%d circlog failed: " WOLFSENTRY_ERROR_FMT "\n", __FILE__, __LINE__, WOLFSENTRY_ERROR_FMT_ARGS(ret)); + exit(1); + } + memcpy(wrap_buf, wrap_s, strlen(wrap_s)); + + wrap_reachable = 0; + wrap_msg = NULL; + while (circlog_iterate(&wrap_msg) >= 0) + ++wrap_reachable; + + if (circlog_nents != wrap_reachable) { + fprintf(stderr, "ERROR: %s L%d circlog nents=%zu disagrees with reachable=%zu after wrap (filled=%zu).\n", + __FILE__, __LINE__, circlog_nents, wrap_reachable, wrap_filled); + exit(1); + } + + fprintf(stderr, "circlog wrap-eviction check OK: nents=%zu reachable=%zu\n", circlog_nents, wrap_reachable); + } + } #endif /* CIRCLOG_UNIT_TEST */ log_server_init(); From 2b893398534f495a58feabc62074b2ce1eb4a404 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:48:11 -0700 Subject: [PATCH 070/255] F-4704 - https://fenrir.wolfssl.com/finding/4704 - Skip route dispatch at shutdown when no rule route was checked out --- examples/notification-demo/log_server/log_server.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index 3e7b1d6f..96d7bca6 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -1962,7 +1962,8 @@ int main(int argc, char **argv) { } #endif - if (! (wolfsentry_data->action_results & WOLFSENTRY_ACTION_RES_FALLTHROUGH)) { + if ((! (wolfsentry_data->action_results & WOLFSENTRY_ACTION_RES_FALLTHROUGH)) && + (wolfsentry_data->rule_route != NULL)) { wolfsentry_action_res_t action_results; if (transaction_successful) { ret = wolfsentry_route_event_dispatch_by_route(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(global_wolfsentry, global_thread), From 5ae6bd3ac56829da2e31ab5ae1753af960ab7664 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:48:30 -0700 Subject: [PATCH 071/255] F-4821 - https://fenrir.wolfssl.com/finding/4821 - Reject circlog-size values that overflow size_t before allocating --- examples/notification-demo/log_server/log_server.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index 96d7bca6..6d92ace5 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -1467,6 +1467,12 @@ int main(int argc, char **argv) { exit(1); } + if (circlog_size_uint64 > (uint64_t)SIZE_MAX) { + fprintf(stderr, "ERROR: \"circlog-size\" value %llu exceeds SIZE_MAX on this ABI\n", + (unsigned long long)circlog_size_uint64); + exit(1); + } + ret = circlog_init((size_t)circlog_size_uint64); if (ret < 0) { fprintf(stderr, "ERROR: circlog_init() failed: " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From 9709d0538cb31fa8a872243ab26435e6b364f236 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:48:54 -0700 Subject: [PATCH 072/255] F-5073 - https://fenrir.wolfssl.com/finding/5073 - Stop truncating the config file size returned by ftell --- examples/notification-demo/log_server/log_server.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/notification-demo/log_server/log_server.c b/examples/notification-demo/log_server/log_server.c index 6d92ace5..2c3a3dc2 100644 --- a/examples/notification-demo/log_server/log_server.c +++ b/examples/notification-demo/log_server/log_server.c @@ -1170,6 +1170,7 @@ static int http_write_header(WOLFSSL *ssl, char *http_buf, size_t http_buf_size, #ifndef NO_FILESYSTEM /* reads file size, allocates buffer, reads into buffer, returns buffer */ #include +#define LOAD_FILE_MAX_SZ (64 * 1024 * 1024) static int load_file(const char* fname, unsigned char** buf, size_t* bufLen) { int ret; @@ -1192,9 +1193,9 @@ static int load_file(const char* fname, unsigned char** buf, size_t* bufLen) } fseek(lFile, 0, SEEK_END); - fileSz = (int)ftell(lFile); + fileSz = ftell(lFile); rewind(lFile); - if (fileSz > 0) { + if ((fileSz > 0) && (fileSz <= (long int)LOAD_FILE_MAX_SZ)) { *bufLen = (size_t)fileSz; *buf = (unsigned char*)malloc(*bufLen); if (*buf == NULL) { From e017545134f6a104c88ff05cc7d357018331f8b3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:49:06 -0700 Subject: [PATCH 073/255] F-5008 - https://fenrir.wolfssl.com/finding/5008 - Check buffer space before writing the decision list separator --- examples/notification-demo/log_server/sentry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/notification-demo/log_server/sentry.c b/examples/notification-demo/log_server/sentry.c index e415539c..22eea810 100644 --- a/examples/notification-demo/log_server/sentry.c +++ b/examples/notification-demo/log_server/sentry.c @@ -551,10 +551,10 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( if ((res_string = wolfsentry_action_res_assoc_by_flag(*action_results, res_bit)) != NULL) { ++n_res_bits; if (n_res_bits > 1) { + if (msgbuf_space_left <= 0) + WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); *msgbuf_ptr++ = ','; --msgbuf_space_left; - if (msgbuf_space_left < 0) - WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); } msgbuf_len = snprintf(msgbuf_ptr, (size_t)msgbuf_space_left, "\"%s\"", res_string); msgbuf_space_left -= msgbuf_len; From 06902a5984ff4588b1d579783233b5bbe7f03a98 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 21 Oct 2026 14:50:00 -0700 Subject: [PATCH 074/255] F-4763 - https://fenrir.wolfssl.com/finding/4763 - Escape event and address-family labels written into the circular log JSON --- .../notification-demo/log_server/sentry.c | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/examples/notification-demo/log_server/sentry.c b/examples/notification-demo/log_server/sentry.c index 22eea810..e6c229ae 100644 --- a/examples/notification-demo/log_server/sentry.c +++ b/examples/notification-demo/log_server/sentry.c @@ -320,6 +320,39 @@ static int wolfSentry_NetworkFilterCallback( } +/* labels come from loaded config; escape them so they cannot break the JSON record. */ +static const char *json_escape_label(const char *in, char *out, size_t outsz) { + size_t i, o; + unsigned char c; + + if (in == NULL) + return ""; + if (outsz == 0) + return ""; + o = 0; + for (i = 0; in[i] != '\0'; ++i) { + c = (unsigned char)in[i]; + if ((c == '"') || (c == '\\')) { + if ((o + 2) >= outsz) + break; + out[o++] = '\\'; + out[o++] = (char)c; + } + else if ((c < 0x20) || (c == 0x7f)) { + if ((o + 1) >= outsz) + break; + out[o++] = '?'; + } + else { + if ((o + 1) >= outsz) + break; + out[o++] = (char)c; + } + } + out[o] = '\0'; + return out; +} + static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_action *action, @@ -353,6 +386,9 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( char msgbuf[1024], *msgbuf_ptr = msgbuf; int msgbuf_space_left = (int)sizeof msgbuf; int msgbuf_len; + char trigger_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; + char parent_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; + char family_name_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; (void)handler_arg; (void)route_table; @@ -452,11 +488,11 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( "{ \"timestamp\" : \"%s\", \"action\" : \"%s\", \"trigger\" : %s%s%s, \"parent\" : %s%s%s, \"rule-id\" : " WOLFSENTRY_ENT_ID_FMT ", \"rule-hitcount\" : " WOLFSENTRY_HITCOUNT_FMT ", \"af\" : \"%s\", \"proto\" : %d, \"remote\" : { \"address\" : \"", timebuf, wolfsentry_action_get_label(action), - trigger_event ? "\"" : "", trigger_event ? wolfsentry_event_get_label(trigger_event) : "null", trigger_event ? "\"" : "", - trigger_route_exports.parent_event_label ? "\"" : "", trigger_route_exports.parent_event_label ? trigger_route_exports.parent_event_label : "null", trigger_route_exports.parent_event_label ? "\"" : "", + trigger_event ? "\"" : "", trigger_event ? json_escape_label(wolfsentry_event_get_label(trigger_event), trigger_label_buf, sizeof trigger_label_buf) : "null", trigger_event ? "\"" : "", + trigger_route_exports.parent_event_label ? "\"" : "", trigger_route_exports.parent_event_label ? json_escape_label(trigger_route_exports.parent_event_label, parent_label_buf, sizeof parent_label_buf) : "null", trigger_route_exports.parent_event_label ? "\"" : "", wolfsentry_get_object_id(rule_route), rule_route_exports.meta.hit_count, - family_name, + json_escape_label(family_name, family_name_buf, sizeof family_name_buf), trigger_route_exports.sa_proto); msgbuf_space_left -= msgbuf_len; From c6d23756fdd9f1a3f895a5da0841319e9d12730b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:09:36 -0700 Subject: [PATCH 075/255] F-4763 - https://fenrir.wolfssl.com/finding/4763 - Escape the action label written into the circular log JSON --- examples/notification-demo/log_server/sentry.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/notification-demo/log_server/sentry.c b/examples/notification-demo/log_server/sentry.c index e6c229ae..ff3a23b2 100644 --- a/examples/notification-demo/log_server/sentry.c +++ b/examples/notification-demo/log_server/sentry.c @@ -386,6 +386,7 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( char msgbuf[1024], *msgbuf_ptr = msgbuf; int msgbuf_space_left = (int)sizeof msgbuf; int msgbuf_len; + char action_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; char trigger_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; char parent_label_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; char family_name_buf[(WOLFSENTRY_MAX_LABEL_BYTES * 2) + 1]; @@ -487,7 +488,7 @@ static wolfsentry_errcode_t wolfsentry_notify_via_UDP_JSON( (size_t)msgbuf_space_left, "{ \"timestamp\" : \"%s\", \"action\" : \"%s\", \"trigger\" : %s%s%s, \"parent\" : %s%s%s, \"rule-id\" : " WOLFSENTRY_ENT_ID_FMT ", \"rule-hitcount\" : " WOLFSENTRY_HITCOUNT_FMT ", \"af\" : \"%s\", \"proto\" : %d, \"remote\" : { \"address\" : \"", timebuf, - wolfsentry_action_get_label(action), + json_escape_label(wolfsentry_action_get_label(action), action_label_buf, sizeof action_label_buf), trigger_event ? "\"" : "", trigger_event ? json_escape_label(wolfsentry_event_get_label(trigger_event), trigger_label_buf, sizeof trigger_label_buf) : "null", trigger_event ? "\"" : "", trigger_route_exports.parent_event_label ? "\"" : "", trigger_route_exports.parent_event_label ? json_escape_label(trigger_route_exports.parent_event_label, parent_label_buf, sizeof parent_label_buf) : "null", trigger_route_exports.parent_event_label ? "\"" : "", wolfsentry_get_object_id(rule_route), From 768c31087618437d6bfaaaf9130f42fbfd162f6a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:42:32 -0700 Subject: [PATCH 076/255] F-15465 - https://fenrir.wolfssl.com/finding/15465 - Terminate route lookup after one jump into the wildcard-AF span --- src/routes.c | 8 ++++ tests/unittests.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/routes.c b/src/routes.c index c9a13acb..5213d6c6 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1647,6 +1647,7 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( int contiguous_search; wolfsentry_route_flags_t inexact_matches_buf; int seen_target_af; + int af_wildcard_span_entered; #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING int af_is_bitmask_matched; #endif @@ -1803,6 +1804,7 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( #endif seen_target_af = 0; + af_wildcard_span_entered = 0; for (i = (struct wolfsentry_route *)wolfsentry_table_cursor_current(&cursor); i; @@ -1812,9 +1814,15 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( if (! seen_target_af) seen_target_af = 1; } else { + /* jump to the wildcard-AF span at most once; the span's own + * members never match the target AF and would otherwise re-trigger + * the jump forever. + */ if (seen_target_af && + (! af_wildcard_span_entered) && (! (target_route->flags & WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD))) { + af_wildcard_span_entered = 1; if (table->last_af_wildcard_route) { i = table->last_af_wildcard_route; wolfsentry_table_cursor_set(&cursor, &i->header); diff --git a/tests/unittests.c b/tests/unittests.c index d0415ad0..8c510032 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -2947,6 +2947,120 @@ static int test_static_routes(void) { WOLFSENTRY_RETURN_OK; } +struct regression_sockaddr_v4 { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; +}; + +static void regression_sockaddr_v4_init(struct regression_sockaddr_v4 *sa, const byte *addr, wolfsentry_port_t port, wolfsentry_proto_t proto, byte interface) { + memset(sa, 0, sizeof *sa); + sa->sa.sa_family = AF_INET; + sa->sa.sa_proto = proto; + sa->sa.sa_port = port; + sa->sa.addr_len = sizeof sa->addr_buf * BITS_PER_BYTE; + sa->sa.interface = interface; + memcpy(sa->sa.addr, addr, sizeof sa->addr_buf); +} + +#define REGRESSION_AF_WILDCARD_FLAGS (WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD | \ + WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD) + +static int test_route_lookup_af_wildcard_span_terminates(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_inet, id_wild_in, id_wild_out, route_id; + struct regression_sockaddr_v4 remote, local, remote_wild, local_wild; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_inet, &action_results)); + + memset(&remote_wild, 0, sizeof remote_wild); + memset(&local_wild, 0, sizeof local_wild); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &id_wild_in, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + NULL /* event_label */, 0 /* event_label_len */, + &id_wild_out, &action_results)); + + /* a port with no exact match forces the full reverse scan through the + * wildcard-AF span. + */ + remote.sa.sa_port = 54321; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_wild_in); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(inexact_matches, WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD)); + + remote.sa.sa_port = 12345; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_inet); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_wild_out, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_wild_in, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_inet, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8330,6 +8444,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_static_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_lookup_af_wildcard_span_terminates(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_af_wildcard_span_terminates failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From 801fe1ec68fee9ce200e44683a3e7a9d533492b1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:44:24 -0700 Subject: [PATCH 077/255] F-11598 - https://fenrir.wolfssl.com/finding/11598 - Honor the penalty-box exclusion on the exact-match lookup fast path --- src/routes.c | 13 ++++++-- tests/unittests.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/routes.c b/src/routes.c index 5213d6c6..469ff521 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1741,9 +1741,16 @@ static wolfsentry_errcode_t wolfsentry_route_lookup_0( */ if (cursor_position == 0) { struct wolfsentry_event *parent_event = ((struct wolfsentry_route *)cursor.point)->parent_event ? ((struct wolfsentry_route *)cursor.point)->parent_event : NULL; - if ((action_results == NULL) || (parent_event == NULL) || (parent_event->config == NULL) || - (((*action_results & parent_event->config->config.action_res_filter_bits_set) == parent_event->config->config.action_res_filter_bits_set) && - ((~(*action_results) & parent_event->config->config.action_res_filter_bits_unset) == parent_event->config->config.action_res_filter_bits_unset))) + /* PENALTYBOXED is mutable and not part of the key, so an exact hit + * must still honor the bind/listen exclusion applied by the full scan. + */ + int excluded_reject_route = (action_results != NULL) && + WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES) && + WOLFSENTRY_MASKIN_BITS(((struct wolfsentry_route *)cursor.point)->flags, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED); + if ((! excluded_reject_route) && + ((action_results == NULL) || (parent_event == NULL) || (parent_event->config == NULL) || + (((*action_results & parent_event->config->config.action_res_filter_bits_set) == parent_event->config->config.action_res_filter_bits_set) && + ((~(*action_results) & parent_event->config->config.action_res_filter_bits_unset) == parent_event->config->config.action_res_filter_bits_unset)))) { int effective_priority = parent_event ? parent_event->priority : 0; if (effective_priority <= table->highest_priority_route_in_table) { diff --git a/tests/unittests.c b/tests/unittests.c index 8c510032..294c078f 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3061,6 +3061,78 @@ static int test_route_lookup_af_wildcard_span_terminates(void) { WOLFSENTRY_RETURN_OK; } +static int test_route_lookup_exact_match_honors_exclude_reject(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_pbox, route_id; + struct regression_sockaddr_v4 remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + + /* the parent-event wildcard makes the route key identical to the lookup + * target, so the exact-seek short circuit is the path under test. + */ + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_pbox, &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_pbox); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_pbox); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_pbox, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8449,6 +8521,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_lookup_af_wildcard_span_terminates failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_lookup_exact_match_honors_exclude_reject(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_exact_match_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From 8682a06beb6c5a24d0332fb13cc5c9938bf97e3a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:45:08 -0700 Subject: [PATCH 078/255] F-2460 - https://fenrir.wolfssl.com/finding/2460 - Test route lookup filtering on action_res_filter_bits_unset --- tests/unittests.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 294c078f..17310e99 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3133,6 +3133,89 @@ static int test_route_lookup_exact_match_honors_exclude_reject(void) { WOLFSENTRY_RETURN_OK; } +static int test_route_lookup_action_res_filter_bits_unset(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig event_config; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t event_id, id_filtered, route_id; + struct regression_sockaddr_v4 remote, local; + static const char event_label[] = "filter-bits-unset-test"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &event_config)); + event_config.action_res_filter_bits_unset = WOLFSENTRY_ACTION_RES_USER2; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + &event_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &event_id)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id_filtered, &action_results)); + + /* the forbidden bit is present, so the route must be skipped. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_USER2); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_filtered); + + /* a different user bit is not forbidden, so the route must match. */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_USER1); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_filtered); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_filtered, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8526,6 +8609,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_lookup_exact_match_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_lookup_action_res_filter_bits_unset(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_action_res_filter_bits_unset failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From 3083976a6a0584ef2ec206f6deb723386647725e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:45:53 -0700 Subject: [PATCH 079/255] F-2462 - https://fenrir.wolfssl.com/finding/2462 - Test penalty-box exclusion on the full route lookup scan --- tests/unittests.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 17310e99..b80a2152 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3216,6 +3216,75 @@ static int test_route_lookup_action_res_filter_bits_unset(void) { WOLFSENTRY_RETURN_OK; } +static int test_route_lookup_full_scan_honors_exclude_reject(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id_pbox, route_id; + struct regression_sockaddr_v4 remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\0", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + /* a /24 rule never seeks to an exact hit for a /32 target, so the reverse + * scan is the path under test. + */ + remote.sa.addr_len = 24; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, + NULL /* event_label */, 0 /* event_label_len */, + &id_pbox, &action_results)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_EXCLUDE_REJECT_ROUTES); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(route_id == id_pbox); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id_pbox); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_pbox, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8614,6 +8683,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_lookup_action_res_filter_bits_unset failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_lookup_full_scan_honors_exclude_reject(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_lookup_full_scan_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From 58815e717da86c1d3a989df78ffde3f4a97b0fba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:48:54 -0700 Subject: [PATCH 080/255] F-6261 - https://fenrir.wolfssl.com/finding/6261 - Return before auxiliary route bookkeeping when insert actions fail --- src/routes.c | 5 ++ tests/unittests.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/src/routes.c b/src/routes.c index 469ff521..a320cad2 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1320,6 +1320,11 @@ static wolfsentry_errcode_t wolfsentry_route_insert_1( wolfsentry_route_flags_t flags_before, flags_after; WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &route_to_insert->header)); wolfsentry_route_update_flags_1(route_to_insert, WOLFSENTRY_ROUTE_FLAG_NONE, WOLFSENTRY_ROUTE_FLAG_IN_TABLE, &flags_before, &flags_after); + WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_INSERTED); + /* the caller drops the last reference on failure, so the route + * must not be linked into the purge list or wildcard bookkeeping. + */ + WOLFSENTRY_ERROR_RERETURN(ret); } } else { if (route_to_insert->parent_event) { diff --git a/tests/unittests.c b/tests/unittests.c index b80a2152..cdf72f5a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3285,6 +3285,146 @@ static int test_route_lookup_full_scan_honors_exclude_reject(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t regression_failing_insert_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_context; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + WOLFSENTRY_ERROR_RETURN(NOT_PERMITTED); +} + +static int test_route_insert_action_failure_detaches_route(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig event_config; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t id, route_id; + wolfsentry_hitcount_t n_ents_before, purge_len_before; + const struct wolfsentry_route *last_af_wildcard_before; + wolfsentry_priority_t highest_priority_before; + struct regression_sockaddr_v4 remote, local, remote_wild, local_wild; + static const char event_label[] = "failing-insert-parent"; + static const char action_label[] = "fail-on-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + regression_failing_insert_action, + NULL /* handler_context */, + &id)); + + /* a purge deadline puts the route on the purge list, which must be undone + * along with the wildcard-AF bookkeeping when the insert is rolled back. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &event_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 3600, 0, &event_config.route_idle_time_for_purge)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + &event_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + n_ents_before = wolfsentry->routes->header.n_ents; + purge_len_before = wolfsentry->routes->purge_list.len; + last_af_wildcard_before = wolfsentry->routes->last_af_wildcard_route; + highest_priority_before = wolfsentry->routes->highest_priority_route_in_table; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + memset(&remote_wild, 0, sizeof remote_wild); + memset(&local_wild, 0, sizeof local_wild); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_wild.sa, &local_wild.sa, + REGRESSION_AF_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->header.n_ents == n_ents_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->purge_list.len == purge_len_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->last_af_wildcard_route == last_af_wildcard_before); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->highest_priority_route_in_table == highest_priority_before); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + + /* the lookup and purge paths that consult the auxiliary structures must + * still work after the rollback. + */ + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + route_id = WOLFSENTRY_ENT_ID_NONE; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS( + USED_FALLBACK, + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8688,6 +8828,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_lookup_full_scan_honors_exclude_reject failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_insert_action_failure_detaches_route(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_action_failure_detaches_route failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From 5d9ad1bb803fe63525d19238226588cb2c9ce4b2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:50:07 -0700 Subject: [PATCH 081/255] F-6256 - https://fenrir.wolfssl.com/finding/6256 - Release the bitmask address-family reference when insert actions fail --- src/routes.c | 4 ++ tests/unittests.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/routes.c b/src/routes.c index a320cad2..24bb1e1c 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1320,6 +1320,10 @@ static wolfsentry_errcode_t wolfsentry_route_insert_1( wolfsentry_route_flags_t flags_before, flags_after; WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &route_to_insert->header)); wolfsentry_route_update_flags_1(route_to_insert, WOLFSENTRY_ROUTE_FLAG_NONE, WOLFSENTRY_ROUTE_FLAG_IN_TABLE, &flags_before, &flags_after); +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + if (route_to_insert->flags & (WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK | WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK)) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_bitmask_matching_downref(route_to_insert->sa_family, route_table)); +#endif WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_INSERTED); /* the caller drops the last reference on failure, so the route * must not be linked into the purge list or wildcard bookkeeping. diff --git a/tests/unittests.c b/tests/unittests.c index cdf72f5a..8b216770 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3425,6 +3425,99 @@ static int test_route_insert_action_failure_detaches_route(void) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static int test_route_insert_action_failure_releases_bitmask_af(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_addr_family_t n_bitmask_afs_before; + struct wolfsentry_route_exports exp_route; + static const byte exp_remote_addr_and_mask[8] = { 0xC0, 0xA8, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0x00 }; + static const char event_label[] = "failing-insert-bitmask-parent"; + static const char action_label[] = "fail-on-bitmask-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + regression_failing_insert_action, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + n_bitmask_afs_before = wolfsentry->routes->n_bitmask_matching_afs; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + memset(&exp_route, 0, sizeof exp_route); + exp_route.parent_event_label = event_label; + exp_route.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + exp_route.remote_address = exp_remote_addr_and_mask; + exp_route.remote.addr_len = sizeof exp_remote_addr_and_mask * BITS_PER_BYTE; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->routes->n_bitmask_matching_afs == n_bitmask_afs_before); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8833,6 +8926,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_insert_action_failure_detaches_route failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + ret = test_route_insert_action_failure_releases_bitmask_af(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_action_failure_releases_bitmask_af failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_DYNAMIC_RULES From 9818a4630484f16610197cdf4fab7876a7c87395 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:51:17 -0700 Subject: [PATCH 082/255] F-8768 - https://fenrir.wolfssl.com/finding/8768 - Mark the parent event on every successful route insert --- src/routes.c | 15 ++++-- tests/unittests.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/src/routes.c b/src/routes.c index 24bb1e1c..ebfaa29c 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1330,12 +1330,17 @@ static wolfsentry_errcode_t wolfsentry_route_insert_1( */ WOLFSENTRY_ERROR_RERETURN(ret); } - } else { - if (route_to_insert->parent_event) { - if (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) - WOLFSENTRY_SET_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); - } + } else ret = WOLFSENTRY_ERROR_ENCODE(OK); + + /* an aux event parents the ephemeral routes it generates, so it keeps its + * subevent role rather than taking on the mutually exclusive parent role. + */ + if ((route_to_insert->parent_event != NULL) && + (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) && + (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT))) + { + WOLFSENTRY_SET_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); } if (route_to_insert->meta.purge_after) diff --git a/tests/unittests.c b/tests/unittests.c index 8b216770..f71b1daf 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3518,6 +3518,123 @@ static int test_route_insert_action_failure_releases_bitmask_af(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static wolfsentry_errcode_t regression_noop_insert_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_context; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_insert_with_actions_marks_parent_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id, id_route; + struct regression_sockaddr_v4 remote, local; + static const char parent_label[] = "parent-with-insert-actions"; + static const char other_label[] = "would-be-aux-user"; + static const char action_label[] = "noop-on-insert"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + regression_noop_insert_action, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + parent_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + other_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + parent_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + action_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\0\1\2\3", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\377\376\375\374", 443, IPPROTO_TCP, 1); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id_route, &action_results)); + + /* an event that already parents a route can't become an aux event. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + other_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id_route, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, other_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, parent_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_action_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, action_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -8933,6 +9050,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_route_insert_with_actions_marks_parent_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_with_actions_marks_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From affc18091ae5654955656e813f3476df0a2f66c5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:54:55 -0700 Subject: [PATCH 083/255] F-3191 - https://fenrir.wolfssl.com/finding/3191 - Test rejection of route inserts with an expired purge deadline --- tests/unittests.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index f71b1daf..49362c7a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3635,6 +3635,75 @@ static int test_route_insert_with_actions_marks_parent_event(void) { WOLFSENTRY_RETURN_OK; } +static int test_route_insert_rejects_expired_purge_after(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_time_t now, one_hour; + struct wolfsentry_route_exports exp_route; + static const byte exp_remote_addr[4] = { 0, 1, 2, 3 }; + static const byte exp_local_addr[4] = { 255, 254, 253, 252 }; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&exp_route, 0, sizeof exp_route); + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + exp_route.remote_address = exp_remote_addr; + exp_route.remote.addr_len = sizeof exp_remote_addr * BITS_PER_BYTE; + exp_route.remote.sa_port = 12345; + exp_route.local_address = exp_local_addr; + exp_route.local.addr_len = sizeof exp_local_addr * BITS_PER_BYTE; + exp_route.local.sa_port = 443; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_time_now_plus_delta(wolfsentry, 0, &now)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 3600, 0, &one_hour)); + + /* a deadline at or before the insert time can never be served. */ + exp_route.meta.purge_after = now; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + exp_route.meta.purge_after = now - one_hour; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + exp_route.meta.purge_after = now + one_hour; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -9055,6 +9124,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_insert_with_actions_marks_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_insert_rejects_expired_purge_after(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_insert_rejects_expired_purge_after failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From d37e07195bf4bf204e7435f9d1cd741f6bcfef3a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:57:53 -0700 Subject: [PATCH 084/255] F-8773 - https://fenrir.wolfssl.com/finding/8773 - Size the address formatter from the space left after its JSON key --- src/routes.c | 12 ++- tests/unittests.c | 258 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+), 4 deletions(-) diff --git a/src/routes.c b/src/routes.c index ebfaa29c..cf93c962 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4212,8 +4212,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (have_r_attr(SA_REMOTE_ADDR)) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING if (r->flags & WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK) { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4240,8 +4241,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( } else #endif { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4280,8 +4282,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (have_r_attr(SA_LOCAL_ADDR)) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING if (r->flags & WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK) { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, @@ -4308,8 +4311,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( } else #endif { - int len_in_out = (int)*json_out_len; + int len_in_out; write_string("\"address\":\""); + len_in_out = (int)*json_out_len; ret = wolfsentry_route_format_address( WOLFSENTRY_CONTEXT_ARGS_OUT, r->sa_family, diff --git a/tests/unittests.c b/tests/unittests.c index 49362c7a..f63b019a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3704,6 +3704,257 @@ static int test_route_insert_rejects_expired_purge_after(void) { WOLFSENTRY_RETURN_OK; } +#if !defined(WOLFSENTRY_NO_JSON) || defined(WOLFSENTRY_JSON_DUMP_UTILS) + +/* renders a route to JSON through every output budget from empty up to the + * full length, checking that a short budget is reported rather than + * overrun. + */ +static int regression_route_format_json_sweep( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_route *route, + unsigned char *full_buf, + size_t full_buf_size, + size_t *full_len) +{ + static const unsigned char guard_byte = 0xA5; + unsigned char guard_buf[2048]; + unsigned char *json_out; + size_t json_out_len, n, i; + wolfsentry_errcode_t ret; + + json_out = full_buf; + json_out_len = full_buf_size; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE)); + *full_len = full_buf_size - json_out_len; + WOLFSENTRY_EXIT_ON_FALSE(*full_len > 0); + WOLFSENTRY_EXIT_ON_FALSE(*full_len + 512 < sizeof guard_buf); + + for (n = 0; n < *full_len; ++n) { + memset(guard_buf, guard_byte, sizeof guard_buf); + json_out = guard_buf; + json_out_len = n; + ret = wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(ret, BUFFER_TOO_SMALL)); + WOLFSENTRY_EXIT_ON_FALSE((size_t)(json_out - guard_buf) <= n); + WOLFSENTRY_EXIT_ON_FALSE(json_out_len <= n); + for (i = n; i < sizeof guard_buf; ++i) + WOLFSENTRY_EXIT_ON_FALSE(guard_buf[i] == guard_byte); + } + + memset(guard_buf, guard_byte, sizeof guard_buf); + json_out = guard_buf; + json_out_len = *full_len; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_format_json(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &json_out, &json_out_len, WOLFSENTRY_FORMAT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(json_out_len == 0); + WOLFSENTRY_EXIT_ON_FALSE(memcmp(guard_buf, full_buf, *full_len) == 0); + WOLFSENTRY_EXIT_ON_FALSE(guard_buf[*full_len] == guard_byte); + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_format_json_truncation_is_bounded(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len; + struct regression_sockaddr_v4 remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 443, IPPROTO_TCP, 2); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_format_json_escapes_parent_event_label(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len; + struct regression_sockaddr_v4 remote, local; + static const char event_label[] = "q\"b\\s\001c"; + static const char expected[] = "\"parent-event\":\"q\\\"b\\\\s\\u0001c\","; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event_label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 0 /* priority */, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 12345, IPPROTO_TCP, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 443, IPPROTO_TCP, 2); + flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &remote.sa, &local.sa, flags, + event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf - 1, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + full_buf[full_len] = 0; + WOLFSENTRY_EXIT_ON_FALSE(strstr((const char *)full_buf, expected) != NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, WOLFSENTRY_LENGTH_NULL_TERMINATED, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_route_format_json_unknown_protocol_numeric(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t flags; + wolfsentry_ent_id_t id; + unsigned char full_buf[1024]; + size_t full_len; + struct regression_sockaddr_v4 remote, local; + /* IANA reserves 253 for experimentation, so no protocols database + * resolves it to a name. + */ + static const wolfsentry_proto_t unknown_proto = 253; + static const char expected[] = "\"protocol\":253,"; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_sockaddr_v4_init(&remote, (const byte *)"\xC0\xA8\x01\x01", 0, unknown_proto, 1); + regression_sockaddr_v4_init(&local, (const byte *)"\12\0\0\1", 0, unknown_proto, 2); + flags = WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + main_routes, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + 1 /* exact_p */, + &inexact_matches, + &route)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_format_json_sweep(WOLFSENTRY_CONTEXT_ARGS_OUT, route, full_buf, sizeof full_buf - 1, &full_len)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + full_buf[full_len] = 0; + WOLFSENTRY_EXIT_ON_FALSE(strstr((const char *)full_buf, expected) != NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* event_label */, 0 /* event_label_len */, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* !WOLFSENTRY_NO_JSON || WOLFSENTRY_JSON_DUMP_UTILS */ + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -9129,6 +9380,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_insert_rejects_expired_purge_after failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#if !defined(WOLFSENTRY_NO_JSON) || defined(WOLFSENTRY_JSON_DUMP_UTILS) + ret = test_route_format_json_truncation_is_bounded(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_truncation_is_bounded failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_DYNAMIC_RULES From c32e53d9d2a0cb515072a291612e142d4f3a8218 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 08:59:35 -0700 Subject: [PATCH 085/255] F-2855 - https://fenrir.wolfssl.com/finding/2855 - Escape event labels and names when rendering routes to JSON --- src/routes.c | 41 +++++++++++++++++++++++++++++++---------- tests/unittests.c | 5 +++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/routes.c b/src/routes.c index cf93c962..0546d587 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4103,6 +4103,30 @@ static wolfsentry_errcode_t ws_itoa(int i, unsigned char **out, size_t *spc) { #define unwrite_byte() do { --(*json_out); ++(*json_out_len); } while (0) #define unwrite_bytes(l) do { size_t _l = (size_t)(l); (*json_out) -= _l; (*json_out_len) += _l; } while (0) +/* labels and names are arbitrary bytes, so they must be escaped to keep the + * output parseable and to keep them from injecting JSON structure. + */ +static wolfsentry_errcode_t write_json_string(unsigned char **json_out, size_t *json_out_len, const char *s, size_t s_len) { + static const char hexdigits[] = "0123456789abcdef"; + size_t i; + + write_byte('"'); + for (i = 0; i < s_len; ++i) { + unsigned char c = (unsigned char)s[i]; + if ((c == '"') || (c == '\\')) { + write_byte('\\'); + write_byte(c); + } else if (c < 0x20U) { + write_string("\\u00"); + write_byte((unsigned char)hexdigits[c >> 4U]); + write_byte((unsigned char)hexdigits[c & 0xfU]); + } else + write_byte(c); + } + write_byte('"'); + WOLFSENTRY_RETURN_OK; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_route *r, @@ -4114,9 +4138,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( write_byte('{'); if (r->parent_event) { - write_string("\"parent-event\":\""); - write_bytes(r->parent_event->label, r->parent_event->label_len); - write_string("\","); + write_string("\"parent-event\":"); + WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, r->parent_event->label, (size_t)r->parent_event->label_len)); + write_byte(','); } { const struct wolfsentry_route_flag_name_map_ent *i; @@ -4148,10 +4172,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( if (family_name_len + 2 > *json_out_len) ret = WOLFSENTRY_ERROR_ENCODE(BUFFER_TOO_SMALL); else { - write_byte('"'); - write_bytes(family_name, family_name_len); - write_byte('"'); - rendered_family = 1; + ret = write_json_string(json_out, json_out_len, family_name, family_name_len); + if (WOLFSENTRY_IS_SUCCESS(ret)) + rendered_family = 1; } if (addr_family) { wolfsentry_errcode_t drop_ret = wolfsentry_addr_family_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, addr_family, NULL /* action_results */ ); @@ -4189,9 +4212,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( get_buf, sizeof get_buf, &p) == 0) { - write_byte('"'); - write_string(protoent.p_name); - write_byte('"'); + WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, protoent.p_name, strlen(protoent.p_name))); rendered_proto = 1; } } diff --git a/tests/unittests.c b/tests/unittests.c index f63b019a..23e46bde 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9386,6 +9386,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_format_json_truncation_is_bounded failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_format_json_escapes_parent_event_label(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_escapes_parent_event_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #endif From 8b325c99ca1965c0a922675844788ce9dd0be7fa Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:01:32 -0700 Subject: [PATCH 086/255] F-8753 - https://fenrir.wolfssl.com/finding/8753 - Check the getprotobynumber_r result pointer before rendering a protocol name --- src/routes.c | 14 ++++++++------ tests/unittests.c | 39 ++++++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/routes.c b/src/routes.c index 0546d587..4cee7486 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4203,16 +4203,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_json( (r->sa_family == WOLFSENTRY_AF_INET6))) { char get_buf[256]; - struct protoent protoent, *p; + struct protoent protoent, *p = NULL; /* note this is glibc-specific and non-standard; other libc * implementations have same name but different args. + * an unknown number returns 0 with a null result pointer. */ - if (getprotobynumber_r(r->sa_proto, - &protoent, - get_buf, sizeof get_buf, - &p) == 0) + if ((getprotobynumber_r(r->sa_proto, + &protoent, + get_buf, sizeof get_buf, + &p) == 0) && + (p != NULL)) { - WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, protoent.p_name, strlen(protoent.p_name))); + WOLFSENTRY_RERETURN_IF_ERROR(write_json_string(json_out, json_out_len, p->p_name, strlen(p->p_name))); rendered_proto = 1; } } diff --git a/tests/unittests.c b/tests/unittests.c index 23e46bde..6342a0c3 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -3885,6 +3885,10 @@ static int test_route_format_json_escapes_parent_event_label(void) { WOLFSENTRY_RETURN_OK; } +#ifndef WOLFSENTRY_NO_GETPROTOBY +#include +#endif + static int test_route_format_json_unknown_protocol_numeric(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *main_routes; @@ -3894,16 +3898,36 @@ static int test_route_format_json_unknown_protocol_numeric(void) { wolfsentry_route_flags_t flags; wolfsentry_ent_id_t id; unsigned char full_buf[1024]; - size_t full_len; + size_t full_len, i; struct regression_sockaddr_v4 remote, local; - /* IANA reserves 253 for experimentation, so no protocols database - * resolves it to a name. + wolfsentry_proto_t unknown_proto = 0; + char expected[32]; + /* IANA experimental and unassigned numbers, checked against the host + * database at runtime because a local protocols file can name any of + * them. */ - static const wolfsentry_proto_t unknown_proto = 253; - static const char expected[] = "\"protocol\":253,"; + static const wolfsentry_proto_t candidates[] = { 253, 254, 143, 144, 145 }; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); +#ifndef WOLFSENTRY_NO_GETPROTOBY + for (i = 0; i < sizeof candidates / sizeof candidates[0]; ++i) { + if (getprotobynumber((int)candidates[i]) == NULL) { + unknown_proto = candidates[i]; + break; + } + } + if (unknown_proto == 0) { + printf("skipping unknown-protocol JSON check: host protocol database names every candidate.\n"); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; + } +#else + (void)i; + unknown_proto = candidates[0]; +#endif + WOLFSENTRY_EXIT_ON_FALSE(snprintf(expected, sizeof expected, "\"protocol\":%u,", (unsigned int)unknown_proto) > 0); + WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_init_ex( wolfsentry_build_settings, @@ -9391,6 +9415,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_route_format_json_escapes_parent_event_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_route_format_json_unknown_protocol_numeric(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_route_format_json_unknown_protocol_numeric failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #endif From 47541cd8b20062fe1392c19c9b053bea1be1486e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:28:59 -0700 Subject: [PATCH 087/255] F-10487 - https://fenrir.wolfssl.com/finding/10487 - Make the AUTO_DOWNGRADE unlock path reachable and consistent --- src/wolfsentry_util.c | 29 ++++++++++++----- tests/unittests.c | 75 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 6ecc1e78..faa55390 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3018,9 +3018,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl WOLFSENTRY_THREAD_ASSERT_NULL_OR_INITED(thread); - /* unlocking a recursive mutex, like recursively locking one, can be done lock-free. */ + /* unlocking a recursive mutex, like recursively locking one, can be done + * lock-free, except when unwinding to the promotion depth, which must + * evaluate the downgrade logic below. + */ if ((WOLFSENTRY_ATOMIC_LOAD(lock->write_lock_holder) == WOLFSENTRY_THREAD_GET_ID) && - (lock->holder_count.write > 1)) + (lock->holder_count.write > 1) && + (lock->holder_count.write - 1 != lock->promoted_at_count)) { --lock->holder_count.write; if (thread) @@ -3127,16 +3131,25 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl if (lock->promoted_at_count == lock->holder_count.write) { lock->promoted_at_count = 0; if ((flags & WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE) && (! thread->tracked_shared_lock)) { - WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + /* same bookkeeping as wolfsentry_lock_mutex2shared(): writer count becomes reader count. */ if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { + if (lock->read2write_reservation_holder != WOLFSENTRY_THREAD_NO_ID) /* can't happen, but be sure. */ { + ret = WOLFSENTRY_ERROR_ENCODE(INTERNAL_CHECK_FATAL); + goto out; + } + ++thread->mutex_and_reservation_count; WOLFSENTRY_ATOMIC_STORE(lock->read2write_reservation_holder, WOLFSENTRY_THREAD_GET_ID); + lock->read2write_waiter_read_count = lock->holder_count.write; /* note, not incrementing write_waiter_count, to allow shared lockers to get locks until the redemption phase. */ - lock->read2write_waiter_read_count = lock->holder_count.read; + } + WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); + WOLFSENTRY_ATOMIC_STORE(lock->write_lock_holder, WOLFSENTRY_THREAD_NO_ID); + thread->mutex_and_reservation_count -= lock->holder_count.write; + thread->shared_count += lock->holder_count.read; + thread->recursion_of_tracked_lock = lock->holder_count.read; + thread->tracked_shared_lock = lock; + if (flags & (WOLFSENTRY_LOCK_FLAG_TRY_RESERVATION_TOO | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)) { ++lock->holder_count.read; /* suppress posts to sem_read2write_waiters until wolfsentry_lock_shared2mutex_redeem() is entered. */ - thread->mutex_and_reservation_count -= lock->holder_count.write - 1; /* -1 for the reservation */ - thread->shared_count += lock->holder_count.read; - thread->tracked_shared_lock = lock; - thread->recursion_of_tracked_lock = lock->holder_count.read; ret = WOLFSENTRY_SUCCESS_ENCODE(LOCK_OK_AND_GOT_RESV); } else ret = WOLFSENTRY_ERROR_ENCODE(OK); diff --git a/tests/unittests.c b/tests/unittests.c index 6342a0c3..693ef00a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1392,6 +1392,76 @@ TEST_SKIP(test_rw_locks) #endif /* WOLFSENTRY_THREADSAFE */ +#if defined(WOLFSENTRY_THREADSAFE) + +static int test_rw_locks_auto_downgrade(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* promote through a redeemed reservation, which records the promotion depth. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* unwinding to the promotion depth with AUTO_DOWNGRADE must leave a shared lock. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(LACKING_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(thread->shared_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(thread->mutex_and_reservation_count == 0); + WOLFSENTRY_EXIT_ON_FALSE(thread->tracked_shared_lock == lock); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_destroy(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* same again, but downgrade with a new reservation and redeem it. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_init(wolfsentry_get_hpi(wolfsentry), thread, lock, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_AUTO_DOWNGRADE | WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_READ_LOCK, wolfsentry_lock_have_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_have_shared2mutex_reservation(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* unwinding past the promotion depth without AUTO_DOWNGRADE stays exclusive. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(LOCK_OK_AND_GOT_RESV, wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_GET_RESERVATION_TOO)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared2mutex_redeem(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_SUCCESS(HAVE_MUTEX, wolfsentry_lock_have_mutex(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#else + +TEST_SKIP(test_rw_locks_auto_downgrade) + +#endif /* WOLFSENTRY_THREADSAFE */ + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9354,6 +9424,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_rw_locks failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_rw_locks_auto_downgrade(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_auto_downgrade failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From 23d5219ce072ebfaf265791b60b87831269ed51b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:28:59 -0700 Subject: [PATCH 088/255] F-11600 - https://fenrir.wolfssl.com/finding/11600 - Post one reader wakeup per queued reader on unlock --- src/wolfsentry_util.c | 7 +-- tests/unittests.c | 101 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index faa55390..0967561c 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3180,11 +3180,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_unlock(struct wolfsentry_rwl } } } else if (lock->read_waiter_count > 0) { - int i; - lock->holder_count.read += lock->read_waiter_count; + /* post once per queued reader, not per holder, as existing shared holders need no wakeup. */ + int read_waiter_count = lock->read_waiter_count; + lock->holder_count.read += read_waiter_count; lock->read_waiter_count = 0; WOLFSENTRY_ATOMIC_STORE(lock->state, WOLFSENTRY_LOCK_SHARED); - for (i = 0; i < lock->holder_count.read; ++i) { + for (; read_waiter_count > 0; --read_waiter_count) { if (sem_post(&lock->sem_read_waiters) < 0) { ret = WOLFSENTRY_ERROR_ENCODE(SYS_OP_FATAL); goto out; diff --git a/tests/unittests.c b/tests/unittests.c index 693ef00a..5bcaf184 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1456,9 +1456,105 @@ static int test_rw_locks_auto_downgrade(void) { WOLFSENTRY_RETURN_OK; } +static void *wr_timeout_routine(struct rwlock_args *args) { + WOLFSENTRY_THREAD_HEADER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_GET_ERROR); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(TIMED_OUT, wolfsentry_lock_mutex_timed(args->lock, thread, args->max_wait, WOLFSENTRY_LOCK_FLAG_NONE)); + INCREMENT_PHASE(args); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + return 0; +} + +static int test_rw_locks_reader_wakeup(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_rwlock *lock; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + volatile int measured_sequence[8], measured_sequence_i = 0; + pthread_t reader_thread, writer_thread; + struct rwlock_args reader_args, writer_args; + int reader_phase; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + (void)alarm(2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_alloc(wolfsentry_get_hpi(wolfsentry), thread, &lock, WOLFSENTRY_LOCK_FLAG_NONE)); + + memset(&reader_args, 0, sizeof reader_args); + reader_args.wolfsentry = wolfsentry; + reader_args.measured_sequence = measured_sequence; + reader_args.measured_sequence_i = &measured_sequence_i; + reader_args.lock = lock; + writer_args = reader_args; + reader_args.thread_id = 1; + reader_args.max_wait = -1; + writer_args.thread_id = 2; + writer_args.max_wait = MAX_WAIT; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&reader_args.thread_phase_lock, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_init(&writer_args.thread_phase_lock, NULL)); + + /* hold the lock shared twice, so the first unlock leaves a shared holder in place. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_shared(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a timed writer queues, which forces the subsequent reader to queue behind it. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&writer_thread, 0 /* attr */, (void *(*)(void *))wr_timeout_routine, (void *)&writer_args)); + WAIT_FOR_PHASE(writer_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(writer_thread, 0)); + usleep(10000); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_create(&reader_thread, 0 /* attr */, (void *(*)(void *))rd_routine, (void *)&reader_args)); + WAIT_FOR_PHASE(reader_args, 1); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_kill(reader_thread, 0)); + usleep(10000); + + /* the writer times out and dequeues itself, leaving the reader queued. */ + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(writer_thread, 0 /* retval */)); + + /* the reader must still be parked, not already holding the lock, else the scenario was not reached. */ + usleep(10000); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_lock(&reader_args.thread_phase_lock)); + reader_phase = reader_args.thread_phase; + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_unlock(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FALSE(reader_phase == 1); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 0); + + /* this unlock releases the queued reader, and must post exactly one wakeup. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_join(reader_thread, 0 /* retval */)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(measured_sequence_i) == 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_unlock(lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + /* a surplus wakeup token is detected as an inconsistency at destroy time. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_lock_free(&lock, thread, WOLFSENTRY_LOCK_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&reader_args.thread_phase_lock)); + WOLFSENTRY_EXIT_ON_FAILURE_PTHREAD(pthread_mutex_destroy(&writer_args.thread_phase_lock)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + + (void)alarm(0); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #else TEST_SKIP(test_rw_locks_auto_downgrade) +TEST_SKIP(test_rw_locks_reader_wakeup) #endif /* WOLFSENTRY_THREADSAFE */ @@ -9429,6 +9525,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_rw_locks_auto_downgrade failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_rw_locks_reader_wakeup(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_rw_locks_reader_wakeup failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From 41e2942c248e2506168efb1a087269098b478b44 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:28:59 -0700 Subject: [PATCH 089/255] F-8775 - https://fenrir.wolfssl.com/finding/8775 - Reject table-less objects in wolfsentry_object_release instead of dereferencing NULL --- src/wolfsentry_util.c | 17 +++++++++-- tests/unittests.c | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 0967561c..91740cdd 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3585,9 +3585,22 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_checkout(WOLFSENTRY_CONTEX } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_IN, void *object, wolfsentry_action_res_t *action_results) { - if (object == NULL) + struct wolfsentry_table_ent_header *ent = (struct wolfsentry_table_ent_header *)object; + if (ent == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (ent->parent_table == NULL) { + /* the fallthrough route is never linked into a table, but is documented as releasable here. */ + if ((wolfsentry->routes != NULL) && + (wolfsentry->routes->fallthrough_route != NULL) && + (object == (void *)wolfsentry->routes->fallthrough_route)) + { + WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry->routes->fallthrough_route, action_results)); + } + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + } + if (ent->parent_table->free_fn == NULL) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); - WOLFSENTRY_ERROR_RERETURN(((struct wolfsentry_table_ent_header *)object)->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_table_ent_header *)object, action_results)); + WOLFSENTRY_ERROR_RERETURN(ent->parent_table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, ent, action_results)); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_init( diff --git a/tests/unittests.c b/tests/unittests.c index 5bcaf184..c05dc565 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1558,6 +1558,66 @@ TEST_SKIP(test_rw_locks_reader_wakeup) #endif /* WOLFSENTRY_THREADSAFE */ +static int test_object_release_tableless(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + const struct wolfsentry_route *fallthrough_route; + struct wolfsentry_route *route_ref = NULL; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* the fallthrough route is never linked into a table, but is documented as releasable. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(fallthrough_route) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, (void *)fallthrough_route, NULL /* action_results */)); + + /* a checked-out route that has since been deleted is table-less too, and must be rejected, not dereferenced. */ + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr,"\0\1\2\3",sizeof remote.addr_buf); + memcpy(local.sa.addr,"\377\376\375\374",sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_and_check_out(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &remote.sa, &local.sa, flags, 0 /* event_label */, 0 /* event_label_len */, &route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_ROUTE); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, wolfsentry_get_object_id(route_ref), 0 /* trigger_label */, 0 /* trigger_label_len */, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry_get_object_type(route_ref) == WOLFSENTRY_OBJECT_TYPE_UNINITED); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, route_ref, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9530,6 +9590,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_rw_locks_reader_wakeup failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_object_release_tableless(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_object_release_tableless failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From bd5d6c7235b65a7d71ad87e64609a7c373084237 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:28:59 -0700 Subject: [PATCH 090/255] F-8765 - https://fenrir.wolfssl.com/finding/8765 - Implement timed and try semantics in the ThreadX semaphore shims --- src/wolfsentry_util.c | 54 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 91740cdd..87af3736 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -1538,16 +1538,47 @@ static int freertos_sem_destroy( sem_t * sem ) } WOLFSENTRY_RETURN_VALUE(0); } + #ifndef EAGAIN + #define EAGAIN 11 /* Try again */ + #endif + #ifndef EINTR + #define EINTR 4 /* Interrupted system call */ + #endif + #define sem_timedwait threadx_sem_timedwait static int threadx_sem_timedwait( sem_t * sem, const struct timespec * abstime ) { - if (tx_semaphore_get(sem, (uint32_t)(TX_TICK_TIME_MS * - (abstime->tv_sec * 1000) + (abstime->tv_nsec / 1000000))) != TX_SUCCESS) { - errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); + ULONG wait_ticks = TX_WAIT_FOREVER; + UINT tx_ret; + + if (abstime != NULL) { + uint64_t deadline_ticks, now_ticks; + if ((abstime->tv_sec < 0) || (abstime->tv_nsec < 0) || (abstime->tv_nsec >= 1000000000L)) { + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); + } + /* abstime is on the builtin get_time clock, so invert that tick mapping, rounding up so a sub-tick future deadline still waits a tick. */ + deadline_ticks = ((uint64_t)abstime->tv_sec * (uint64_t)TX_TICK_TIME_MS) + (((uint64_t)abstime->tv_nsec + (uint64_t)999999) / (uint64_t)1000000); + now_ticks = (uint64_t)tx_time_get(); + if (deadline_ticks <= now_ticks) + wait_ticks = TX_NO_WAIT; + else if (deadline_ticks - now_ticks >= (uint64_t)TX_WAIT_FOREVER) + wait_ticks = (ULONG)(TX_WAIT_FOREVER - 1); + else + wait_ticks = (ULONG)(deadline_ticks - now_ticks); } - WOLFSENTRY_RETURN_VALUE(0); + + tx_ret = tx_semaphore_get(sem, wait_ticks); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = ETIMEDOUT; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else + errno = EINVAL; + WOLFSENTRY_RETURN_VALUE(-1); } #define sem_wait threadx_sem_wait static int threadx_sem_wait( sem_t * sem ) @@ -1561,11 +1592,16 @@ static int freertos_sem_destroy( sem_t * sem ) #define sem_trywait threadx_sem_trywait static int threadx_sem_trywait( sem_t * sem ) { - if (tx_semaphore_get(sem, TX_NO_WAIT) != TX_SUCCESS) { + UINT tx_ret = tx_semaphore_get(sem, TX_NO_WAIT); + if (tx_ret == TX_SUCCESS) + WOLFSENTRY_RETURN_VALUE(0); + if (tx_ret == TX_NO_INSTANCE) + errno = EAGAIN; + else if (tx_ret == TX_WAIT_ABORTED) + errno = EINTR; + else errno = EINVAL; - WOLFSENTRY_RETURN_VALUE(-1); - } - WOLFSENTRY_RETURN_VALUE(0); + WOLFSENTRY_RETURN_VALUE(-1); } static int threadx_sem_destroy( sem_t * sem ) { From 2265aad57f884152502e3bb84a2769dc7443190a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:00 -0700 Subject: [PATCH 091/255] F-8750 - https://fenrir.wolfssl.com/finding/8750 - Free a partially allocated context without an owned mutex --- src/wolfsentry_util.c | 63 ++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 87af3736..9189c446 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3806,8 +3806,13 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( ) { #ifdef WOLFSENTRY_THREADSAFE - wolfsentry_errcode_t ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + wolfsentry_errcode_t ret; + /* a context whose lock never came up is still private to its allocator, so no mutex is possible or needed. */ + int lock_inited = (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED); + if (lock_inited) { + ret = wolfsentry_lock_have_mutex(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif if ((*wolfsentry)->routes != NULL) @@ -3826,10 +3831,12 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( #endif #ifdef WOLFSENTRY_THREADSAFE - ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); - ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (lock_inited) { + ret = wolfsentry_lock_unlock(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + ret = wolfsentry_lock_destroy(&(*wolfsentry)->lock, thread, WOLFSENTRY_LOCK_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } #endif WOLFSENTRY_FREE_1((*wolfsentry)->hpi.allocator, *wolfsentry); @@ -3837,6 +3844,16 @@ static wolfsentry_errcode_t wolfsentry_context_free_1( WOLFSENTRY_RETURN_OK; } +static void *wolfsentry_context_alloc_zeroed( + WOLFSENTRY_CONTEXT_ARGS_IN_EX(const struct wolfsentry_host_platform_interface *hpi), + size_t size) +{ + void *ret = WOLFSENTRY_MALLOC_1(hpi->allocator, size); + if (ret != NULL) + memset(ret, 0, size); + WOLFSENTRY_RETURN_VALUE(ret); +} + static wolfsentry_errcode_t wolfsentry_context_init_1( struct wolfsentry_context *wolfsentry #ifdef WOLFSENTRY_THREADSAFE @@ -3880,36 +3897,29 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( ) { wolfsentry_errcode_t ret; - if ((*wolfsentry = (struct wolfsentry_context *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof **wolfsentry)) == NULL) + if ((*wolfsentry = (struct wolfsentry_context *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof **wolfsentry)) == NULL) WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); - memset(*wolfsentry, 0, sizeof **wolfsentry); - (*wolfsentry)->hpi = *hpi; - if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->events)) == NULL) || - (((*wolfsentry)->actions = (struct wolfsentry_action_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->actions)) == NULL) || - (((*wolfsentry)->routes = (struct wolfsentry_route_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->routes)) == NULL) || - (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->user_values)) == NULL) || - (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) + /* each table is zeroed as allocated, so a partial failure leaves only well-formed tables for free_1 to tear down. */ + if ((((*wolfsentry)->events = (struct wolfsentry_event_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->events)) == NULL) || + (((*wolfsentry)->actions = (struct wolfsentry_action_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->actions)) == NULL) || + (((*wolfsentry)->routes = (struct wolfsentry_route_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->routes)) == NULL) || + (((*wolfsentry)->user_values = (struct wolfsentry_kv_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->user_values)) == NULL) || + (((*wolfsentry)->addr_families_bynumber = (struct wolfsentry_addr_family_bynumber_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_bynumber)) == NULL) #ifdef WOLFSENTRY_PROTOCOL_NAMES - || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)WOLFSENTRY_MALLOC_1(hpi->allocator, sizeof *(*wolfsentry)->addr_families_byname)) == NULL) + || (((*wolfsentry)->addr_families_byname = (struct wolfsentry_addr_family_byname_table *)wolfsentry_context_alloc_zeroed(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(hpi), sizeof *(*wolfsentry)->addr_families_byname)) == NULL) #endif ) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); } - memset((*wolfsentry)->events, 0, sizeof *(*wolfsentry)->events); - memset((*wolfsentry)->actions, 0, sizeof *(*wolfsentry)->actions); - memset((*wolfsentry)->routes, 0, sizeof *(*wolfsentry)->routes); - memset((*wolfsentry)->user_values, 0, sizeof *(*wolfsentry)->user_values); - memset((*wolfsentry)->addr_families_bynumber, 0, sizeof *(*wolfsentry)->addr_families_bynumber); #ifdef WOLFSENTRY_PROTOCOL_NAMES - memset((*wolfsentry)->addr_families_byname, 0, sizeof *(*wolfsentry)->addr_families_byname); if ((ret = wolfsentry_addr_family_table_pair(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*wolfsentry), (*wolfsentry)->addr_families_bynumber, (*wolfsentry)->addr_families_byname)) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_ERROR_RERETURN(ret); } #endif @@ -3919,7 +3929,12 @@ static wolfsentry_errcode_t wolfsentry_context_alloc_1( , lock_flags #endif )) < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* init_1 can fail after the lock is up, and free_1 then needs the mutex, which no one else can hold yet. */ + if (WOLFSENTRY_ATOMIC_LOAD((*wolfsentry)->lock.state) != WOLFSENTRY_LOCK_UNINITED) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE)); +#endif + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */))); WOLFSENTRY_ERROR_RERETURN(ret); } From 026dc64c78037e12c5071eed363ddc96040a0959 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:00 -0700 Subject: [PATCH 092/255] F-10492 - https://fenrir.wolfssl.com/finding/10492 - Free the clone when its mutex cannot be taken in wolfsentry_context_clone --- src/wolfsentry_util.c | 13 +++++++++++-- tests/unittests.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 9189c446..9083bc3a 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4225,6 +4225,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( wolfsentry_clone_flags_t flags) { wolfsentry_errcode_t ret; +#ifdef WOLFSENTRY_THREADSAFE + wolfsentry_errcode_t cleanup_ret; +#endif #ifdef WOLFSENTRY_THREADSAFE WOLFSENTRY_SHARED_OR_RETURN(); @@ -4232,8 +4235,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, thread, clone, wolfsentry->lock.flags)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) - goto out; + if ((ret = wolfsentry_context_lock_mutex_abstimed(*clone, thread, NULL)) < 0) { + /* the clone is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*clone)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(clone, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } #else if ((ret = wolfsentry_context_alloc_1(&wolfsentry->hpi, clone)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); diff --git a/tests/unittests.c b/tests/unittests.c index c05dc565..3e6e4e5a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1618,6 +1618,43 @@ static int test_object_release_tableless(void) { WOLFSENTRY_RETURN_OK; } +#if defined(WOLFSENTRY_THREADSAFE) + +static int test_context_clone_readonly_thread(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_TEST_HPI, + thread, + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* a read-only thread can clone, but cannot take the clone's mutex, so the clone must be torn down. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readonly(thread)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NOT_PERMITTED, wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(clone == NULL); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_set_thread_readwrite(thread)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(&wolfsentry, thread)); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +#else + +TEST_SKIP(test_context_clone_readonly_thread) + +#endif /* WOLFSENTRY_THREADSAFE */ + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9595,6 +9632,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_object_release_tableless failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_context_clone_readonly_thread(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_readonly_thread failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From c262237ae554b5cd0543e8106f04541efd1268e9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:00 -0700 Subject: [PATCH 093/255] F-15470 - https://fenrir.wolfssl.com/finding/15470 - Give an as-at-creation clone its own fallthrough route --- src/wolfsentry_util.c | 3 ++- tests/unittests.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 9083bc3a..6a503358 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4289,7 +4289,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_clone( #endif if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)) { - ret = WOLFSENTRY_ERROR_ENCODE(OK); + /* the fresh route table needs its own fallthrough route, as wolfsentry_init_ex() provides. */ + ret = wolfsentry_route_table_fallthrough_route_alloc(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(*clone), (*clone)->routes); goto out; } diff --git a/tests/unittests.c b/tests/unittests.c index 3e6e4e5a..4def920e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1655,6 +1655,40 @@ TEST_SKIP(test_context_clone_readonly_thread) #endif /* WOLFSENTRY_THREADSAFE */ +static int test_context_clone_fallthrough(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + struct wolfsentry_route_table *clone_routes; + const struct wolfsentry_route *fallthrough_route; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* a full-flush clone starts with an empty route table, which must still carry a fallthrough route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), &clone_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), clone_routes, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_object_release(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), (void *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9637,6 +9671,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_context_clone_readonly_thread failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_context_clone_fallthrough(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_clone_fallthrough failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From efd5f0bc0b41fc724c80c208fba35f63b4266874 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:01 -0700 Subject: [PATCH 094/255] F-11601 - https://fenrir.wolfssl.com/finding/11601 - Free a partially initialized context when wolfsentry_init_ex fails --- src/wolfsentry_util.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 6a503358..6e627658 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3951,6 +3951,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( struct wolfsentry_host_platform_interface hpi; #ifdef WOLFSENTRY_THREADSAFE wolfsentry_lock_flags_t lock_flags = WOLFSENTRY_LOCK_FLAG_NONE; + wolfsentry_errcode_t cleanup_ret; #endif wolfsentry_errcode_t ret; @@ -4046,7 +4047,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( out: if (ret < 0) { - (void)wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT); +#ifdef WOLFSENTRY_THREADSAFE + /* the new context is still private, so tear it down with no thread context, which a read-only thread or an expired deadline cannot block. */ + cleanup_ret = wolfsentry_lock_mutex_abstimed(&(*wolfsentry)->lock, NULL /* thread */, ×pec_deadline_now, WOLFSENTRY_LOCK_FLAG_NONE); + if (cleanup_ret >= 0) + cleanup_ret = wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL /* thread */)); + WOLFSENTRY_WARN_ON_FAILURE(cleanup_ret); +#else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT)); +#endif } WOLFSENTRY_ERROR_RERETURN(ret); } From 7d2dc2d3c45710c7658ba81d476921e82cfdf8e7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:01 -0700 Subject: [PATCH 095/255] F-10497 - https://fenrir.wolfssl.com/finding/10497 - Fail wolfsentry_init_ex when a duration conversion is rejected --- src/wolfsentry_util.c | 6 ++++-- tests/unittests.c | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 6e627658..d6838bfc 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4031,8 +4031,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_init_ex( * because wolfsentry_interval_from_seconds() needs a valid * wolfsentry_context (circular dependency). fix it now that we can. */ - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration)); - hpi.timecbs.interval_from_seconds((long int)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge)); + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.penaltybox_duration, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.penaltybox_duration))) < 0) + goto out; + if ((ret = hpi.timecbs.interval_from_seconds((time_t)(*wolfsentry)->config.config.route_idle_time_for_purge, 0 /* howlong_nsecs */, &((*wolfsentry)->config.config.route_idle_time_for_purge))) < 0) + goto out; (*wolfsentry)->config_at_creation = (*wolfsentry)->config; diff --git a/tests/unittests.c b/tests/unittests.c index 4def920e..be2eac19 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1689,6 +1689,48 @@ static int test_context_clone_fallthrough(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t failing_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + (void)howlong_secs; + (void)howlong_nsecs; + (void)howlong; + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); +} + +static int test_init_interval_conversion_failure(void) { + struct wolfsentry_context *wolfsentry, *wolfsentry2 = NULL; + struct wolfsentry_host_platform_interface hpi; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10, .penaltybox_duration = 1 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 1, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* borrow the working platform interface, then make the seconds conversion fail. */ + hpi = *wolfsentry_get_hpi(wolfsentry); + hpi.timecbs.interval_from_seconds = failing_interval_from_seconds; + + /* a rejected duration must fail initialization and leave no context behind. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&hpi), + &config, + &wolfsentry2, + WOLFSENTRY_INIT_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry2 == NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9676,6 +9718,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_context_clone_fallthrough failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_init_interval_conversion_failure(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_init_interval_conversion_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From 81c094f39cd9874331c2075308a1221b6d7515bd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 09:29:01 -0700 Subject: [PATCH 096/255] F-6507 - https://fenrir.wolfssl.com/finding/6507 - Refresh the lock-free default policy copy in wolfsentry_context_exchange --- src/wolfsentry_util.c | 4 ++++ tests/unittests.c | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index d6838bfc..55252928 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4396,6 +4396,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_context_exchange(WOLFSENTRY_CONTE wolfsentry2->ents_by_id = scratch.ents_by_id; + /* keep the lock-free copy of the default policy in step with the swapped route tables. */ + WOLFSENTRY_ATOMIC_STORE(wolfsentry->default_policy, wolfsentry->routes->default_policy); + WOLFSENTRY_ATOMIC_STORE(wolfsentry2->default_policy, wolfsentry2->routes->default_policy); + /* note that wolfsentry->lock and wolfsentry->cleanup_hooks are not copied. * both of these follow the context pointer itself, which is necessarily * invariant once allocated by wolfsentry_init(), until final deallocation diff --git a/tests/unittests.c b/tests/unittests.c index be2eac19..cf5eb539 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1731,6 +1731,45 @@ static int test_init_interval_conversion_failure(void) { WOLFSENTRY_RETURN_OK; } +static int test_context_exchange_default_policy(void) { + struct wolfsentry_context *wolfsentry, *clone = NULL; + wolfsentry_action_res_t policy; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_init_ex(wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_REJECT)); + + /* stage a new policy in a clone, then swap it in, as a config reload does. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &clone, WOLFSENTRY_CLONE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, clone)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_get(WOLFSENTRY_CONTEXT_ARGS_OUT, &policy)); + WOLFSENTRY_EXIT_ON_FALSE(policy == WOLFSENTRY_ACTION_RES_ACCEPT); + /* the lock-free dispatch fallback reads the context-level copy, which must follow the swap. */ + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->default_policy == WOLFSENTRY_ACTION_RES_ACCEPT); + WOLFSENTRY_EXIT_ON_FALSE(clone->default_policy == WOLFSENTRY_ACTION_RES_REJECT); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_RWLOCKS */ #ifdef TEST_STATIC_ROUTES @@ -9723,6 +9762,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_init_interval_conversion_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_context_exchange_default_policy(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_context_exchange_default_policy failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_STATIC_ROUTES From d5a99e4671bd7adcd0e19848f6816e15ff656f9b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:47:59 -0700 Subject: [PATCH 097/255] F-8750 - https://fenrir.wolfssl.com/finding/8750 - Initialize every eventconfig field in the context lifecycle tests for C89 builds --- tests/unittests.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index cf5eb539..fb7146e2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1400,7 +1400,7 @@ static int test_rw_locks_auto_downgrade(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -1472,7 +1472,7 @@ static int test_rw_locks_reader_wakeup(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif volatile int measured_sequence[8], measured_sequence_i = 0; pthread_t reader_thread, writer_thread; @@ -1572,7 +1572,7 @@ static int test_object_release_tableless(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -1625,7 +1625,7 @@ static int test_context_clone_readonly_thread(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -1662,7 +1662,7 @@ static int test_context_clone_fallthrough(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -1702,7 +1702,7 @@ static int test_init_interval_conversion_failure(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10, .penaltybox_duration = 1 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 1, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -1737,7 +1737,7 @@ static int test_context_exchange_default_policy(void) { #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else - struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0 }; + struct wolfsentry_eventconfig config = { 32, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #endif WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); From 69d4855fcac47a88128307f763f4913f8d850152 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:08:45 -0700 Subject: [PATCH 098/255] F-10470 - https://fenrir.wolfssl.com/finding/10470 - Correct version comparison macro docs to state the header version is the left operand --- wolfsentry/wolfsentry.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index e81f6f28..4a8d4a05 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -61,15 +61,15 @@ #define WOLFSENTRY_VERSION WOLFSENTRY_VERSION_ENCODE(WOLFSENTRY_VERSION_MAJOR, WOLFSENTRY_VERSION_MINOR, WOLFSENTRY_VERSION_TINY) /*!< \brief The version recorded in wolfsentry.h, encoded as an integer @hideinitializer */ #define WOLFSENTRY_VERSION_GT(major, minor, tiny) (WOLFSENTRY_VERSION > WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is greater than that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is greater than the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_GE(major, minor, tiny) (WOLFSENTRY_VERSION >= WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is greater than or equal to that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is greater than or equal to the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_EQ(major, minor, tiny) (WOLFSENTRY_VERSION == WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version equals that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h equals the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_LT(major, minor, tiny) (WOLFSENTRY_VERSION < WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is less than that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is less than the given version. @hideinitializer */ #define WOLFSENTRY_VERSION_LE(major, minor, tiny) (WOLFSENTRY_VERSION <= WOLFSENTRY_VERSION_ENCODE(major, minor, tiny)) - /*!< \brief Helper macro that is true if the given version is less than or equal to that in wolfsentry.h. @hideinitializer */ + /*!< \brief Helper macro that is true if the version in wolfsentry.h is less than or equal to the given version. @hideinitializer */ /*! \brief flags to pass to wolfsentry_init_ex(), to be `OR`d together. */ typedef enum { From 1a7968d09a07f942d581cc551bf5e219198a0aed Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:08:45 -0700 Subject: [PATCH 099/255] F-10471 - https://fenrir.wolfssl.com/finding/10471 - Correct sem_init_cb_t docs on pshared argument --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 4a8d4a05..69e6a5b0 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -198,7 +198,7 @@ struct wolfsentry_timecbs { */ typedef int (*sem_init_cb_t)(sem_t *sem, int pshared, unsigned int value); - /*!< Pointer to function with arguments and semantics of POSIX `sem_init()`. Currently, `pshared` and `value` are always zero as called by wolfSentry, so implementations can ignore them. */ + /*!< Pointer to function with arguments and semantics of POSIX `sem_init()`. Currently, `value` is always zero as called by wolfSentry. `pshared` is zero unless the lock is initialized with `WOLFSENTRY_LOCK_FLAG_PSHARED`, in which case it is nonzero. */ typedef int (*sem_post_cb_t)(sem_t *sem); /*!< Pointer to function with arguments and semantics of POSIX `sem_post()` */ typedef int (*sem_wait_cb_t)(sem_t *sem); From e50d760e896602149f9455f86e56f9b6a36fe922 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:14 -0700 Subject: [PATCH 100/255] F-10472 - https://fenrir.wolfssl.com/finding/10472 - Correct WOLFSENTRY_PROMOTABLE_EX docs to describe a shared lock with promotion reservation --- wolfsentry/wolfsentry_errcodes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry_errcodes.h b/wolfsentry/wolfsentry_errcodes.h index 6dda150b..7753eaab 100644 --- a/wolfsentry/wolfsentry_errcodes.h +++ b/wolfsentry/wolfsentry_errcodes.h @@ -260,7 +260,7 @@ static inline int WOLFSENTRY_ERROR_DECODE_LINE_NUMBER(wolfsentry_errcode_t x) { /*!< \brief Get a shared lock on the current context, and on failure, return the `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_PROMOTABLE_EX(ctx) wolfsentry_context_lock_shared_with_reservation_abstimed(ctx, thread, NULL) - /*!< \brief Get a mutex on a `wolfsentry_context`, evaluating to the resulting `wolfsentry_errcode_t`. @hideinitializer */ + /*!< \brief Get a shared lock with mutex promotion reservation on a `wolfsentry_context`, evaluating to the resulting `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_PROMOTABLE_OR_RETURN() do { \ wolfsentry_errcode_t _lock_ret; \ From e131228053ca47d8b80959fda1aa6ffeba506e6f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:14 -0700 Subject: [PATCH 101/255] F-10473 - https://fenrir.wolfssl.com/finding/10473 - Correct clone flag name in wolfsentry_context_clone docs --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 69e6a5b0..b703e58b 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -1578,7 +1578,7 @@ typedef enum { \brief Clones a wolfsentry context \param clone the destination wolfsentry context, should be a pointer to a NULL pointer as this function will malloc - \param flags set to WOLFSENTRY_CLONE_FLAG_AT_CREATION to use the config at the creation of the original wolfsentry context instead of the current configuration + \param flags set to WOLFSENTRY_CLONE_FLAG_AS_AT_CREATION to use the config at the creation of the original wolfsentry context instead of the current configuration \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. From 8af2f751a425b3aba9e61f0dc15794360adad144 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:22 -0700 Subject: [PATCH 102/255] F-10474 - https://fenrir.wolfssl.com/finding/10474 - Correct wolfsentry_route_event_dispatch_by_route docs to say route pointer instead of ID --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index b703e58b..9bbf0b77 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2275,7 +2275,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route( void *caller_arg, /* passed to action callback(s) as the caller_arg. */ wolfsentry_action_res_t *action_results ); - /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route by ID, mainly for use by application code that tracks route/session relationships. */ + /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route with the supplied route pointer, mainly for use by application code that tracks route/session relationships. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_with_inited_result( WOLFSENTRY_CONTEXT_ARGS_IN, From b341daa0d6eb7dab3531c68b3f199ac92432191c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 103/255] F-10475 - https://fenrir.wolfssl.com/finding/10475 - Correct wolfsentry_route_event_dispatch_by_route_with_inited_result docs to say route pointer instead of ID --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 9bbf0b77..35a538e8 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2285,7 +2285,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit void *caller_arg, /* passed to action callback(s) as the caller_arg. */ wolfsentry_action_res_t *action_results ); - /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route by ID, and doesn't clear \p action_results on entry, mainly for use by application code that tracks route/session relationships. */ + /*!< \brief Variant of wolfsentry_route_event_dispatch() that preselects the matched route with the supplied route pointer, and doesn't clear \p action_results on entry, mainly for use by application code that tracks route/session relationships. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_max_purgeable_routes_get( WOLFSENTRY_CONTEXT_ARGS_IN, From b9da241ab1141b9ba2e545271eba9561f6209892 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 104/255] F-10476 - https://fenrir.wolfssl.com/finding/10476 - Correct WOLFSENTRY_KV_V_SINT docs to name WOLFSENTRY_KV_SINT --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 35a538e8..273eb921 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -3080,7 +3080,7 @@ struct wolfsentry_kv_pair { #define WOLFSENTRY_KV_V_UINT(kv) ((kv)->a.v_uint) /*!< \brief Evaluates to the `uint64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_UINT`. @hideinitializer */ #define WOLFSENTRY_KV_V_SINT(kv) ((kv)->a.v_sint) - /*!< \brief Evaluates to the `int64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_INT`. @hideinitializer */ + /*!< \brief Evaluates to the `int64_t` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_SINT`. @hideinitializer */ #define WOLFSENTRY_KV_V_FLOAT(kv) ((kv)->a.v_float) /*!< \brief Evaluates to the `double` value of a `wolfsentry_kv_pair` of type `WOLFSENTRY_KV_FLOAT`. @hideinitializer */ #define WOLFSENTRY_KV_V_STRING_LEN(kv) ((kv)->a.string_len) From 6cde2a84b751f80a0fd435f3bc6fba4fbf55e0aa Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 105/255] F-10477 - https://fenrir.wolfssl.com/finding/10477 - Correct wolfsentry_user_value_get_sint docs to name WOLFSENTRY_KV_SINT --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 273eb921..c36640fd 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -3184,7 +3184,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_sint( const char *key, int key_len, int64_t *value); - /*!< \brief Gets a `WOLFSENTRY_KV_UINT` value with the designated `key`. */ + /*!< \brief Gets a `WOLFSENTRY_KV_SINT` value with the designated `key`. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_double( WOLFSENTRY_CONTEXT_ARGS_IN, From 3a8b3c2792c6bc40ee49a5da06a6044fba57425f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 106/255] F-10478 - https://fenrir.wolfssl.com/finding/10478 - Correct wolfsentry_user_value_get_float docs to name WOLFSENTRY_KV_FLOAT --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index c36640fd..9b70e7e7 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -3199,7 +3199,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_get_float( const char *key, int key_len, double *value); - /*!< \brief Gets a `WOLFSENTRY_KV_UINT` value with the designated `key`. */ + /*!< \brief Gets a `WOLFSENTRY_KV_FLOAT` value with the designated `key`. */ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_value_store_string( WOLFSENTRY_CONTEXT_ARGS_IN, From 8bbd5e6d2559abc6e6517a49b2fb6eb37b1b689c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 107/255] F-10479 - https://fenrir.wolfssl.com/finding/10479 - Correct wolfsentry_route_get_metadata docs for the metadata parameter indirection --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 9b70e7e7..86649424 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2416,7 +2416,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_get_flags( \brief Gets the metadata for a route \param route the route to get the metadata for - \param metadata a pointer to a pointer to receive the metadata + \param metadata a pointer to a caller-provided `struct wolfsentry_route_metadata_exports` to populate \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. */ From 31ca06f6df2bffadf4e572c9d02c5f7917f2717e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 108/255] F-10480 - https://fenrir.wolfssl.com/finding/10480 - Correct WOLFSENTRY_NO_PROTOCOL_NAMES docs to describe protocol name support --- wolfsentry/wolfsentry_settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry_settings.h b/wolfsentry/wolfsentry_settings.h index 6670084c..5864a0f0 100644 --- a/wolfsentry/wolfsentry_settings.h +++ b/wolfsentry/wolfsentry_settings.h @@ -255,7 +255,7 @@ #undef WOLFSENTRY_NO_ERROR_STRINGS #define WOLFSENTRY_NO_PROTOCOL_NAMES - /*!< \brief If defined, omit APIs for rendering error codes and source code files in human readable form. They will be rendered numerically. */ + /*!< \brief If defined, omit APIs for converting between address family names and numbers. Address families will be rendered numerically. Protocol and service name lookup is controlled separately by `WOLFSENTRY_NO_GETPROTOBY`. */ #undef WOLFSENTRY_NO_PROTOCOL_NAMES #define WOLFSENTRY_NO_ADDR_BITMASK_MATCHING From a9bf4be00dc171fe598646351c889a1f981192a0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 109/255] F-11596 - https://fenrir.wolfssl.com/finding/11596 - Correct dictionary walk callback docs to say only negative returns abort --- wolfsentry/centijson_value.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wolfsentry/centijson_value.h b/wolfsentry/centijson_value.h index 03241ca4..d1928fd8 100644 --- a/wolfsentry/centijson_value.h +++ b/wolfsentry/centijson_value.h @@ -514,8 +514,9 @@ WOLFSENTRY_API int json_value_dict_remove( /* Walking over all items in the dictionary. The callback function is called * for every item in the dictionary, providing key and value and propagating - * the user data into it. If the callback returns non-zero, the function - * aborts immediately. + * the user data into it. If the callback returns a negative value, the + * function aborts immediately and returns that value; otherwise the walk + * continues. * * Note dict_walk_ordered() is supported only if DICT_MAINTAINORDER * flag was used in init_dict(). From bc0246ecde12dd43ffa40cc37e227bc8ae3691cc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:23 -0700 Subject: [PATCH 110/255] F-11597 - https://fenrir.wolfssl.com/finding/11597 - Correct event label argument annotations in wolfsentry_setup calls --- wolfsentry/wolfssl_test.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wolfsentry/wolfssl_test.h b/wolfsentry/wolfssl_test.h index 6163b935..a3c2a51b 100644 --- a/wolfsentry/wolfssl_test.h +++ b/wolfsentry/wolfssl_test.h @@ -373,7 +373,7 @@ static int wolfsentry_setup( WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, - 0 /* event_label_len */, 0 /* event_label */, &id, + NULL /* event_label */, 0 /* event_label_len */, &id, &action_results)) < 0) { fprintf(stderr, "wolfsentry_route_insert() returned " WOLFSENTRY_ERROR_FMT "\n", @@ -432,7 +432,7 @@ static int wolfsentry_setup( WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, - 0 /* event_label_len */, 0 /* event_label */, &id, + NULL /* event_label */, 0 /* event_label_len */, &id, &action_results)) < 0) { fprintf(stderr, "wolfsentry_route_insert() returned " WOLFSENTRY_ERROR_FMT "\n", From 347e79d5cba3f832ca2f5e1c0189037a90aa5c58 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 111/255] F-12519 - https://fenrir.wolfssl.com/finding/12519 - Correct wolfsentry_lock_get_flags docs to say flags must be non-NULL --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 86649424..636200f9 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -709,7 +709,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_lock_shared2mutex_is_reserved(str \param lock a pointer to the lock \param thread pointer to the `wolfsentry_thread_context` - \param flags optional `wolfsentry_lock_flags_t` + \param flags non-NULL pointer to a `wolfsentry_lock_flags_t` that receives the current flags \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. From 977b3eed87233e46ae8488d5ee894d343293d9a5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 112/255] F-12520 - https://fenrir.wolfssl.com/finding/12520 - Correct route deletion docs to refer to the delete action --- wolfsentry/wolfsentry.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 636200f9..1aa34bd2 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -1852,7 +1852,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_from_table( \param flags flags for the route \param trigger_label a label for the trigger event (or null) \param trigger_label_len the length of the trigger_label parameter - \param action_results a pointer to results of the insert action -- all bits are cleared on entry. + \param action_results a pointer to results of the delete action -- all bits are cleared on entry. \param n_deleted a counter for the number of entries deleted \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. @@ -1877,7 +1877,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete( \param id the object ID, as returned by wolfsentry_route_insert() or wolfsentry_get_object_id() \param trigger_label a label for a trigger event (or null) \param trigger_label_len the length of the trigger_label parameter - \param action_results a pointer to results of the insert action -- all bits are cleared on entry. + \param action_results a pointer to results of the delete action -- all bits are cleared on entry. \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. From c2463b4228bba6c565aa2ffcd4166cea8ba5343d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 113/255] F-12521 - https://fenrir.wolfssl.com/finding/12521 - Correct wolfsentry_route_event_dispatch docs example to dereference action_results --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 1aa34bd2..9702be76 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2192,7 +2192,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table( /*!< \brief Variant of wolfsentry_route_event_dispatch() that accepts an explicit \p route_table. */ /*! - \brief Submit an event into wolfsentry and pass it through the filters. The action_results are cleared on entry, and can be checked to see what actions wolfsentry took, and what actions the caller should take (most saliently, #WOLFSENTRY_ACTION_RES_ACCEPT or #WOLFSENTRY_ACTION_RES_REJECT). \p action_results can be filtered with constructs like `WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)` + \brief Submit an event into wolfsentry and pass it through the filters. The action_results are cleared on entry, and can be checked to see what actions wolfsentry took, and what actions the caller should take (most saliently, #WOLFSENTRY_ACTION_RES_ACCEPT or #WOLFSENTRY_ACTION_RES_REJECT). \p action_results can be filtered with constructs like `WOLFSENTRY_MASKIN_BITS(*action_results, WOLFSENTRY_ACTION_RES_REJECT)` \param remote the remote sockaddr details \param local the local sockaddr details From edd0c63a14c2d1fa07cd8233adedebcbb5bd2bf5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 114/255] F-12522 - https://fenrir.wolfssl.com/finding/12522 - Correct SAX process callback docs to say only negative returns abort --- wolfsentry/centijson_sax.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wolfsentry/centijson_sax.h b/wolfsentry/centijson_sax.h index 828cf2ff..c8ddf835 100644 --- a/wolfsentry/centijson_sax.h +++ b/wolfsentry/centijson_sax.h @@ -169,8 +169,8 @@ typedef struct JSON_CALLBACKS { * corresponding values in the alternating fashion (i.e. in the order * as they are in the JSON input.). * - * Application can abort the parsing operation by returning a non-zero. - * Note the non-zero return value of the callback is propagated to + * Application can abort the parsing operation by returning a negative + * value. Note the negative return value of the callback is propagated to * json_feed() and json_fini(). */ int (*process)(JSON_TYPE /*type*/, const unsigned char* /*data*/, From 7f97940cfd7b3a9a43b5c96f3fb495d55edc7769 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 115/255] F-12523 - https://fenrir.wolfssl.com/finding/12523 - Correct JSON dump writer callback docs to say only negative returns abort --- wolfsentry/centijson_sax.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/centijson_sax.h b/wolfsentry/centijson_sax.h index c8ddf835..7e974e50 100644 --- a/wolfsentry/centijson_sax.h +++ b/wolfsentry/centijson_sax.h @@ -337,7 +337,7 @@ typedef int (*JSON_DUMP_CALLBACK)(const unsigned char* /*str*/, size_t /*size*/, * handles proper escaping of control characters. * * The provided writer callback must write all the data provided to it and - * return zero to indicate success, or non-zero to indicate an error and abort + * return zero to indicate success, or a negative value to indicate an error and abort * the operation. * * All these return zero on success, JSON_ERR_OUTOFMEMORY, or an error code From aaa172fb2feaf1a8ea3e6c6fe9ae3918171c4f99 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 116/255] F-12524 - https://fenrir.wolfssl.com/finding/12524 - Correct DOM dump writer callback docs to say only negative returns abort --- wolfsentry/centijson_dom.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/centijson_dom.h b/wolfsentry/centijson_dom.h index a502b47b..f02c339b 100644 --- a/wolfsentry/centijson_dom.h +++ b/wolfsentry/centijson_dom.h @@ -152,7 +152,7 @@ WOLFSENTRY_API int json_dom_parse( * callback. * * The provided writing function must write all the data provided to it - * and return zero to indicate success, or non-zero to indicate an error + * and return zero to indicate success, or a negative value to indicate an error * and abort the operation. * * Returns zero on success, JSON_ERR_OUTOFMEMORY, or an error the code returned From 9761211049cfeb4accc583e04d7678dea2e07c59 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:09:24 -0700 Subject: [PATCH 117/255] F-12526 - https://fenrir.wolfssl.com/finding/12526 - Correct WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN docs example to a success name --- wolfsentry/wolfsentry_errcodes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry_errcodes.h b/wolfsentry/wolfsentry_errcodes.h index 7753eaab..df262daa 100644 --- a/wolfsentry/wolfsentry_errcodes.h +++ b/wolfsentry/wolfsentry_errcodes.h @@ -304,7 +304,7 @@ static inline int WOLFSENTRY_ERROR_DECODE_LINE_NUMBER(wolfsentry_errcode_t x) { /*!< \brief Calculate the `wolfsentry_errcode_t` return value for an expression `y`, then unlock the current context, and finally, return the encoded `wolfsentry_errcode_t`. @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN(name) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RETURN(name); } while (0) - /*!< \brief Unlock the current context, and return a `wolfsentry_errcode_t` encoding the current source ID and line number, and the designated short-form success `name` (e.g. `INVALID_ARG`). @hideinitializer */ + /*!< \brief Unlock the current context, and return a `wolfsentry_errcode_t` encoding the current source ID and line number, and the designated short-form success `name` (e.g. `OK`). @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN_RECODED(x) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RETURN_RECODED(x); } while (0) /*!< \brief Unlock the current context, then take an encoded `wolfsentry_errcode_t` `x`, recode it with the current source ID and line number, and return it. @hideinitializer */ #define WOLFSENTRY_SUCCESS_UNLOCK_AND_RERETURN(x) do { WOLFSENTRY_UNLOCK_FOR_RETURN(); WOLFSENTRY_SUCCESS_RERETURN(x); } while (0) From 10b95ec1ab069c3f13707ce4b55cf3de72c5723c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:23:07 -0700 Subject: [PATCH 118/255] F-8757 - https://fenrir.wolfssl.com/finding/8757 - Honor lenient junk handling in base64 pad lookahead --- src/wolfsentry_util.c | 6 ++++-- tests/unittests.c | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 55252928..a0e0f288 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4469,13 +4469,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_base64_decode(const char *src, si else WOLFSENTRY_ERROR_RETURN(INVALID_ARG); } else if (this_char == (uint32_t)'@') { - if ((src + 1 < src_end) && (base64_inv_lut[*((unsigned char *)src + 1)] != '@')) - WOLFSENTRY_ERROR_RETURN(INVALID_ARG); ++pad_chars; decoded <<= 6U; continue; } + /* data must not appear after padding, in strict or lenient mode */ + if (pad_chars != 0) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + decoded <<= 6U; decoded |= this_char; decoded_bits += 6U; diff --git a/tests/unittests.c b/tests/unittests.c index fb7146e2..38ab8363 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -277,6 +277,26 @@ static wolfsentry_errcode_t test_init(void) { WOLFSENTRY_ERROR_RERETURN(ret); } +static wolfsentry_errcode_t test_base64_decode_lenient_padding(void) { + /* A correctly padded value followed by ignorable whitespace must be + * accepted when lenient parsing is requested. + */ + static const char input[] = "QQ==\n"; + byte outbuf[8]; + size_t outbuf_spc = sizeof outbuf; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode(input, strlen(input), outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == 1) && (outbuf[0] == 'A')); + + /* lenient parsing still rejects data appearing after padding */ + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("Q=Q=", 4, outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("QQ==QUJD", 8, outbuf, &outbuf_spc, 1 /* ignore_junk_p */)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9712,6 +9732,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_init failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_base64_decode_lenient_padding(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_base64_decode_lenient_padding failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_LWIP From 156a889ca4d11371c9cf3157c863d79a2f7fa292 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:24:00 -0700 Subject: [PATCH 119/255] F-8771 - https://fenrir.wolfssl.com/finding/8771 - Reject base64 padded quantums with too few data sextets --- src/wolfsentry_util.c | 5 ++++- tests/unittests.c | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index a0e0f288..3c869413 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -4504,12 +4504,15 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_base64_decode(const char *src, si case 0: break; case 1: + if (decoded_bits != 18U) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); *dest++ = (byte)(decoded >> 16U); *dest++ = (byte)(decoded >> 8U); dest_len += 2; break; - /* fall through */ case 2: + if (decoded_bits != 12U) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); *dest++ = (byte)(decoded >> 16U); ++dest_len; break; diff --git a/tests/unittests.c b/tests/unittests.c index 38ab8363..e839c13e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -297,6 +297,27 @@ static wolfsentry_errcode_t test_base64_decode_lenient_padding(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_base64_decode_quantum(void) { + /* A padded quantum must carry enough data sextets for its pad count; a + * single data sextet followed by padding is structurally invalid. + */ + byte outbuf[8]; + size_t outbuf_spc; + + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("A=", 2, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_base64_decode("A==", 3, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + + /* A well-formed padded quantum is still accepted. */ + outbuf_spc = sizeof outbuf; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_base64_decode("QQ==", 4, outbuf, &outbuf_spc, 0 /* ignore_junk_p */)); + WOLFSENTRY_EXIT_ON_FALSE((outbuf_spc == 1) && (outbuf[0] == 'A')); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9737,6 +9758,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_base64_decode_lenient_padding failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_base64_decode_quantum(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_base64_decode_quantum failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_LWIP From 91c76379c233fbe843c9a73b32469901479ceada Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:25:19 -0700 Subject: [PATCH 120/255] F-2843 - https://fenrir.wolfssl.com/finding/2843 - Test eventconfig rejection of conflicting add and clear bits --- tests/unittests.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index e839c13e..e3b69927 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -318,6 +318,28 @@ static wolfsentry_errcode_t test_base64_decode_quantum(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_eventconfig_overlap_bits(void) { + /* A bit cannot be simultaneously requested for addition and clearing. */ + struct wolfsentry_eventconfig config; + + memset(&config, 0, sizeof config); + config.route_flags_to_add_on_insert = WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + config.route_flags_to_clear_on_insert = WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + memset(&config, 0, sizeof config); + config.action_res_filter_bits_set = WOLFSENTRY_ACTION_RES_CONNECT; + config.action_res_filter_bits_unset = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + memset(&config, 0, sizeof config); + config.action_res_bits_to_add = WOLFSENTRY_ACTION_RES_CONNECT; + config.action_res_bits_to_clear = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9763,6 +9785,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_base64_decode_quantum failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_eventconfig_overlap_bits(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_overlap_bits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_LWIP From dbe9f4d7a03ad7827c972d65f11db51209e1d889 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:26:04 -0700 Subject: [PATCH 121/255] F-2849 - https://fenrir.wolfssl.com/finding/2849 - Test eventconfig route private data alignment validation --- tests/unittests.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index e3b69927..187054a6 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -340,6 +340,40 @@ static wolfsentry_errcode_t test_eventconfig_overlap_bits(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_eventconfig_alignment(void) { + struct wolfsentry_eventconfig config; + + /* Non-zero alignment with zero private-data size is contradictory. */ + memset(&config, 0, sizeof config); + config.route_private_data_alignment = sizeof(void *); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Alignment smaller than a pointer is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = 32; + config.route_private_data_alignment = 1; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Non-power-of-two alignment is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = 64; + config.route_private_data_alignment = (3 * sizeof(void *)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* Alignment larger than the private-data size is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = sizeof(void *); + config.route_private_data_alignment = (2 * sizeof(void *)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + + /* A private-data size beyond the data_addr_offset field is rejected. */ + memset(&config, 0, sizeof config); + config.route_private_data_size = (size_t)0x10000; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9790,6 +9824,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_eventconfig_overlap_bits failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_eventconfig_alignment(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_alignment failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_LWIP From dcaeafa9925a0a5daedd334cecb360516b71d64a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:26:30 -0700 Subject: [PATCH 122/255] F-3190 - https://fenrir.wolfssl.com/finding/3190 - Test eventconfig derogatory threshold range check --- tests/unittests.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 187054a6..b804340c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -374,6 +374,27 @@ static wolfsentry_errcode_t test_eventconfig_alignment(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_eventconfig_derogatory_threshold(void) { + /* derogatory_threshold_for_penaltybox must fit the uint16_t per-route + * derogatory_count counter. + */ + struct wolfsentry_eventconfig config; + + memset(&config, 0, sizeof config); + /* the route's derogatory_count counter is 16-bit; only test the + * out-of-range rejection when the configured field can exceed that. + */ + if (MAX_UINT_OF(config.derogatory_threshold_for_penaltybox) > (uint64_t)0xffffU) { + config.derogatory_threshold_for_penaltybox = (wolfsentry_hitcount_t)0x10000U; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(INVALID_ARG, wolfsentry_eventconfig_check(&config)); + } + + config.derogatory_threshold_for_penaltybox = (wolfsentry_hitcount_t)0xffffU; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_check(&config)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9829,6 +9850,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_eventconfig_alignment failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_eventconfig_derogatory_threshold(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_eventconfig_derogatory_threshold failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_LWIP From 0a85a2cd8f66bbb0f834180ed22fd1343c711291 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:27:15 -0700 Subject: [PATCH 123/255] F-1214 - https://fenrir.wolfssl.com/finding/1214 - Correct user-defined success string table size arithmetic --- src/wolfsentry_util.c | 2 +- tests/unittests.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 3c869413..f66c991b 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -85,7 +85,7 @@ WOLFSENTRY_API const char *wolfsentry_errcode_source_string(wolfsentry_errcode_t } static const char *user_defined_errors[WOLFSENTRY_ERROR_ID_MAX - WOLFSENTRY_ERROR_ID_USER_BASE + 1] = {0}; -static const char *user_defined_successes[WOLFSENTRY_ERROR_ID_MAX + WOLFSENTRY_SUCCESS_ID_USER_BASE + 1] = {0}; +static const char *user_defined_successes[WOLFSENTRY_ERROR_ID_MAX - WOLFSENTRY_SUCCESS_ID_USER_BASE + 1] = {0}; WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_user_error_string_set(enum wolfsentry_error_id wolfsentry_error_id, const char *message_string) { if (wolfsentry_error_id < 0) { diff --git a/tests/unittests.c b/tests/unittests.c index b804340c..645aded7 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -395,6 +395,21 @@ static wolfsentry_errcode_t test_eventconfig_derogatory_threshold(void) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_ERROR_STRINGS +static wolfsentry_errcode_t test_user_string_table_bounds(void) { + /* Guard that the user-defined success/error string tables are sized to + * hold their highest valid index. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_error_string_set((enum wolfsentry_error_id)WOLFSENTRY_ERROR_ID_MAX, "boundary success string")); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_ALREADY_PRESENT, wolfsentry_user_error_string_set((enum wolfsentry_error_id)WOLFSENTRY_ERROR_ID_MAX, "boundary success string")); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_user_error_string_set((enum wolfsentry_error_id)(-WOLFSENTRY_ERROR_ID_MAX), "boundary error string")); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_ALREADY_PRESENT, wolfsentry_user_error_string_set((enum wolfsentry_error_id)(-WOLFSENTRY_ERROR_ID_MAX), "boundary error string")); + + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ERROR_STRINGS */ + #endif /* TEST_INIT */ static __attribute_maybe_unused__ wolfsentry_errcode_t my_addr_family_parser( @@ -9855,6 +9870,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_eventconfig_derogatory_threshold failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_ERROR_STRINGS + ret = test_user_string_table_bounds(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_user_string_table_bounds failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_LWIP From 3f380d9a4920015948be21d71196fd0f637c5849 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:27:50 -0700 Subject: [PATCH 124/255] F-10495 - https://fenrir.wolfssl.com/finding/10495 - Propagate relative deadline conversion failures instead of substituting zero --- src/wolfsentry_util.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index f66c991b..b4bf2f7d 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -912,15 +912,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_free_thread_context(struct wolfse WOLFSENTRY_RETURN_OK; } -static wolfsentry_time_t usecs_to_wolfsentry_time(struct wolfsentry_context *wolfsentry, long usecs) { - wolfsentry_time_t wolfsentry_time; - wolfsentry_errcode_t ret = WOLFSENTRY_INTERVAL_FROM_SECONDS(0, usecs * 1000L, &wolfsentry_time); - if (ret < 0) { - WOLFSENTRY_WARN_ON_FAILURE(ret); - return 0; - } - else - return wolfsentry_time; +static wolfsentry_errcode_t usecs_to_wolfsentry_time(struct wolfsentry_context *wolfsentry, long usecs, wolfsentry_time_t *wolfsentry_time) { + /* split into seconds and nanoseconds so the full long range of usecs is + * preserved without overflowing the intermediate multiplication. + */ + WOLFSENTRY_RERETURN_IF_ERROR(WOLFSENTRY_INTERVAL_FROM_SECONDS(usecs / 1000000L, (usecs % 1000000L) * 1000L, wolfsentry_time)); + WOLFSENTRY_RETURN_OK; } static long wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts) { @@ -963,7 +960,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel(WOLFSENTRY_CONTE } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel_usecs(WOLFSENTRY_CONTEXT_ARGS_IN, long usecs) { - return wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_OUT, usecs_to_wolfsentry_time(wolfsentry, usecs)); + wolfsentry_time_t rel_when; + wolfsentry_errcode_t ret = usecs_to_wolfsentry_time(wolfsentry, usecs, &rel_when); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + return wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_OUT, rel_when); } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_get_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_IN, wolfsentry_time_t *rel_when) { From d044c63b6e382385a3012d40975719a62e2dee0b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:28:15 -0700 Subject: [PATCH 125/255] F-13886 - https://fenrir.wolfssl.com/finding/13886 - Propagate deadline getter interval conversion failures --- src/wolfsentry_util.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index b4bf2f7d..d575da9b 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -920,20 +920,17 @@ static wolfsentry_errcode_t usecs_to_wolfsentry_time(struct wolfsentry_context * WOLFSENTRY_RETURN_OK; } -static long wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts) { +static wolfsentry_errcode_t wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts, long *usecs) { time_t howlong_secs; long howlong_nsecs; - wolfsentry_errcode_t ret = WOLFSENTRY_INTERVAL_TO_SECONDS(ts, &howlong_secs, &howlong_nsecs); - if (ret < 0) { - WOLFSENTRY_WARN_ON_FAILURE(ret); - return 0; - } - else if (howlong_secs * (time_t)1000000 >= (time_t)MAX_SINT_OF(long) - (time_t)1000000L) - return (long)MAX_SINT_OF(long); + WOLFSENTRY_RERETURN_IF_ERROR(WOLFSENTRY_INTERVAL_TO_SECONDS(ts, &howlong_secs, &howlong_nsecs)); + if (howlong_secs * (time_t)1000000 >= (time_t)MAX_SINT_OF(long) - (time_t)1000000L) + *usecs = (long)MAX_SINT_OF(long); else if (howlong_secs * (time_t)1000000 <= (time_t)MIN_SINT_OF(long)) - return (long)MIN_SINT_OF(long); + *usecs = (long)MIN_SINT_OF(long); else - return ((long)howlong_secs * 1000000L) + (howlong_nsecs / 1000L); + *usecs = ((long)howlong_secs * 1000000L) + (howlong_nsecs / 1000L); + WOLFSENTRY_RETURN_OK; } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_set_deadline_rel(WOLFSENTRY_CONTEXT_ARGS_IN, wolfsentry_time_t rel_when) { @@ -1004,8 +1001,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_get_deadline_rel_usecs(WOLFSENTRY { return ret; } - if (usecs) - *usecs = wolfsentry_time_to_usecs(wolfsentry, rel_when); + if (usecs) { + wolfsentry_errcode_t conv_ret = wolfsentry_time_to_usecs(wolfsentry, rel_when, usecs); + WOLFSENTRY_RERETURN_IF_ERROR(conv_ret); + } return ret; } From 9a786f7ef4453e430ad3b3a94eb13d66129b0864 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:28:51 -0700 Subject: [PATCH 126/255] F-13887 - https://fenrir.wolfssl.com/finding/13887 - Check seconds against divided limits before microsecond multiplication --- src/wolfsentry_util.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index d575da9b..da29ca8a 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -923,13 +923,22 @@ static wolfsentry_errcode_t usecs_to_wolfsentry_time(struct wolfsentry_context * static wolfsentry_errcode_t wolfsentry_time_to_usecs(struct wolfsentry_context *wolfsentry, wolfsentry_time_t ts, long *usecs) { time_t howlong_secs; long howlong_nsecs; + long howlong_usecs; WOLFSENTRY_RERETURN_IF_ERROR(WOLFSENTRY_INTERVAL_TO_SECONDS(ts, &howlong_secs, &howlong_nsecs)); - if (howlong_secs * (time_t)1000000 >= (time_t)MAX_SINT_OF(long) - (time_t)1000000L) + howlong_usecs = howlong_nsecs / 1000L; + /* compare the seconds quotient and then the remainder so a representable + * value is never saturated, and the multiply never overflows long. + */ + if ((howlong_secs > (time_t)(MAX_SINT_OF(long) / (int64_t)1000000)) || + ((howlong_secs == (time_t)(MAX_SINT_OF(long) / (int64_t)1000000)) && + (howlong_usecs > (long)(MAX_SINT_OF(long) % (int64_t)1000000)))) *usecs = (long)MAX_SINT_OF(long); - else if (howlong_secs * (time_t)1000000 <= (time_t)MIN_SINT_OF(long)) + else if ((howlong_secs < (time_t)(MIN_SINT_OF(long) / (int64_t)1000000)) || + ((howlong_secs == (time_t)(MIN_SINT_OF(long) / (int64_t)1000000)) && + (howlong_usecs < (long)(MIN_SINT_OF(long) % (int64_t)1000000)))) *usecs = (long)MIN_SINT_OF(long); else - *usecs = ((long)howlong_secs * 1000000L) + (howlong_nsecs / 1000L); + *usecs = ((long)howlong_secs * 1000000L) + howlong_usecs; WOLFSENTRY_RETURN_OK; } From f19d649b912e66e095ef2d4cd859b4c249e2a779 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:29:16 -0700 Subject: [PATCH 127/255] F-10496 - https://fenrir.wolfssl.com/finding/10496 - Propagate epoch conversion failures in timespec wrappers --- src/wolfsentry_util.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index da29ca8a..2896cfa4 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3529,7 +3529,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_now_plus_delta(struct wolfse WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_to_timespec(struct wolfsentry_context *wolfsentry, wolfsentry_time_t t, struct timespec *ts) { time_t epoch_secs; long int epoch_nsecs; - WOLFSENTRY_TO_EPOCH_TIME(t, &epoch_secs, &epoch_nsecs); + wolfsentry_errcode_t ret = WOLFSENTRY_TO_EPOCH_TIME(t, &epoch_secs, &epoch_nsecs); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ts->tv_sec = epoch_secs; ts->tv_nsec = epoch_nsecs; WOLFSENTRY_RETURN_OK; @@ -3541,7 +3542,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_time_now_plus_delta_timespec(stru long int epoch_nsecs; wolfsentry_errcode_t ret = WOLFSENTRY_GET_TIME(&now); WOLFSENTRY_RERETURN_IF_ERROR(ret); - WOLFSENTRY_TO_EPOCH_TIME(WOLFSENTRY_ADD_TIME(now, td), &epoch_secs, &epoch_nsecs); + ret = WOLFSENTRY_TO_EPOCH_TIME(WOLFSENTRY_ADD_TIME(now, td), &epoch_secs, &epoch_nsecs); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ts->tv_sec = epoch_secs; ts->tv_nsec = epoch_nsecs; WOLFSENTRY_RETURN_OK; From 40c86d78e6c4af10ab0e79096f1fd8c5283485c7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:29:36 -0700 Subject: [PATCH 128/255] F-10503 - https://fenrir.wolfssl.com/finding/10503 - Keep malloc debug count unchanged on a failed nonzero realloc --- src/wolfsentry_util.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index 2896cfa4..f7c73e48 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -668,7 +668,10 @@ static void *wolfsentry_builtin_realloc( void *ret = realloc(ptr, size); if ((ptr == NULL) && (ret != NULL)) WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); - else if ((ptr != NULL) && (ret == NULL)) + /* a failed nonzero-size realloc leaves the original block live, so + * only a zero-size request counts as a deallocation. + */ + else if ((ptr != NULL) && (ret == NULL) && (size == 0)) WOLFSENTRY_ATOMIC_DECREMENT(n_mallocs, 1); return ret; } From 23f2b00ed4a4ca2127a54cb159d6bb633f0308bd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:31:02 -0700 Subject: [PATCH 129/255] F-10510 - https://fenrir.wolfssl.com/finding/10510 - Validate alignment and guard overflow in fallback aligned allocators --- src/wolfsentry_util.c | 44 +++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index f7c73e48..f4825115 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -586,15 +586,23 @@ static void *wolfsentry_builtin_memalign( WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; if (alignment && size) { - uint32_t hdr_size = sizeof(uint16_t) + (alignment - 1); - void *p = pvPortMalloc(size + hdr_size); - if (p) { + size_t hdr_size = sizeof(uint16_t) + (alignment - 1); + /* require power-of-two alignment, a base offset that fits the stored + * uint16_t, and no overflow when reserving the header. + */ + if (((alignment & (alignment - 1)) == 0) && + (hdr_size <= (size_t)MAX_UINT_OF(uint16_t)) && + (size <= (size_t)MAX_UINT_OF(size_t) - hdr_size)) + { + void *p = pvPortMalloc(size + hdr_size); + if (p) { #ifdef WOLFSENTRY_MALLOC_DEBUG - WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); + WOLFSENTRY_ATOMIC_INCREMENT(n_mallocs, 1); #endif - /* Align to powers of two */ - ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); - *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + /* Align to powers of two */ + ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); + *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + } } } WOLFSENTRY_RETURN_VALUE(ptr); @@ -690,14 +698,22 @@ static void *wolfsentry_builtin_memalign( WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; if (alignment && size) { size_t hdr_size = sizeof(uint16_t) + (alignment - 1); - void *p = malloc(size + hdr_size); - if (p) { - /* Align to powers of two */ - ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); - *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); - } - /* cppcheck-suppress memleak + /* require power-of-two alignment, a base offset that fits the stored + * uint16_t, and no overflow when reserving the header. */ + if (((alignment & (alignment - 1)) == 0) && + (hdr_size <= (size_t)MAX_UINT_OF(uint16_t)) && + (size <= (size_t)MAX_UINT_OF(size_t) - hdr_size)) + { + void *p = malloc(size + hdr_size); + if (p) { + /* Align to powers of two */ + ptr = (void *) ((((uintptr_t)p + sizeof(uint16_t)) + (alignment - 1)) & ~(alignment - 1)); + *((uint16_t *)ptr - 1) = (uint16_t)((uintptr_t)ptr - (uintptr_t)p); + } + /* cppcheck-suppress memleak + */ + } } #ifdef WOLFSENTRY_MALLOC_DEBUG if (ptr != NULL) From 16d6cddf11e008216b55dabb935a0daa26dfa2a8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:22:27 -0700 Subject: [PATCH 130/255] F-4277 - https://fenrir.wolfssl.com/finding/4277 - Reject out-of-range uint64 config values without relying on errno --- src/json/load_config.c | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 16ad3d67..430daf24 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -154,7 +154,51 @@ static wolfsentry_errcode_t reset_o_u_c(struct wolfsentry_json_process_state *jp WOLFSENTRY_RETURN_OK; } +/* Range check that does not depend on errno: re-scan the already + * syntactically-validated numeric string in buf (strtol-style base-0 rules) and + * reject it when its magnitude exceeds limit. A leading sign is skipped so the + * caller decides sign handling. + */ +static wolfsentry_errcode_t convert_check_magnitude(const char *buf, uint64_t limit) { + uint64_t acc = 0; + uint64_t base = 10; + const char *p = buf; + + while ((*p == ' ') || (*p == '\t')) + ++p; + if ((*p == '-') || (*p == '+')) + ++p; + if ((p[0] == '0') && ((p[1] == 'x') || (p[1] == 'X'))) { + base = 16; + p += 2; + } else if (p[0] == '0') { + base = 8; + ++p; + } + + while (*p != 0) { + uint64_t digit; + if ((*p >= '0') && (*p <= '9')) + digit = (uint64_t)(*p - '0'); + else if ((*p >= 'a') && (*p <= 'f')) + digit = (uint64_t)(*p - 'a' + 10); + else if ((*p >= 'A') && (*p <= 'F')) + digit = (uint64_t)(*p - 'A' + 10); + else + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (digit >= base) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (acc > (limit - digit) / base) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + acc = (acc * base) + digit; + ++p; + } + + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char *data, size_t data_size, uint64_t *out) { + wolfsentry_errcode_t ret; char buf[24]; char *endptr; #ifdef WOLFSENTRY_HAVE_LONG_LONG @@ -187,6 +231,8 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + ret = convert_check_magnitude(buf, (uint64_t)MAX_UINT_OF(*out)); + WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (uint64_t)conv; WOLFSENTRY_RETURN_OK; From 8b15ad98f35d957bdf5c70f74e8bc7aa86075318 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:24:40 -0700 Subject: [PATCH 131/255] F-10488 - https://fenrir.wolfssl.com/finding/10488 - Reject a leading minus sign in unsigned integer config values --- src/json/load_config.c | 13 ++++++++++++ tests/unittests.c | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 430daf24..8ac5f264 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -197,6 +197,13 @@ static wolfsentry_errcode_t convert_check_magnitude(const char *buf, uint64_t li WOLFSENTRY_RETURN_OK; } +/* local equivalent of strspn(p, " \t"), which freestanding targets may lack. */ +static const char *convert_skip_blanks(const char *p) { + while ((*p == ' ') || (*p == '\t')) + ++p; + return p; +} + static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char *data, size_t data_size, uint64_t *out) { wolfsentry_errcode_t ret; char buf[24]; @@ -216,6 +223,12 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * memcpy(buf, data, data_size); buf[data_size] = 0; + /* strtoull()/strtoul() silently negate a leading minus; reject it so a + * negative literal does not wrap to a large unsigned value. + */ + if (*convert_skip_blanks(buf) == '-') + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + #ifndef WOLFSENTRY_NO_ERRNO_H errno = 0; #endif diff --git a/tests/unittests.c b/tests/unittests.c index 645aded7..1cc178fd 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9598,6 +9598,49 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { #endif /* WOLFSENTRY_HAVE_JSON_DOM */ +static wolfsentry_errcode_t test_json_oneshot_string( + const char *json, + wolfsentry_config_load_flags_t load_flags) +{ + wolfsentry_errcode_t ret, load_ret; + struct wolfsentry_context *wolfsentry; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + load_ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, + strlen(json), + load_flags, + NULL, + 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_ERROR_RERETURN(load_ret); +} + +static int test_json_uint64_negative(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"v\" : { \"uint\" : -1 } } }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -10153,6 +10196,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_uint64_negative(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint64_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 7bd579e055fc77f34b2d3dbf2aa961185d20d519 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:25:26 -0700 Subject: [PATCH 132/255] F-4282 - https://fenrir.wolfssl.com/finding/4282 - Reject out-of-range signed integer config values without relying on errno --- src/json/load_config.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 8ac5f264..e944a8a1 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -252,8 +252,11 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * } static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char *data, size_t data_size, int64_t *out) { + wolfsentry_errcode_t ret; char buf[24]; char *endptr; + const char *sign_p; + uint64_t magnitude_limit; #ifdef WOLFSENTRY_HAVE_LONG_LONG long long conv; #else @@ -284,6 +287,13 @@ static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char * if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + sign_p = convert_skip_blanks(buf); + if (*sign_p == '-') + magnitude_limit = (uint64_t)MAX_SINT_OF(*out) + 1U; + else + magnitude_limit = (uint64_t)MAX_SINT_OF(*out); + ret = convert_check_magnitude(buf, magnitude_limit); + WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (int64_t)conv; WOLFSENTRY_RETURN_OK; From 8796b017ba8adeeb8f50b48571467415004b2118 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:26:02 -0700 Subject: [PATCH 133/255] F-4283 - https://fenrir.wolfssl.com/finding/4283 - Use a wider accumulator so the uint32 range check is live on 32-bit ABIs --- src/json/load_config.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index e944a8a1..62d660d6 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -302,7 +302,14 @@ static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char * static wolfsentry_errcode_t convert_uint32(JSON_TYPE type, const unsigned char *data, size_t data_size, uint32_t *out) { char buf[16]; char *endptr; + /* use the widest available unsigned type so the range check below is live + * even on ILP32 ABIs, where unsigned long is the same width as *out. + */ +#ifdef WOLFSENTRY_HAVE_LONG_LONG + unsigned long long conv; +#else unsigned long conv; +#endif /* allow JSON_STRING to accommodate hex and octal values. */ if ((type != JSON_NUMBER) && (type != JSON_STRING)) @@ -316,7 +323,11 @@ static wolfsentry_errcode_t convert_uint32(JSON_TYPE type, const unsigned char * #ifndef WOLFSENTRY_NO_ERRNO_H errno = 0; #endif +#ifdef WOLFSENTRY_HAVE_LONG_LONG + conv = strtoull(buf, &endptr, 0); +#else conv = strtoul(buf, &endptr, 0); +#endif #ifndef WOLFSENTRY_NO_ERRNO_H if (errno != 0) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); From 185a99034fd97713393125387fb2cb6ffd0b3997 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:26:29 -0700 Subject: [PATCH 134/255] F-4278 - https://fenrir.wolfssl.com/finding/4278 - Range check CAN addresses without relying on errno on 32-bit ABIs --- src/json/load_config.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 62d660d6..e45e7233 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -785,7 +785,14 @@ static wolfsentry_errcode_t convert_sockaddr_address( else if (sa->sa_family == WOLFSENTRY_AF_CAN) { /* accept CAN addrs as either strings consisting of hex or octal numbers, or as a JSON-native decimal integer. */ char d_buf[13], *endptr; + /* use the widest available unsigned type so the range check below is + * live even on ILP32 ABIs, where unsigned long equals uint32_t. + */ +#ifdef WOLFSENTRY_HAVE_LONG_LONG + unsigned long long addr; +#else unsigned long addr; +#endif if ((type != JSON_STRING) && (type != JSON_NUMBER)) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (data_size >= sizeof d_buf) @@ -806,7 +813,18 @@ static wolfsentry_errcode_t convert_sockaddr_address( } memcpy(d_buf, data, data_size); d_buf[data_size] = 0; +#ifndef WOLFSENTRY_NO_ERRNO_H + errno = 0; +#endif +#ifdef WOLFSENTRY_HAVE_LONG_LONG + addr = strtoull(d_buf, &endptr, 0); +#else addr = strtoul(d_buf, &endptr, 0); +#endif +#ifndef WOLFSENTRY_NO_ERRNO_H + if (errno != 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); +#endif if ((size_t)(endptr - d_buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (addr > MAX_UINT_OF(uint32_t)) From 12d4fc3548a371183f5a6f387cfab511293f0f60 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:28:07 -0700 Subject: [PATCH 135/255] F-6687 - https://fenrir.wolfssl.com/finding/6687 - Add coverage for uint16 config value range rejection and boundary --- tests/unittests.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 1cc178fd..13bf3f2c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9641,6 +9641,18 @@ static int test_json_uint64_negative(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_uint16_bounds(void) { + static const char too_big[] = + "{ \"wolfsentry-config-version\" : 1, \"events\" : [ { \"label\" : \"e\", \"priority\" : 70000 } ] }"; + static const char at_max[] = + "{ \"wolfsentry-config-version\" : 1, \"events\" : [ { \"label\" : \"e\", \"priority\" : 65535 } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(too_big, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(at_max, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -10201,6 +10213,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_uint64_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_uint16_bounds(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint16_bounds failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From dd6857dc4951c6a1e0b130b68a1f34955610f7bc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:29:08 -0700 Subject: [PATCH 136/255] F-3435 - https://fenrir.wolfssl.com/finding/3435 - Size the uint8 parse buffer to accept hex and octal interface values --- src/json/load_config.c | 2 +- tests/unittests.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index e45e7233..e079419e 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -375,7 +375,7 @@ static wolfsentry_errcode_t convert_uint16(JSON_TYPE type, const unsigned char * } static wolfsentry_errcode_t convert_uint8(JSON_TYPE type, const unsigned char *data, size_t data_size, uint8_t *out) { - char buf[4]; + char buf[8]; char *endptr; unsigned long conv; diff --git a/tests/unittests.c b/tests/unittests.c index 13bf3f2c..9089df4f 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9641,6 +9641,14 @@ static int test_json_uint64_negative(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_uint8_hex_interface(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"interface\" : \"0xff\" } } ] }"; + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_uint16_bounds(void) { static const char too_big[] = "{ \"wolfsentry-config-version\" : 1, \"events\" : [ { \"label\" : \"e\", \"priority\" : 70000 } ] }"; @@ -10218,6 +10226,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_uint16_bounds failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_uint8_hex_interface(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_uint8_hex_interface failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 5f1a5124da4480a235b458dd3c67477027d297c1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:30:16 -0700 Subject: [PATCH 137/255] F-4322 - https://fenrir.wolfssl.com/finding/4322 - Reject negative durations in the JSON loader --- src/json/load_config.c | 6 ++++++ tests/unittests.c | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index e079419e..c9ef1821 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -494,6 +494,12 @@ static wolfsentry_errcode_t convert_wolfsentry_duration(struct wolfsentry_contex } if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + /* durations are non-negative; a negative value (including a LONG_MIN + * saturation when errno is unavailable) would otherwise reach the interval + * conversion and overflow. + */ + if (conv < 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if ((ret = wolfsentry_interval_from_seconds(wolfsentry, conv, 0 /* howlong_nsecs */, out)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); else diff --git a/tests/unittests.c b/tests/unittests.c index 9089df4f..2fcf86ba 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9641,6 +9641,14 @@ static int test_json_uint64_negative(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_duration_negative(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"config-update\" : { \"penalty-box-duration\" : \"-100s\" } }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_uint8_hex_interface(void) { static const char json[] = "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"interface\" : \"0xff\" } } ] }"; @@ -10231,6 +10239,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_uint8_hex_interface failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_duration_negative(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_duration_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From edb477f779635af0ec45f87a6f8328221ff526cf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:35:38 -0700 Subject: [PATCH 138/255] F-4565 - https://fenrir.wolfssl.com/finding/4565 - Reject an explicit LINK64 family when the address byte count is not EUI-64 --- src/json/load_config.c | 7 +++++++ tests/unittests.c | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index c9ef1821..04eafccf 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -719,6 +719,7 @@ static wolfsentry_errcode_t convert_sockaddr_address( byte *sa_addr) { if ((sa->sa_family == WOLFSENTRY_AF_LINK48) || (sa->sa_family == WOLFSENTRY_AF_LINK64)) { + wolfsentry_addr_family_t requested_family = sa->sa_family; int n; if (type != JSON_STRING) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); @@ -729,6 +730,12 @@ static wolfsentry_errcode_t convert_sockaddr_address( WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); ++n; if (data_size == 0) { + /* an explicit EUI-64 family must not be silently reclassified + * as EUI-48 by the parsed byte count (LINK48 and the generic + * LINK alias share a value, so only LINK64 can be enforced). + */ + if ((requested_family == WOLFSENTRY_AF_LINK64) && (n != 8)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (n == 6) sa->sa_family = WOLFSENTRY_AF_LINK48; else if (n == 8) diff --git a/tests/unittests.c b/tests/unittests.c index 2fcf86ba..f1679721 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9641,6 +9641,17 @@ static int test_json_uint64_negative(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_link64_short_address(void) { + /* family 119 == WOLFSENTRY_AF_LINK64; a six-byte EUI-48 address must not be + * silently accepted as LINK64. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 119, \"remote\" : { \"address\" : \"00:11:22:33:44:55\" }, \"local\" : { \"address\" : \"00:11:22:33:44:55\" } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_duration_negative(void) { static const char json[] = "{ \"wolfsentry-config-version\" : 1, \"config-update\" : { \"penalty-box-duration\" : \"-100s\" } }"; @@ -10244,6 +10255,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_duration_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_link64_short_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_link64_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 58d619eea1148a3420f706097f88d2e34cfb840e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:37:12 -0700 Subject: [PATCH 139/255] F-8780 - https://fenrir.wolfssl.com/finding/8780 - Treat a null service lookup result as item-not-found --- src/json/load_config.c | 4 ++++ tests/unittests.c | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 04eafccf..65f53e00 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -925,6 +925,10 @@ static wolfsentry_errcode_t convert_sockaddr_port_name(struct wolfsentry_json_pr WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); } + /* glibc reports an unknown service with a zero return and a NULL result. */ + if (s == NULL) + WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); + sa->sa_port = (wolfsentry_port_t)ntohs((uint16_t)s->s_port); WOLFSENTRY_RETURN_OK; } diff --git a/tests/unittests.c b/tests/unittests.c index f1679721..1c681c3d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9641,6 +9641,16 @@ static int test_json_uint64_negative(void) { WOLFSENTRY_RETURN_OK; } +#ifndef WOLFSENTRY_NO_GETPROTOBY +static int test_json_unknown_service_name(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : 6, \"remote\" : { \"port\" : \"no-such-service-zzz\" } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* !WOLFSENTRY_NO_GETPROTOBY */ + static int test_json_link64_short_address(void) { /* family 119 == WOLFSENTRY_AF_LINK64; a six-byte EUI-48 address must not be * silently accepted as LINK64. @@ -10260,6 +10270,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_link64_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifndef WOLFSENTRY_NO_GETPROTOBY + ret = test_json_unknown_service_name(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_unknown_service_name failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_JSON_CORPUS From 2085b8f32bc2ef4ddf17caea0b8cc7806a309047 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:37:57 -0700 Subject: [PATCH 140/255] F-8781 - https://fenrir.wolfssl.com/finding/8781 - Treat a null protocol lookup result as item-not-found --- src/json/load_config.c | 4 ++++ tests/unittests.c | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 65f53e00..5021dafb 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -1097,6 +1097,10 @@ static wolfsentry_errcode_t handle_route_protocol_clause(struct wolfsentry_json_ WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); } + /* glibc reports an unknown protocol with a zero return and a NULL result. */ + if (p == NULL) + WOLFSENTRY_ERROR_RETURN(ITEM_NOT_FOUND); + jps->o_u_c.route.remote.sa_proto = (wolfsentry_proto_t)p->p_proto; jps->o_u_c.route.local.sa_proto = (wolfsentry_proto_t)p->p_proto; } diff --git a/tests/unittests.c b/tests/unittests.c index 1c681c3d..a37d8ae7 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9649,6 +9649,14 @@ static int test_json_unknown_service_name(void) { test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; } + +static int test_json_unknown_protocol_name(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : \"no-such-protocol-zzz\" } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} #endif /* !WOLFSENTRY_NO_GETPROTOBY */ static int test_json_link64_short_address(void) { @@ -10276,6 +10284,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_unknown_service_name failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_unknown_protocol_name(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_unknown_protocol_name failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #endif From 85c7175eb67768498832b643196868fa7aaee471 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:40:27 -0700 Subject: [PATCH 141/255] F-3666 - https://fenrir.wolfssl.com/finding/3666 - Right-justify a bitmask address by byte count to prevent an out-of-bounds write --- src/json/load_config.c | 10 ++++++++-- tests/unittests.c | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 5021dafb..d977e096 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -984,8 +984,14 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ * move it to right-justified position. */ if ((sa->addr_len != 0) && (sa->addr_len != addr_size)) { - size_t pad_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)addr_size - (size_t)sa->addr_len); - memmove(sa->addr + pad_bytes, sa->addr, sa->addr_len); + size_t addr_bytes; + size_t pad_bytes; + if (sa->addr_len > addr_size) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + /* addr_len is a bit count; the move operates on whole bytes. */ + addr_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)sa->addr_len); + pad_bytes = WOLFSENTRY_BITS_TO_BYTES((size_t)addr_size) - addr_bytes; + memmove(sa->addr + pad_bytes, sa->addr, addr_bytes); memset(sa->addr, 0, pad_bytes); } diff --git a/tests/unittests.c b/tests/unittests.c index a37d8ae7..12e0aa59 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9659,6 +9659,20 @@ static int test_json_unknown_protocol_name(void) { } #endif /* !WOLFSENTRY_NO_GETPROTOBY */ +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static int test_json_bitmask_short_address(void) { + /* a partial address (prefix-bits smaller than the family width) followed by + * a bitmask exercises the right-justification move; the move must copy + * bytes, not the bit count, or it corrupts adjacent parse state. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"prefix-bits\" : 16, \"address\" : \"1.2.0.0\", \"bitmask\" : \"255.255.0.0\" } } ] }"; + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + static int test_json_link64_short_address(void) { /* family 119 == WOLFSENTRY_AF_LINK64; a six-byte EUI-48 address must not be * silently accepted as LINK64. @@ -10273,6 +10287,13 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_duration_negative failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + ret = test_json_bitmask_short_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_bitmask_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif ret = test_json_link64_short_address(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_link64_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From 70e32a3d573591096693dc58110f68c103cc14af Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:41:31 -0700 Subject: [PATCH 142/255] F-3873 - https://fenrir.wolfssl.com/finding/3873 - Bound prefix-bits to the address family width in the JSON loader --- src/json/load_config.c | 13 ++++++++++++- tests/unittests.c | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index d977e096..f83fbffd 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -1008,6 +1008,8 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ } #endif else if (! strcmp(jps->cur_keyname, "prefix-bits")) { + wolfsentry_addr_bits_t max_addr_bits; + wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING @@ -1020,7 +1022,16 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ WOLFSENTRY_ERROR_RETURN(CONFIG_MISPLACED_KEY); } #endif - WOLFSENTRY_ERROR_RERETURN(convert_uint16(type, data, data_size, &sa->addr_len)); + ret = convert_uint16(type, data, data_size, &sa->addr_len); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + /* bound prefix-bits to the address family width so a later copy cannot + * read past the fixed-size staging buffer. + */ + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (sa->addr_len > max_addr_bits) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + WOLFSENTRY_RETURN_OK; } else if (! strcmp(jps->cur_keyname, "interface")) { WOLFSENTRY_CLEAR_BITS(jps->o_u_c.route.flags, diff --git a/tests/unittests.c b/tests/unittests.c index 12e0aa59..12ab1d67 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9673,6 +9673,17 @@ static int test_json_bitmask_short_address(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static int test_json_prefix_bits_overbound(void) { + /* prefix-bits beyond the family width must be rejected before the value can + * drive an out-of-bounds copy from the fixed staging buffer. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"address\" : \"1.2.3.4\", \"prefix-bits\" : 65535 } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_link64_short_address(void) { /* family 119 == WOLFSENTRY_AF_LINK64; a six-byte EUI-48 address must not be * silently accepted as LINK64. @@ -10294,6 +10305,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_prefix_bits_overbound(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_prefix_bits_overbound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } ret = test_json_link64_short_address(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_link64_short_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From d9fa5a0304e4dac469c5746991e28f603a3895d1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:43:12 -0700 Subject: [PATCH 143/255] F-13290 - https://fenrir.wolfssl.com/finding/13290 - Route the actions-update rejection through the parser error-position epilogue --- src/json/load_config.c | 2 +- tests/unittests.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index f83fbffd..88a726fe 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -1769,7 +1769,7 @@ static wolfsentry_errcode_t json_process( WOLFSENTRY_RETURN_OK; } if (! strcmp(jps->cur_keyname, "actions-update")) { - WOLFSENTRY_ERROR_RETURN(CONFIG_MISSING_HANDLER); + WOLFSENTRY_ERROR_OUT(CONFIG_MISSING_HANDLER); /* jps->table_under_construction = T_U_C_ACTIONS; WOLFSENTRY_RETURN_OK; diff --git a/tests/unittests.c b/tests/unittests.c index 12ab1d67..38678a41 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9673,6 +9673,14 @@ static int test_json_bitmask_short_address(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static int test_json_actions_update_rejected(void) { + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"actions-update\" : [ ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_prefix_bits_overbound(void) { /* prefix-bits beyond the family width must be rejected before the value can * drive an out-of-bounds copy from the fixed staging buffer. @@ -10305,6 +10313,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_actions_update_rejected(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_actions_update_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } ret = test_json_prefix_bits_overbound(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_prefix_bits_overbound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From 26af1c11f0268145621979215d0684f37b205393 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:45:42 -0700 Subject: [PATCH 144/255] F-2441 - https://fenrir.wolfssl.com/finding/2441 - Fail closed when a default-mode JSON config load fails after flushing --- src/json/load_config.c | 20 ++++++++++++++++ tests/unittests.c | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/json/load_config.c b/src/json/load_config.c index 88a726fe..9a56543b 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -2173,6 +2173,26 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_fini( if ((*jps)->wolfsentry && ((*jps)->wolfsentry != (*jps)->wolfsentry_actual)) WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_free(JPSP_P_WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* In the default (non-transactional) load mode the live context was flushed + * before parsing; a failure leaves it partially populated while its default + * policy is unchanged. Fail closed so a half-loaded policy cannot admit + * traffic: clear the routes and set a REJECT default policy. + */ + if ((ret < 0) && + (! WOLFSENTRY_MASKIN_BITS((*jps)->load_flags, + WOLFSENTRY_CONFIG_LOAD_FLAG_DRY_RUN | + WOLFSENTRY_CONFIG_LOAD_FLAG_LOAD_THEN_COMMIT | + WOLFSENTRY_CONFIG_LOAD_FLAG_NO_FLUSH))) + { + struct wolfsentry_route_table *main_table; + wolfsentry_action_res_t action_results; + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + if (wolfsentry_route_get_main_table(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &main_table) >= 0) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_flush_table(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, main_table, &action_results)); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_table_default_policy_set(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, main_table, WOLFSENTRY_ACTION_RES_REJECT)); + } + } + #ifdef WOLFSENTRY_THREADSAFE { wolfsentry_errcode_t _lock_ret; diff --git a/tests/unittests.c b/tests/unittests.c index 38678a41..ee69322c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9673,6 +9673,53 @@ static int test_json_bitmask_short_address(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static int test_json_failed_load_fails_closed(void) { + wolfsentry_errcode_t ret; + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t default_policy = WOLFSENTRY_ACTION_RES_NONE; + static const char good[] = + "{ \"wolfsentry-config-version\" : 1, \"default-policies\" : { \"default-policy\" : \"accept\" } }"; + static const char bad[] = + "{ \"wolfsentry-config-version\" : 1, \"user-values\" : { \"x\" : { \"uint\" : -1 } } }"; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)good, strlen(good), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); + + /* a failing default-mode load must leave the context fail-closed. */ + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)bad, strlen(bad), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_get(WOLFSENTRY_CONTEXT_ARGS_OUT, &default_policy)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FALSE(default_policy == WOLFSENTRY_ACTION_RES_REJECT); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_RETURN_OK; +} + static int test_json_actions_update_rejected(void) { static const char json[] = "{ \"wolfsentry-config-version\" : 1, \"actions-update\" : [ ] }"; @@ -10313,6 +10360,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_failed_load_fails_closed(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_failed_load_fails_closed failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } ret = test_json_actions_update_rejected(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_actions_update_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From c73e294bd7094fb8e58a83cf54c004f64e84d633 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:46:58 -0700 Subject: [PATCH 145/255] F-15478 - https://fenrir.wolfssl.com/finding/15478 - Re-enable actions on a post-exchange LOAD_THEN_COMMIT failure --- src/json/load_config.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 9a56543b..5104fbb3 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -2145,13 +2145,21 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_config_json_fini( if (full_cycle_of_insert_actions_p) { wolfsentry_action_res_t action_results = WOLFSENTRY_ACTION_RES_NONE; - if ((ret = wolfsentry_route_bulk_clear_insert_action_status(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &action_results)) < 0) - goto out; - if ((ret = wolfsentry_context_flush(JPSP_WOLFSENTRY_CONTEXT_ARGS_OUT)) < 0) + ret = wolfsentry_route_bulk_clear_insert_action_status(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT, &action_results); + if (ret >= 0) + ret = wolfsentry_context_flush(JPSP_WOLFSENTRY_CONTEXT_ARGS_OUT); + /* the candidate is already live after the exchange; re-enable its + * actions even if the preceding steps failed, so a post-exchange + * failure cannot leave the running configuration action-inhibited. + * keep the first error. + */ + if (ret >= 0) + ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT); + else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)); + if (ret < 0) goto out; - } - - if ((ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)) < 0) + } else if ((ret = wolfsentry_context_enable_actions(JPSP_WOLFSENTRY_ACTUAL_CONTEXT_ARGS_OUT)) < 0) goto out; { From efe5c330d1d7ead29ee101b71409083b7b08aa6c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:48:34 -0700 Subject: [PATCH 146/255] F-2453 - https://fenrir.wolfssl.com/finding/2453 - Parse AF_LOCAL path addresses so JSON dump and reload stay symmetric --- src/json/load_config.c | 51 ++++++++++++++++++++++++++++-- tests/unittests.c | 70 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 5104fbb3..2611799a 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -850,6 +850,35 @@ static wolfsentry_errcode_t convert_sockaddr_address( sa->addr_len = 32; WOLFSENTRY_RETURN_OK; } + else if (sa->sa_family == WOLFSENTRY_AF_LOCAL) { + /* mirror wolfsentry_route_format_address(): the address is a raw path + * stored verbatim, with addr_len recording its length. + */ + size_t i; + if (type != JSON_STRING) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + /* an empty path would be a zero-length, match-all prefix. */ + if (data_size == 0) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if (data_size > (size_t)WOLFSENTRY_BITS_TO_BYTES(WOLFSENTRY_MAX_ADDR_BITS)) + WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); + /* the route formatter emits the path verbatim between quotes without + * escaping, so reject bytes that would corrupt or inject into that + * dump (quote, backslash, control bytes including NUL). + */ + for (i = 0; i < data_size; ++i) { + if ((data[i] < 0x20) || (data[i] == 0x7f) || (data[i] == '"') || (data[i] == '\\')) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + } + /* addr_len must equal the pathname length, so a prefix-bits supplied + * before the address cannot leave trailing implicit bytes in the dump. + */ + if ((sa->addr_len != 0) && (sa->addr_len != (wolfsentry_addr_bits_t)(data_size * 8))) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + memcpy(sa_addr, data, data_size); + sa->addr_len = (wolfsentry_addr_bits_t)(data_size * 8); + WOLFSENTRY_RETURN_OK; + } else if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) { WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); } @@ -1009,6 +1038,7 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ #endif else if (! strcmp(jps->cur_keyname, "prefix-bits")) { wolfsentry_addr_bits_t max_addr_bits; + wolfsentry_addr_bits_t prior_addr_len = sa->addr_len; wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); @@ -1025,10 +1055,25 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ ret = convert_uint16(type, data, data_size, &sa->addr_len); WOLFSENTRY_RERETURN_IF_ERROR(ret); /* bound prefix-bits to the address family width so a later copy cannot - * read past the fixed-size staging buffer. + * read past the fixed-size staging buffer. AF_LOCAL path addresses have + * no fixed family width (and no entry in the family table), so bound + * them by the staging buffer, matching the formatter's dump output. */ - ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); - WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (sa->sa_family == WOLFSENTRY_AF_LOCAL) { + max_addr_bits = WOLFSENTRY_MAX_ADDR_BITS; + /* an AF_LOCAL prefix-bits only denotes the pathname length, so it + * must be nonzero whole bytes and match any pathname already parsed; + * keeping it nonzero leaves addr_len == 0 meaning "not supplied". + */ + if ((sa->addr_len == 0) || (sa->addr_len & 0x7)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + if ((prior_addr_len != 0) && (sa->addr_len != prior_addr_len)) + WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); + } + else { + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + } if (sa->addr_len > max_addr_bits) WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); WOLFSENTRY_RETURN_OK; diff --git a/tests/unittests.c b/tests/unittests.c index ee69322c..4f0c360b 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9673,6 +9673,71 @@ static int test_json_bitmask_short_address(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static int test_json_af_local_address(void) { + /* family 1 == WOLFSENTRY_AF_LOCAL; the loader must parse the path address + * that the formatter emits, keeping dump/reload symmetric. + */ + static const char json[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/sock\" } } ] }"; + /* the formatter dumps AF_LOCAL routes with an explicit prefix-bits; that + * dumped form must reload, which requires the loader to accept prefix-bits + * for AF_LOCAL ("/tmp/sock" is 9 bytes == 72 bits). + */ + static const char json_dump_form[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/sock\", \"prefix-bits\" : 72 } } ] }"; + /* a path containing a JSON-special byte would dump unescaped and corrupt + * the output, so it must be rejected at load. + */ + static const char json_unsafe[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/tmp/a\\\"b\" } } ] }"; + /* a prefix-bits inconsistent with the pathname length (here 2 bytes == 16 + * bits) would leave implicit trailing bytes, so it must be rejected in + * either key order; a non-byte-aligned prefix must also be rejected. + */ + static const char json_overlong_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 128 } } ] }"; + static const char json_prefix_first[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 128, \"address\" : \"/x\" } } ] }"; + static const char json_unaligned_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 12 } } ] }"; + /* an empty path or a zero prefix would make a zero-length, match-all + * address with the wildcard flag cleared; reject both in either order. + */ + static const char json_empty_path[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"\" } } ] }"; + static const char json_empty_path_then_prefix[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"\", \"prefix-bits\" : 128 } } ] }"; + static const char json_prefix_then_empty_path[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 128, \"address\" : \"\" } } ] }"; + static const char json_zero_prefix_first[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"prefix-bits\" : 0, \"address\" : \"/x\" } } ] }"; + static const char json_zero_prefix_last[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 1, \"remote\" : { \"address\" : \"/x\", \"prefix-bits\" : 0 } } ] }"; + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_empty_path, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_empty_path_then_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_prefix_then_empty_path, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_zero_prefix_first, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_zero_prefix_last, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE( + test_json_oneshot_string(json_dump_form, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_unsafe, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_overlong_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_prefix_first, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_SUCCESS( + test_json_oneshot_string(json_unaligned_prefix, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_json_failed_load_fails_closed(void) { wolfsentry_errcode_t ret; struct wolfsentry_context *wolfsentry; @@ -10360,6 +10425,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_af_local_address(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_af_local_address failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } ret = test_json_failed_load_fails_closed(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_failed_load_fails_closed failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From 4a3b93aa5a55d396359e45437460cbd800c7bea6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:42 -0700 Subject: [PATCH 147/255] F-2854 - https://fenrir.wolfssl.com/finding/2854 - Keep an explicit tcplike-port-numbers setting over the protocol default --- src/json/load_config.c | 13 ++++++-- tests/unittests.c | 76 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 2611799a..e9f1b731 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -111,6 +111,7 @@ struct wolfsentry_json_process_state { WOLFSENTRY_SOCKADDR(WOLFSENTRY_MAX_ADDR_BITS) remote; WOLFSENTRY_SOCKADDR(WOLFSENTRY_MAX_ADDR_BITS) local; wolfsentry_route_flags_t flags; + int tcplike_port_numbers_explicit; } route; struct { char label[WOLFSENTRY_MAX_LABEL_BYTES]; @@ -1170,7 +1171,9 @@ static wolfsentry_errcode_t handle_route_protocol_clause(struct wolfsentry_json_ else WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); - if (((jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET) || (jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET6)) && + /* implicit default only; an explicit tcplike-port-numbers setting wins. */ + if ((! jps->o_u_c.route.tcplike_port_numbers_explicit) && + ((jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET) || (jps->o_u_c.route.remote.sa_family == WOLFSENTRY_AF_INET6)) && ((jps->o_u_c.route.remote.sa_proto == IPPROTO_TCP) || (jps->o_u_c.route.remote.sa_proto == IPPROTO_UDP))) WOLFSENTRY_SET_BITS(jps->o_u_c.route.flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS); @@ -1253,8 +1256,12 @@ static wolfsentry_errcode_t handle_route_clause(struct wolfsentry_json_process_s else { wolfsentry_route_flags_t flag; ret = wolfsentry_route_flag_assoc_by_name(jps->cur_keyname, WOLFSENTRY_LENGTH_NULL_TERMINATED, &flag); - if (ret >= 0) - WOLFSENTRY_ERROR_RERETURN(handle_route_boolean_clause(type, &jps->o_u_c.route.flags, flag)); + if (ret >= 0) { + ret = handle_route_boolean_clause(type, &jps->o_u_c.route.flags, flag); + if ((ret >= 0) && (flag == WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)) + jps->o_u_c.route.tcplike_port_numbers_explicit = 1; + WOLFSENTRY_ERROR_RERETURN(ret); + } else { if (WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_KEY); diff --git a/tests/unittests.c b/tests/unittests.c index 4f0c360b..3364549e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9843,6 +9843,77 @@ static int test_json_uint16_bounds(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_load_one_route_flags( + const char *json, + wolfsentry_route_flags_t *route_flags) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, strlen(json), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, NULL, 0)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_current(main_routes, cursor, &route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_flags(route, route_flags)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + ret = WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + WOLFSENTRY_RETURN_OK; +} + +static int test_json_tcplike_explicit_flag(void) { + /* family 2 == INET, protocol 6 == TCP; the flag key precedes "protocol", + * as in the route dump, so an explicit setting must survive the protocol + * clause's implicit default. + */ + static const char explicit_false[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"tcplike-port-numbers\" : false, \"protocol\" : 6 } ] }"; + static const char explicit_true[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"tcplike-port-numbers\" : true, \"protocol\" : 6 } ] }"; + static const char omitted[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"protocol\" : 6 } ] }"; + wolfsentry_route_flags_t route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(explicit_false, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(! WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); + + route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(explicit_true, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); + + route_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_load_one_route_flags(omitted, &route_flags), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -10462,6 +10533,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif + ret = test_json_tcplike_explicit_flag(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_tcplike_explicit_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 891d0b66ded8b25c0a4f52e4146fdd84261c2573 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 09:57:58 -0700 Subject: [PATCH 148/255] F-3873 - https://fenrir.wolfssl.com/finding/3873 - Judge a generic LINK prefix-bits the same before or after its address --- src/json/load_config.c | 31 +++++++++++++++++++++++++++++-- tests/unittests.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index e9f1b731..45dfc77d 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -982,13 +982,34 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ else WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); } else if (! strcmp(jps->cur_keyname, "address")) { + wolfsentry_addr_bits_t prior_addr_len = sa->addr_len; + wolfsentry_addr_bits_t max_addr_bits; + int prefix_given = (prior_addr_len != 0); + wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + /* a bitmask also sets addr_len, to the combined address and mask width */ + if (jps->o_u_c.route.flags & (sa == (struct wolfsentry_sockaddr *)&jps->o_u_c.route.remote ? + WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK : + WOLFSENTRY_ROUTE_FLAG_LOCAL_ADDR_BITMASK)) + prefix_given = 0; +#endif WOLFSENTRY_CLEAR_BITS(jps->o_u_c.route.flags, sa == (struct wolfsentry_sockaddr *)&jps->o_u_c.route.remote ? WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD : WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD); - WOLFSENTRY_ERROR_RERETURN(convert_sockaddr_address(jps, type, data, data_size, sa, sa->addr)); + ret = convert_sockaddr_address(jps, type, data, data_size, sa, sa->addr); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + /* an earlier prefix-bits on generic LINK was allowed the EUI-64 width; + * now that the address has settled the family, the prefix must fit it. */ + if (prefix_given && (sa->sa_family == WOLFSENTRY_AF_LINK48)) { + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (prior_addr_len > max_addr_bits) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + } + WOLFSENTRY_RETURN_OK; } #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING else if (! strcmp(jps->cur_keyname, "bitmask")) { @@ -1040,6 +1061,7 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ else if (! strcmp(jps->cur_keyname, "prefix-bits")) { wolfsentry_addr_bits_t max_addr_bits; wolfsentry_addr_bits_t prior_addr_len = sa->addr_len; + wolfsentry_addr_family_t width_family = sa->sa_family; wolfsentry_errcode_t ret; if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); @@ -1072,7 +1094,12 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); } else { - ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, &max_addr_bits); + /* generic LINK becomes EUI-48 or EUI-64 from the address length, so + * before an address arrives allow the wider width; the address + * handler then checks the prefix against the settled family. */ + if ((width_family == WOLFSENTRY_AF_LINK48) && (prior_addr_len == 0)) + width_family = WOLFSENTRY_AF_LINK64; + ret = wolfsentry_addr_family_max_addr_bits(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, width_family, &max_addr_bits); WOLFSENTRY_RERETURN_IF_ERROR(ret); } if (sa->addr_len > max_addr_bits) diff --git a/tests/unittests.c b/tests/unittests.c index 3364549e..cbeef4dd 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9914,6 +9914,40 @@ static int test_json_tcplike_explicit_flag(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_link_prefix_bits_key_order(void) { + static const char fmt[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : %u," + " \"remote\" : { %s }, \"local\" : { \"address\" : \"%s\" } } ] }"; + static const char eui64[] = "00:11:22:33:44:55:66:77"; + static const char eui48[] = "00:11:22:33:44:55"; + char json[512]; + int json_len; + + /* generic LINK resolves to EUI-48 or EUI-64 from the address, so a + * prefix-bits key must be judged the same before or after the address. */ + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 64, \"address\" : \"00:11:22:33:44:55:66:77\"", eui64); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"address\" : \"00:11:22:33:44:55:66:77\", \"prefix-bits\" : 64", eui64); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 64, \"address\" : \"00:11:22:33:44:55\"", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), NUMERIC_ARG_TOO_BIG)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"address\" : \"00:11:22:33:44:55\", \"prefix-bits\" : 64", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), NUMERIC_ARG_TOO_BIG)); + + json_len = snprintf(json, sizeof json, fmt, (unsigned int)WOLFSENTRY_AF_LINK, "\"prefix-bits\" : 48, \"address\" : \"00:11:22:33:44:55\"", eui48); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE), OK)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -10538,6 +10572,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_tcplike_explicit_flag failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_link_prefix_bits_key_order(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_link_prefix_bits_key_order failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From ad8a630ce73b701c7eef62263d8b39c1ce374894 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 10:31:05 -0700 Subject: [PATCH 149/255] F-2453 - https://fenrir.wolfssl.com/finding/2453 - Defer AF_LOCAL addresses to a registered handler and keep the path limit byte-aligned --- src/json/load_config.c | 15 +++++-- tests/unittests.c | 93 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 45dfc77d..1ca46ac1 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -711,6 +711,13 @@ static inline int convert_hex_byte(const unsigned char **in, size_t *in_len, byt return 0; } +/* a handler registered for AF_LOCAL takes precedence over the built-in literal path */ +static int af_local_is_builtin(struct wolfsentry_json_process_state *jps) +{ + wolfsentry_addr_family_parser_t parser; + return (wolfsentry_addr_family_get_parser(JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_AF_LOCAL, &parser) < 0); +} + static wolfsentry_errcode_t convert_sockaddr_address( struct wolfsentry_json_process_state *jps, JSON_TYPE type, @@ -851,7 +858,7 @@ static wolfsentry_errcode_t convert_sockaddr_address( sa->addr_len = 32; WOLFSENTRY_RETURN_OK; } - else if (sa->sa_family == WOLFSENTRY_AF_LOCAL) { + else if ((sa->sa_family == WOLFSENTRY_AF_LOCAL) && af_local_is_builtin(jps)) { /* mirror wolfsentry_route_format_address(): the address is a raw path * stored verbatim, with addr_len recording its length. */ @@ -861,7 +868,7 @@ static wolfsentry_errcode_t convert_sockaddr_address( /* an empty path would be a zero-length, match-all prefix. */ if (data_size == 0) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); - if (data_size > (size_t)WOLFSENTRY_BITS_TO_BYTES(WOLFSENTRY_MAX_ADDR_BITS)) + if (data_size > (size_t)(WOLFSENTRY_MAX_ADDR_BITS / 8)) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); /* the route formatter emits the path verbatim between quotes without * escaping, so reject bytes that would corrupt or inject into that @@ -1082,8 +1089,8 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ * no fixed family width (and no entry in the family table), so bound * them by the staging buffer, matching the formatter's dump output. */ - if (sa->sa_family == WOLFSENTRY_AF_LOCAL) { - max_addr_bits = WOLFSENTRY_MAX_ADDR_BITS; + if ((sa->sa_family == WOLFSENTRY_AF_LOCAL) && af_local_is_builtin(jps)) { + max_addr_bits = (wolfsentry_addr_bits_t)((WOLFSENTRY_MAX_ADDR_BITS / 8) * 8); /* an AF_LOCAL prefix-bits only denotes the pathname length, so it * must be nonzero whole bytes and match any pathname already parsed; * keeping it nonzero leaves addr_len == 0 meaning "not supplied". diff --git a/tests/unittests.c b/tests/unittests.c index cbeef4dd..940ad1f7 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9948,6 +9948,94 @@ static int test_json_link_prefix_bits_key_order(void) { WOLFSENTRY_RETURN_OK; } +static int af_local_custom_parser_calls; + +static wolfsentry_errcode_t af_local_custom_parser( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *addr_text, + int addr_text_len, + byte *addr_internal, + wolfsentry_addr_bits_t *addr_internal_bits) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)addr_text; + (void)addr_text_len; + ++af_local_custom_parser_calls; + addr_internal[0] = 0x5a; + *addr_internal_bits = 8; + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t af_local_custom_formatter( + WOLFSENTRY_CONTEXT_ARGS_IN, + const byte *addr_internal, + unsigned int addr_internal_bits, + char *addr_text, + int *addr_text_len) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)addr_internal; + (void)addr_internal_bits; + if (*addr_text_len < 1) + WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); + addr_text[0] = 'z'; + *addr_text_len = 1; + WOLFSENTRY_RETURN_OK; +} + +static int test_json_af_local_registered_handler(void) { + struct wolfsentry_context *wolfsentry; + char json[256]; + int json_len; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_AF_LOCAL, + "test_local", + -1, + af_local_custom_parser, + af_local_custom_formatter, + 8)); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : %u, \"remote\" : { \"address\" : \"anything\" } } ] }", + (unsigned int)WOLFSENTRY_AF_LOCAL); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + + af_local_custom_parser_calls = 0; + (void)wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + NULL, + 0); + WOLFSENTRY_EXIT_ON_FALSE(af_local_custom_parser_calls == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -10577,6 +10665,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_link_prefix_bits_key_order failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_json_af_local_registered_handler(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_af_local_registered_handler failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From affa9dc82eabd52061782fe38c6615109a200174 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:38:37 -0700 Subject: [PATCH 150/255] F-2453 - https://fenrir.wolfssl.com/finding/2453 - Check the config load result in the AF_LOCAL handler test --- tests/unittests.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 940ad1f7..0947032d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -10020,13 +10020,14 @@ static int test_json_af_local_registered_handler(void) { WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); af_local_custom_parser_calls = 0; - (void)wolfsentry_config_json_oneshot( - WOLFSENTRY_CONTEXT_ARGS_OUT, - (const unsigned char *)json, - (size_t)json_len, - WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, - NULL, - 0); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (const unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + NULL, + 0)); WOLFSENTRY_EXIT_ON_FALSE(af_local_custom_parser_calls == 1); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); From 36cb033e09339a8ce365a8b5b62152950dbc2337 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:23:06 -0700 Subject: [PATCH 151/255] F-4277 - https://fenrir.wolfssl.com/finding/4277 - Take the 64-bit config value range limits from the conversion result type --- src/json/load_config.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index 1ca46ac1..e84c28ed 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -245,7 +245,7 @@ static wolfsentry_errcode_t convert_uint64(JSON_TYPE type, const unsigned char * if ((size_t)(endptr - buf) != data_size) WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); - ret = convert_check_magnitude(buf, (uint64_t)MAX_UINT_OF(*out)); + ret = convert_check_magnitude(buf, (uint64_t)MAX_UINT_OF(conv)); WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (uint64_t)conv; @@ -290,9 +290,9 @@ static wolfsentry_errcode_t convert_sint64(JSON_TYPE type, const unsigned char * WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); sign_p = convert_skip_blanks(buf); if (*sign_p == '-') - magnitude_limit = (uint64_t)MAX_SINT_OF(*out) + 1U; + magnitude_limit = (uint64_t)MAX_SINT_OF(conv) + 1U; else - magnitude_limit = (uint64_t)MAX_SINT_OF(*out); + magnitude_limit = (uint64_t)MAX_SINT_OF(conv); ret = convert_check_magnitude(buf, magnitude_limit); WOLFSENTRY_RERETURN_IF_ERROR(ret); *out = (int64_t)conv; From b7214efce493e84a9b51298cb68ef7b615e1d04b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:39:34 -0700 Subject: [PATCH 152/255] F-3666 - https://fenrir.wolfssl.com/finding/3666 - Refuse a bitmask after prefix-bits on the same endpoint --- src/json/load_config.c | 14 ++++++++++++++ tests/unittests.c | 15 ++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index e84c28ed..dc142d26 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -112,6 +112,8 @@ struct wolfsentry_json_process_state { WOLFSENTRY_SOCKADDR(WOLFSENTRY_MAX_ADDR_BITS) local; wolfsentry_route_flags_t flags; int tcplike_port_numbers_explicit; + int remote_prefix_bits_given; + int local_prefix_bits_given; } route; struct { char label[WOLFSENTRY_MAX_LABEL_BYTES]; @@ -1026,6 +1028,14 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ if (sa->sa_family == WOLFSENTRY_AF_UNSPEC) WOLFSENTRY_ERROR_RETURN(CONFIG_OUT_OF_SEQUENCE); + /* the right-justify below would keep only the prefix-sized leading bytes of a full address */ + if ((jps->section_under_construction == S_U_C_REMOTE_ENDPOINT) ? + jps->o_u_c.route.remote_prefix_bits_given : + jps->o_u_c.route.local_prefix_bits_given) + { + WOLFSENTRY_ERROR_RETURN(CONFIG_MISPLACED_KEY); + } + ret = wolfsentry_addr_family_max_addr_bits( JPS_WOLFSENTRY_CONTEXT_ARGS_OUT, sa->sa_family, @@ -1111,6 +1121,10 @@ static wolfsentry_errcode_t handle_route_endpoint_clause(struct wolfsentry_json_ } if (sa->addr_len > max_addr_bits) WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); + if (jps->section_under_construction == S_U_C_REMOTE_ENDPOINT) + jps->o_u_c.route.remote_prefix_bits_given = 1; + else + jps->o_u_c.route.local_prefix_bits_given = 1; WOLFSENTRY_RETURN_OK; } else if (! strcmp(jps->cur_keyname, "interface")) { diff --git a/tests/unittests.c b/tests/unittests.c index 0947032d..41b146e9 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9661,14 +9661,19 @@ static int test_json_unknown_protocol_name(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING static int test_json_bitmask_short_address(void) { - /* a partial address (prefix-bits smaller than the family width) followed by - * a bitmask exercises the right-justification move; the move must copy - * bytes, not the bit count, or it corrupts adjacent parse state. + /* prefix-bits before a bitmask would right-justify only the prefix-sized + * leading bytes of a full-width address, so the combination is refused, + * as prefix-bits after a bitmask already is; address then bitmask loads. */ - static const char json[] = + static const char json_prefixed[] = "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"prefix-bits\" : 16, \"address\" : \"1.2.0.0\", \"bitmask\" : \"255.255.0.0\" } } ] }"; + static const char json_plain[] = + "{ \"wolfsentry-config-version\" : 1, \"routes\" : [ { \"direction-in\" : true, \"family\" : 2, \"remote\" : { \"address\" : \"1.2.0.0\", \"bitmask\" : \"255.255.0.0\" } } ] }"; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + CONFIG_MISPLACED_KEY, + test_json_oneshot_string(json_prefixed, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); WOLFSENTRY_EXIT_ON_FAILURE( - test_json_oneshot_string(json, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); + test_json_oneshot_string(json_plain, WOLFSENTRY_CONFIG_LOAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ From 21e71582569e0ff2275cbe11d1a00ea80114f316 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:29:52 -0700 Subject: [PATCH 153/255] F-4562 - https://fenrir.wolfssl.com/finding/4562 - Fix byte/bit unit mismatch in bitmask address match scoring --- src/routes.c | 4 +- tests/unittests.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index 4cee7486..28ecc399 100644 --- a/src/routes.c +++ b/src/routes.c @@ -304,12 +304,12 @@ static inline int addr_bitmask_match_size( longer_addr = b; mask_len = WOLFSENTRY_BITS_TO_BYTES(b_len >> 1); shorter_addr = a; - padding_offset = mask_len - a_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(a_len); } else { longer_addr = a; mask_len = WOLFSENTRY_BITS_TO_BYTES(a_len >> 1); shorter_addr = b; - padding_offset = mask_len - b_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(b_len); } for (ret = 0, i = 0; i < mask_len; ++i) { diff --git a/tests/unittests.c b/tests/unittests.c index 41b146e9..b0a46a46 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6352,6 +6352,115 @@ static int test_dispatch_insert_delete_already_guard(void) { WOLFSENTRY_RETURN_OK; } +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING +static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t route_id, broad_id, narrow_id; + struct wolfsentry_route_exports broad_route, narrow_route; + byte broad_addr[8], narrow_addr[8]; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + static const wolfsentry_route_flags_t common_flags = + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_REMOTE_ADDR_BITMASK + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&broad_route, 0, sizeof broad_route); + memset(&narrow_route, 0, sizeof narrow_route); + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + + /* broad bitmask route 10.0.0.0/255.0.0.0, greenlisted (accept). */ + memcpy(broad_addr, "\x0A\x00\x00\x00\xFF\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + broad_route.sa_family = AF_INET; + broad_route.sa_proto = IPPROTO_TCP; + broad_route.remote_address = broad_addr; + broad_route.remote.addr_len = 64; + broad_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_GREENLISTED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + + /* narrow bitmask route 10.1.2.0/255.255.255.0, penaltyboxed (reject). */ + memcpy(narrow_addr, "\x0A\x01\x02\x00\xFF\xFF\xFF\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + narrow_route.sa_family = AF_INET; + narrow_route.sa_proto = IPPROTO_TCP; + narrow_route.remote_address = narrow_addr; + narrow_route.remote.addr_len = 64; + narrow_route.flags = common_flags | WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + (void)broad_id; + + /* 10.1.2.3 matches both; the more specific /24 route must win, so the + * verdict is reject, not accept. + */ + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x01\x02\x03", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} +#endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ + +static int test_dynamic_rules_regressions(void) { +#ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); +#endif + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -10506,6 +10615,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dispatch_insert_delete_already_guard failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_dynamic_rules_regressions(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_dynamic_rules_regressions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 0f35126915d7dd20745f2b275abb8d1a961691f7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:30:26 -0700 Subject: [PATCH 154/255] F-4705 - https://fenrir.wolfssl.com/finding/4705 - Compute bitmask compare padding offset in bytes not bits --- src/routes.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/routes.c b/src/routes.c index 28ecc399..48519de2 100644 --- a/src/routes.c +++ b/src/routes.c @@ -248,17 +248,16 @@ static inline int cmp_addrs_bitmaskful( if (left_addr_len < right_addr_len) { maskful_addr = right_addr; - mask_len = right_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(right_addr_len >> 1); target_addr = left_addr; - padding_offset = mask_len - left_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(left_addr_len); } else { maskful_addr = left_addr; - mask_len = left_addr_len >> 1; + mask_len = WOLFSENTRY_BITS_TO_BYTES(left_addr_len >> 1); target_addr = right_addr; - padding_offset = mask_len - right_addr_len; + padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(right_addr_len); } - mask_len = WOLFSENTRY_BITS_TO_BYTES(mask_len); mask = maskful_addr + mask_len; for (i = 0; i < mask_len; ++i) { From 3158353bb6b2973a9d0d5d7214a9d1009aeac2e4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:33:19 -0700 Subject: [PATCH 155/255] F-8764 - https://fenrir.wolfssl.com/finding/8764 - Preserve connected routes within the max purgeable idle time --- src/routes.c | 31 +++++++++---- tests/unittests.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 9 deletions(-) diff --git a/src/routes.c b/src/routes.c index 48519de2..c69314c5 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3103,6 +3103,7 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( wolfsentry_errcode_t ret; int n = 0; wolfsentry_action_res_t fallback_action_results = 0; + struct wolfsentry_list_ent_header *cursor; #ifdef WOLFSENTRY_THREADSAFE int have_mutex = 0; int got_lock = 0; @@ -3149,17 +3150,29 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( table = wolfsentry->routes; while (table->purge_list.tail) { - struct wolfsentry_route *route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(table->purge_list.tail); - if (mode != 3) { - if (route->meta.purge_after > now) - break; - if ((route->meta.connection_count > 0) && - (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && - ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time > table->max_purgeable_idle_time))) - { - break; + struct wolfsentry_route *route = NULL; + int found = 0; + /* re-scan from the most stale entry each pass, because a delete runs + * application callbacks that can mutate the purge list; skip routes + * that still have a live connection within the idle limit so one + * protected route does not strand the expired routes behind it. */ + for (cursor = table->purge_list.tail; cursor != NULL; cursor = cursor->prev) { + route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(cursor); + if (mode != 3) { + if (route->meta.purge_after > now) + break; + if ((route->meta.connection_count > 0) && + (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && + ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time <= table->max_purgeable_idle_time))) + { + continue; + } } + found = 1; + break; } + if (! found) + break; #ifdef WOLFSENTRY_THREADSAFE if (! have_mutex) { if (mode == 2) diff --git a/tests/unittests.c b/tests/unittests.c index b0a46a46..a9a37418 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6454,10 +6454,123 @@ static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, route_flags; + wolfsentry_ent_id_t id, id_b, route_id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, remote_b, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x09\x08\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* a second route with no connection, less stale than the first; it sits + * ahead of the protected route in the purge list and must still be purged. */ + memcpy(&remote_b, &remote, sizeof remote_b); + memcpy(remote_b.sa.addr, "\x0A\x09\x08\x06", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote_b.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id_b, &action_results)); + + /* idle-time limit far larger than this test's wall-clock runtime, so a + * connected route is within the limit and must be preserved by a purge. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* open a connection on the route (connection_count -> 1). */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_SET_BITS(action_results, WOLFSENTRY_ACTION_RES_CONNECT); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(route_id == id); + + /* mark the protected route as the most stale entry (purge_after 1), and the + * unconnected route as less stale (purge_after 2); both are in the past. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 2)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + (void)wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* the route's live connection is within the idle limit, so it must survive. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + /* the unconnected route behind it must not be stranded by the purge. */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id_b, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); #endif + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); WOLFSENTRY_RETURN_OK; } From b11e6bcc08144c9662126907f6bc3a78cb6f9735 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:35:47 -0700 Subject: [PATCH 156/255] F-13881 - https://fenrir.wolfssl.com/finding/13881 - Surface dispatch resource failures instead of masking them as fallthrough --- src/routes.c | 12 +++++++++ tests/unittests.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/routes.c b/src/routes.c index c69314c5..9257f74d 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1178,6 +1178,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( thread, #endif config->config.route_private_data_alignment, *new_route); + *new_route = NULL; WOLFSENTRY_ERROR_RERETURN(ret); } WOLFSENTRY_REFCOUNT_INCREMENT((*new_route)->parent_event->header.refcount, ret); @@ -2723,6 +2724,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( struct wolfsentry_route *rule_route = NULL; struct wolfsentry_event *trigger_event = NULL; wolfsentry_errcode_t ret; + int apply_default_policy = 0; if ((remote == NULL) || (local == NULL) || @@ -2795,6 +2797,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( else { /* nothing to do. */ /* carry through ret from final wolfsentry_route_lookup_0(), and impose the default policy. */ + apply_default_policy = 1; goto just_free_resources; } @@ -2834,6 +2837,15 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, trigger_event, NULL /* action_results */)); if (rule_route == NULL) { + if (! apply_default_policy) { + /* a resource failure (not a clean no-match) prevented rule + * evaluation; fail closed with an explicit reject rather than + * silently applying the default policy as a successful fallthrough, + * so an integrator that gates on _RES_REJECT drops the packet. + */ + *action_results |= WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_REJECT; + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } if (inexact_matches) *inexact_matches = WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD; *action_results |= route_table->default_policy | WOLFSENTRY_ACTION_RES_FALLTHROUGH; diff --git a/tests/unittests.c b/tests/unittests.c index a9a37418..61a5b001 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6566,11 +6566,73 @@ static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_dispatch_error_not_masked(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches, flags; + wolfsentry_ent_id_t id; + wolfsentry_errcode_t ret; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.addr_len = 0; + local.sa.sa_family = AF_INET; + local.sa.addr_len = 0; + + /* a family wildcard without the remote/local/proto wildcards it requires is + * rejected while the ephemeral target route is built, before any rule is + * found; the dispatcher must surface that error, not report a successful + * default-policy fallthrough. + */ + WOLFSENTRY_CLEAR_ALL_BITS(flags); + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + ret = wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_IS_FAILURE(ret)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ERROR)); + /* the default policy is unset (NONE) here, so the failure must fail closed + * with an explicit reject rather than leaving the packet to be allowed. */ + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); #endif WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); WOLFSENTRY_RETURN_OK; } From 754c2d2cab14d22c5647e169ce95dfc3d634726d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:37:38 -0700 Subject: [PATCH 157/255] F-12514 - https://fenrir.wolfssl.com/finding/12514 - Keep the purge list consistent when copying route metadata --- src/routes.c | 8 +++++ tests/unittests.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/routes.c b/src/routes.c index 9257f74d..da8d644d 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4970,8 +4970,16 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_copy_metadata( /* pointers are advanced inside the loop. */) { if (from_i->header.id == to_i->header.id) { + /* meta carries purge_after, which determines purge_list membership + * and ordering; refresh the list around the copy so it stays + * consistent with the new value. + */ + if (to_i->meta.purge_after) + wolfsentry_list_ent_delete(&to_table->purge_list, &to_i->purge_links); to_i->flags = from_i->flags; to_i->meta = from_i->meta; + if (to_i->meta.purge_after) + wolfsentry_route_purge_list_insert(to_table, to_i); } else { int cmpret = wolfsentry_route_key_cmp_1(from_i, to_i, 0 /* match_wildcards_p */, NULL /* inexact_matches */); if (cmpret < 0) { diff --git a/tests/unittests.c b/tests/unittests.c index 61a5b001..1047b1b7 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6627,12 +6627,97 @@ static wolfsentry_errcode_t test_regression_dispatch_error_not_masked(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *ctx_clone = NULL; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t route_flags; + wolfsentry_ent_id_t id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x05\x06\x07", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* clone before the route has a purge time, so the clone's copy of the route + * is not in the clone's purge list. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + /* give the live route a (stale) purge time, so copy_metadata will propagate + * a nonzero purge_after into the clone's not-yet-listed route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* swap the clone in, copying the live metadata across. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_exchange(WOLFSENTRY_CONTEXT_ARGS_OUT, ctx_clone)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + /* the swapped-in route now has a stale purge_after, so it must be reachable + * from the purge list and get purged. */ + (void)wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); #endif WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); WOLFSENTRY_RETURN_OK; } From 28dc18f31f5fa3fde6a28d7eeec93e1373040ef0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:40:02 -0700 Subject: [PATCH 158/255] F-8751 - https://fenrir.wolfssl.com/finding/8751 - Refresh config and route flags after switching to the fallthrough route --- src/routes.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/routes.c b/src/routes.c index da8d644d..85857def 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2503,6 +2503,11 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( wolfsentry_route_increment_hitcount(WOLFSENTRY_CONTEXT_ARGS_OUT, rule_route, action_results); } parent_event = route_table->default_event; + /* the rule route and its event changed, so refresh the cached config + * and flags to match the fallthrough route rather than reusing the + * superseded route's state. */ + config = (parent_event && parent_event->config) ? parent_event->config : &wolfsentry->config; + current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->match_action_list.header) > 0)) { From c2b726cf8c01c415daf704e3a8129a9447dfdf23 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:40:56 -0700 Subject: [PATCH 159/255] F-13287 - https://fenrir.wolfssl.com/finding/13287 - Decrement connection count on disconnect regardless of the configured limit --- src/routes.c | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/routes.c b/src/routes.c index 85857def..ce5a5903 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2539,29 +2539,36 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( (void)ret; } - if ((! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) && - (config->config.max_connection_count > 0)) - { + if (! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) { if (*action_results & WOLFSENTRY_ACTION_RES_CONNECT) { - if (rule_route->meta.connection_count >= config->config.max_connection_count) { - *action_results |= WOLFSENTRY_ACTION_RES_REJECT; - ret = WOLFSENTRY_ERROR_ENCODE(OK); - goto done; - } - if (WOLFSENTRY_ATOMIC_INCREMENT_BY_ONE(rule_route->meta.connection_count) > config->config.max_connection_count) { - WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); - *action_results |= WOLFSENTRY_ACTION_RES_REJECT; - ret = WOLFSENTRY_ERROR_ENCODE(OK); - goto done; + /* the limit is only enforced when configured; a zero limit + * means unlimited, so no increment or rejection applies. */ + if (config->config.max_connection_count > 0) { + if (rule_route->meta.connection_count >= config->config.max_connection_count) { + *action_results |= WOLFSENTRY_ACTION_RES_REJECT; + ret = WOLFSENTRY_ERROR_ENCODE(OK); + goto done; + } + if (WOLFSENTRY_ATOMIC_INCREMENT_BY_ONE(rule_route->meta.connection_count) > config->config.max_connection_count) { + WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); + *action_results |= WOLFSENTRY_ACTION_RES_REJECT; + ret = WOLFSENTRY_ERROR_ENCODE(OK); + goto done; + } } } else if (*action_results & WOLFSENTRY_ACTION_RES_DISCONNECT) { uint16_t new_connection_count; - /* important to decrement safely, because untimely route - * deletions can lead to a decrement being assigned to a - * different route than the one originally incremented. + /* decrement regardless of the limit, so counts seeded at insert + * time still drain when the limit is zero (unlimited); decrement + * safely because untimely route deletions can assign a decrement + * to a different route than the one originally incremented. */ WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(rule_route->meta.connection_count, 1, new_connection_count); - if (new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) + /* only an already-zero count under an enforced limit is + * anomalous; with no limit, unconnected routes legitimately see + * disconnects, so do not warn on them. */ + if ((new_connection_count == MAX_UINT_OF(rule_route->meta.connection_count)) && + (config->config.max_connection_count > 0)) WOLFSENTRY_WARN("_RES_DISCONNECT for route #" WOLFSENTRY_ENT_ID_FMT ", whose connection_count is already zero.", rule_route->header.id); } } From 785ff3100d5568b74582ad1f150799262eebc1ef Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:41:24 -0700 Subject: [PATCH 160/255] F-15468 - https://fenrir.wolfssl.com/finding/15468 - Always reposition a refreshed route in the purge list --- src/routes.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/routes.c b/src/routes.c index ce5a5903..9d3308ba 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2460,10 +2460,11 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( #endif { rule_route->meta.purge_after = new_purge_after; /* coherent assignment */ - if (route_table->purge_list.tail != &rule_route->purge_links) { - wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); - wolfsentry_route_purge_list_insert(route_table, rule_route); - } + /* the refreshed route may now be the newest rather than the + * stalest, so always reposition it instead of leaving a + * just-refreshed tail stranded at the stale end. */ + wolfsentry_list_ent_delete(&route_table->purge_list, &rule_route->purge_links); + wolfsentry_route_purge_list_insert(route_table, rule_route); } } } From 9f37e6e877c72d59bdf269b9676860af92e57d71 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:42:27 -0700 Subject: [PATCH 161/255] F-2442 - https://fenrir.wolfssl.com/finding/2442 - Set the error result bit when an action chain dispatch fails --- src/routes.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/routes.c b/src/routes.c index 9d3308ba..98793e93 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2409,6 +2409,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( struct wolfsentry_eventconfig_internal *config; wolfsentry_route_flags_t current_rule_route_flags; wolfsentry_errcode_t ret; + wolfsentry_errcode_t action_ret; wolfsentry_time_t now; int penalty_triggered = 0; wolfsentry_hitcount_t derog_snap; @@ -2477,7 +2478,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( * trigger_event->action_list must call wolfsentry_route_set_wildcard(), * in addition to setting _ACTION_RES_INSERT. */ - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, trigger_event, @@ -2486,7 +2487,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2512,7 +2516,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->match_action_list.header) > 0)) { - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2521,7 +2525,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); current_rule_route_flags = WOLFSENTRY_ATOMIC_LOAD(rule_route->flags); } @@ -2680,7 +2687,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( parent_event && (wolfsentry_list_ent_get_len(&parent_event->update_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2689,13 +2696,16 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } if (parent_event && (wolfsentry_list_ent_get_len(&parent_event->decision_action_list.header) > 0)) { WOLFSENTRY_CLEAR_BITS(*action_results, WOLFSENTRY_ACTION_RES_STOP); - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_dispatch( + action_ret = wolfsentry_action_list_dispatch( WOLFSENTRY_CONTEXT_ARGS_OUT, caller_arg, parent_event, @@ -2704,7 +2714,10 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( target_route, route_table, rule_route, - action_results)); + action_results); + WOLFSENTRY_WARN_ON_FAILURE(action_ret); + if (action_ret < 0) + *action_results |= WOLFSENTRY_ACTION_RES_ERROR; /* no need to refresh current_rule_route_flags */ } From aa4510db4929ec25e9e6912268bc51d4a31f7b9b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:43:35 -0700 Subject: [PATCH 162/255] F-2437 - https://fenrir.wolfssl.com/finding/2437 - Warn once when unmatched traffic falls through with an unset default policy --- src/routes.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/routes.c b/src/routes.c index 98793e93..e7b50307 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2751,6 +2751,7 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( struct wolfsentry_event *trigger_event = NULL; wolfsentry_errcode_t ret; int apply_default_policy = 0; + static int warned_unset_default_policy = 0; if ((remote == NULL) || (local == NULL) || @@ -2874,6 +2875,18 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_1( } if (inexact_matches) *inexact_matches = WOLFSENTRY_ROUTE_WILDCARD_FLAGS | WOLFSENTRY_ROUTE_FLAG_PARENT_EVENT_WILDCARD; + if (route_table->default_policy == WOLFSENTRY_ACTION_RES_NONE) { + /* claim the one-shot atomically so only a single caller warns. */ + int warn_expected = 0; +#ifdef WOLFSENTRY_THREADSAFE + if (WOLFSENTRY_ATOMIC_CMPXCHG(&warned_unset_default_policy, &warn_expected, 1, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) +#else + warn_expected = warned_unset_default_policy; + warned_unset_default_policy = 1; + if (! warn_expected) +#endif + WOLFSENTRY_WARN("%s", "default route policy is unset (NONE); unmatched traffic falls through with no accept or reject verdict.\n"); + } *action_results |= route_table->default_policy | WOLFSENTRY_ACTION_RES_FALLTHROUGH; WOLFSENTRY_SUCCESS_UNLOCK_AND_RETURN(USED_FALLBACK); } From 8ce0caea63b2d4770a8a1e9377488b83877f606d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:45:05 -0700 Subject: [PATCH 163/255] F-3441 - https://fenrir.wolfssl.com/finding/3441 - Reject internal-only result bits in caller-initialized dispatch input --- src/routes.c | 2 ++ tests/unittests.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/src/routes.c b/src/routes.c index e7b50307..17c923d2 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2936,6 +2936,8 @@ static wolfsentry_errcode_t check_user_inited_result(wolfsentry_action_res_t act if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | + WOLFSENTRY_ACTION_RES_FALLTHROUGH | + WOLFSENTRY_ACTION_RES_INSERTED | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_DEALLOCATED | WOLFSENTRY_ACTION_RES_ERROR)) diff --git a/tests/unittests.c b/tests/unittests.c index 1047b1b7..65a8d0d8 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6711,6 +6711,70 @@ static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_route_api_invariants(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_ent_id_t id; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = AF_INET; + remote.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, "\x0A\x02\x03\x04", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + local.sa.sa_family = AF_INET; + local.sa.sa_proto = IPPROTO_TCP; + local.sa.addr_len = 0; + + /* internal-only result bits must not be accepted as caller-initialized + * input to the dispatch-with-inited-result entry points. */ + action_results = WOLFSENTRY_ACTION_RES_FALLTHROUGH; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + action_results = WOLFSENTRY_ACTION_RES_INSERTED; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); @@ -6718,6 +6782,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_stale_purge_connected_idle(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); WOLFSENTRY_RETURN_OK; } From d155b2e46bf60376d16cb0199e7d0ce99aec1729 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:46:01 -0700 Subject: [PATCH 164/255] F-6510 - https://fenrir.wolfssl.com/finding/6510 - Reject a default policy that sets both accept and reject --- src/routes.c | 2 ++ tests/unittests.c | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/routes.c b/src/routes.c index 17c923d2..21fcec61 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2025,6 +2025,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_default_policy_set( if ((default_policy != WOLFSENTRY_ACTION_RES_NONE) && (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (WOLFSENTRY_CHECK_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_MUTEX_OR_RETURN(); table->default_policy = default_policy; if (table == wolfsentry->routes) diff --git a/tests/unittests.c b/tests/unittests.c index 65a8d0d8..29fc4a09 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6770,6 +6770,14 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { &id, &inexact_matches, &action_results), INVALID_ARG)); + /* a default policy may be accept or reject, never both at once. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT), + INVALID_ARG)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From f849f0b8ab9302a959efe20043f1afe3f152f9d4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:47:25 -0700 Subject: [PATCH 165/255] F-1216 - https://fenrir.wolfssl.com/finding/1216 - Report the written label length from the default event getter --- src/routes.c | 1 + tests/unittests.c | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/routes.c b/src/routes.c index 21fcec61..38fc740c 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3681,6 +3681,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_get_default_event( if (table->default_event->label_len >= *event_label_len) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(BUFFER_TOO_SMALL); memcpy(event_label, table->default_event->label, (size_t)(table->default_event->label_len + 1)); + *event_label_len = table->default_event->label_len; WOLFSENTRY_UNLOCK_AND_RETURN_OK; } diff --git a/tests/unittests.c b/tests/unittests.c index 29fc4a09..99b54ad9 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6713,9 +6713,12 @@ static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { static wolfsentry_errcode_t test_regression_route_api_invariants(void) { struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; wolfsentry_action_res_t action_results; wolfsentry_route_flags_t inexact_matches; wolfsentry_ent_id_t id; + char default_event_label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + int default_event_label_len; struct { struct wolfsentry_sockaddr sa; byte addr_buf[4]; @@ -6778,6 +6781,27 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT), INVALID_ARG)); + /* reading back the default event must report the written label length, not + * leave the caller's in/out length untouched. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "default_evt", -1 /* label_len */, 10 /* priority */, + NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_set_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, "default_evt", -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + default_event_label_len = (int)sizeof default_event_label; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_get_default_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, default_event_label, &default_event_label_len)); + WOLFSENTRY_EXIT_ON_FALSE(default_event_label_len == (int)strlen("default_evt")); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(default_event_label, "default_evt") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From b3d10ce9e499bb0f578f7c58e5d13ca13f11f8a9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:49:01 -0700 Subject: [PATCH 166/255] F-10502 - https://fenrir.wolfssl.com/finding/10502 - Treat a zero count delta as a no-op and negate safely in count adjustment --- src/routes.c | 14 ++++++++++---- tests/unittests.c | 32 +++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/routes.c b/src/routes.c index 38fc740c..e65b5229 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3491,10 +3491,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_derogatory_count( WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, (unsigned)(-count_to_add), new_derogatory_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.derogatory_count, 0U - (unsigned)count_to_add, new_derogatory_count); if (new_derogatory_count == MAX_UINT_OF(route->meta.derogatory_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_derogatory_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.derogatory_count); } if (new_derogatory_count_ptr) @@ -3517,10 +3520,13 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_increment_commendable_count WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == 0) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); - } else { - WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, (unsigned)(-count_to_add), new_commendable_count); + } else if (count_to_add < 0) { + /* negate in the unsigned domain so INT_MIN does not overflow. */ + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(route->meta.commendable_count, 0U - (unsigned)count_to_add, new_commendable_count); if (new_commendable_count == MAX_UINT_OF(route->meta.commendable_count)) WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + } else { + new_commendable_count = WOLFSENTRY_ATOMIC_LOAD(route->meta.commendable_count); } if (new_commendable_count_ptr) diff --git a/tests/unittests.c b/tests/unittests.c index 99b54ad9..3690715c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6714,11 +6714,13 @@ static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { static wolfsentry_errcode_t test_regression_route_api_invariants(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *table; + struct wolfsentry_route *route; wolfsentry_action_res_t action_results; - wolfsentry_route_flags_t inexact_matches; + wolfsentry_route_flags_t inexact_matches, route_flags; wolfsentry_ent_id_t id; char default_event_label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; int default_event_label_len; + int new_count; struct { struct wolfsentry_sockaddr sa; byte addr_buf[4]; @@ -6802,6 +6804,34 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_EXIT_ON_FALSE(default_event_label_len == (int)strlen("default_evt")); WOLFSENTRY_EXIT_ON_FALSE(strcmp(default_event_label, "default_evt") == 0); + /* incrementing a derogatory count by zero is a no-op that must succeed even + * when the count is saturated, not be mistaken for an averted overflow. */ + WOLFSENTRY_CLEAR_ALL_BITS(route_flags); + WOLFSENTRY_SET_BITS(route_flags, WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, route_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route)); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 65535, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + new_count = -1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_increment_derogatory_count(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 0, &new_count)); + WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From cd81ba74b97f2d0d6441cceefb5da4067ef2aa97 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:50:45 -0700 Subject: [PATCH 167/255] F-11618 - https://fenrir.wolfssl.com/finding/11618 - Reject setting a purge time on a route with no parent table --- src/routes.c | 2 ++ tests/unittests.c | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/routes.c b/src/routes.c index e65b5229..5ae23223 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3140,6 +3140,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_purge_time_set( wolfsentry_time_t purge_after) { WOLFSENTRY_HAVE_MUTEX_OR_RETURN(); + if (route->header.parent_table == NULL) + WOLFSENTRY_ERROR_RETURN(NOT_PERMITTED); if (route->meta.purge_after) wolfsentry_list_ent_delete(&((struct wolfsentry_route_table *)route->header.parent_table)->purge_list, &route->purge_links); route->meta.purge_after = purge_after; diff --git a/tests/unittests.c b/tests/unittests.c index 3690715c..b7cf97bd 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6715,6 +6715,7 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *table; struct wolfsentry_route *route; + const struct wolfsentry_route *fallthrough_route; wolfsentry_action_res_t action_results; wolfsentry_route_flags_t inexact_matches, route_flags; wolfsentry_ent_id_t id; @@ -6832,6 +6833,17 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_EXIT_ON_FALSE(new_count == 65535); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* the fallthrough route belongs to no table, so setting a purge time on it + * must be refused rather than dereferencing a null parent table. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_fallthrough_route_get(WOLFSENTRY_CONTEXT_ARGS_OUT, table, &fallthrough_route)); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, 1), + NOT_PERMITTED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From e63ec501953e754ad0265912921e7e3dc06b06cf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:52:19 -0700 Subject: [PATCH 168/255] F-2860 - https://fenrir.wolfssl.com/finding/2860 - Render the final partial byte of link and CAN addresses --- src/routes.c | 9 +++++---- tests/unittests.c | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/routes.c b/src/routes.c index 5ae23223..e976d2a8 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4031,9 +4031,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - if ((addr_bits >> 3) * 3 > (size_t)*buflen) + unsigned int addr_bytes = (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); + if (addr_bytes * 3 > (size_t)*buflen) WOLFSENTRY_ERROR_RETURN(BUFFER_TOO_SMALL); - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < addr_bytes; ++i) { if (i > 0) *buf++ = ':'; *buf++ = hexdigit_ntoa(addr[i] >> 4); @@ -4060,7 +4061,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_format_address( *buf++ = '0'; *buf++ = 'x'; for (i=0; i < 4; ++i) { - if (i < (addr_bits >> 3)) { + if (i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits)) { *buf++ = hexdigit_ntoa(addr[i] >> 4); *buf++ = hexdigit_ntoa(addr[i]); } else { @@ -4587,7 +4588,7 @@ static wolfsentry_errcode_t wolfsentry_route_render_address(WOLFSENTRY_CONTEXT_A if (sa_family == WOLFSENTRY_AF_LINK || sa_family == WOLFSENTRY_AF_LINK64) { unsigned int i; - for (i=0; i < (addr_bits >> 3); ++i) { + for (i=0; i < (unsigned int)WOLFSENTRY_BITS_TO_BYTES(addr_bits); ++i) { if (fprintf(f, "%s%02x", i ? ":" : "", (unsigned int)addr[i]) < 0) WOLFSENTRY_ERROR_RETURN(IO_FAILED); } diff --git a/tests/unittests.c b/tests/unittests.c index b7cf97bd..6937f093 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6722,6 +6722,9 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { char default_event_label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; int default_event_label_len; int new_count; + char addr_fmt_buf[32]; + int addr_fmt_len; + byte link_addr[2]; struct { struct wolfsentry_sockaddr sa; byte addr_buf[4]; @@ -6844,6 +6847,18 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, (struct wolfsentry_route *)fallthrough_route, NULL /* action_results */)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + /* a link address whose length is not a whole number of bytes must render + * its final partial byte, not silently drop it. */ + link_addr[0] = 0xAB; + link_addr[1] = 0xCD; + addr_fmt_len = (int)sizeof addr_fmt_buf; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_format_address( + WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_AF_LINK, link_addr, 12 /* addr_bits */, + addr_fmt_buf, &addr_fmt_len)); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_len == 5); + WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_buf[2] == ':'); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 4c5e1c73ef8ee9af2e37b02894b7e1bf4cf0a355 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:56:32 -0700 Subject: [PATCH 169/255] F-5564 - https://fenrir.wolfssl.com/finding/5564 - Null check result and count outputs before dereferencing them in route APIs --- src/routes.c | 43 +++++++++++++++++++++++++++++++++++++++---- tests/unittests.c | 12 ++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/routes.c b/src/routes.c index e976d2a8..e849fb1f 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1486,6 +1486,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -1510,6 +1513,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_by_exports_into_tabl wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (route_exports->parent_event_label) { @@ -1580,6 +1586,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_insert_into_table_and_check wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -2311,6 +2320,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_from_table( wolfsentry_errcode_t ret; struct wolfsentry_event *event = NULL; + if ((action_results == NULL) || (n_deleted == NULL)) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (event_label) { @@ -2363,6 +2375,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_delete_by_id( struct wolfsentry_event *trigger_event = NULL; struct wolfsentry_route *route; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); if (trigger_label) { @@ -2913,6 +2928,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2930,6 +2947,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2962,7 +2981,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_table_w wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route_table, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -2980,7 +3002,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_with_inited_ wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_1(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, remote, local, flags, event_label, event_label_len, caller_arg, id, inexact_matches, action_results)); } @@ -3036,6 +3061,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -3049,7 +3076,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_id_with_i wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, id, event_label, event_label_len, caller_arg, action_results)); } @@ -3102,6 +3132,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route( wolfsentry_action_res_t *action_results ) { + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_CLEAR_ALL_BITS(*action_results); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } @@ -3115,7 +3147,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit wolfsentry_action_res_t *action_results ) { - wolfsentry_errcode_t ret = check_user_inited_result(*action_results); + wolfsentry_errcode_t ret; + if (action_results == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + ret = check_user_inited_result(*action_results); WOLFSENTRY_RERETURN_IF_ERROR(ret); WOLFSENTRY_ERROR_RERETURN(wolfsentry_route_event_dispatch_by_route_1(WOLFSENTRY_CONTEXT_ARGS_OUT, route, event_label, event_label_len, caller_arg, action_results)); } diff --git a/tests/unittests.c b/tests/unittests.c index 6937f093..8bd6c674 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6859,6 +6859,18 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_len == 5); WOLFSENTRY_EXIT_ON_FALSE(addr_fmt_buf[2] == ':'); + /* a null action_results must be rejected up front, not dereferenced while + * clearing it. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, NULL /* action_results */), + INVALID_ARG)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 41cce9671b86e41de7c5958e62fe6be8c4da78ec Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:57:08 -0700 Subject: [PATCH 170/255] F-10494 - https://fenrir.wolfssl.com/finding/10494 - Match route flag names by exact length to avoid reading past the name --- src/routes.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index e849fb1f..ebe524e3 100644 --- a/src/routes.c +++ b/src/routes.c @@ -4152,8 +4152,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_flag_assoc_by_name(const ch if (len < 0) len = (int)strlen(name); for (i = wolfsentry_route_flag_names; i < end_ptr_of_array(wolfsentry_route_flag_names); ++i) { - if ((strncmp(i->name, name, (size_t)len) == 0) && - (i->name[len] == 0)) + /* compare lengths first so a shared NUL does not lead to reading + * i->name[len] past the end of the name literal. */ + if ((strlen(i->name) == (size_t)len) && + (strncmp(i->name, name, (size_t)len) == 0)) { *flag = i->flag; WOLFSENTRY_RETURN_OK; From d3eebecce3987002cfaa0dd928d233b3c7b8ebc4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:57:35 -0700 Subject: [PATCH 171/255] F-11605 - https://fenrir.wolfssl.com/finding/11605 - Free and null a new route when taking its parent event reference fails --- src/routes.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/routes.c b/src/routes.c index ebe524e3..2c50dba6 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1003,6 +1003,10 @@ static wolfsentry_errcode_t wolfsentry_route_new( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } @@ -1062,6 +1066,10 @@ static wolfsentry_errcode_t wolfsentry_route_new_by_exports( } else { if (parent_event != NULL) { WOLFSENTRY_REFCOUNT_INCREMENT(parent_event->header.refcount, ret); + if (ret < 0) { + wolfsentry_route_free_1(WOLFSENTRY_CONTEXT_ARGS_OUT, config->config.route_private_data_alignment, *new); + *new = NULL; + } } } From d549530bf02b99852311d02256e5abdced89bee1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 10:57:58 -0700 Subject: [PATCH 172/255] F-10493 - https://fenrir.wolfssl.com/finding/10493 - Read the cloned route address from the source address offset --- src/routes.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index 2c50dba6..7d053f13 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1172,8 +1172,9 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_clone( /* now zero the private data section. */ memset((*new_route)->data, 0, config->config.route_private_data_size); - /* finally, copy the address section, carefully. */ - memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, src_route->data, src_route->data_addr_size); + /* finally, copy the address section, carefully, reading it from the + * source's own address offset rather than the start of its data. */ + memcpy((byte *)(*new_route)->data + (*new_route)->data_addr_offset, (const byte *)src_route->data + src_route->data_addr_offset, src_route->data_addr_size); } WOLFSENTRY_TABLE_ENT_HEADER_RESET(**new_ent); From 61b26a6ec14b87c36267bdbd0b901d3d03cb3e6e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 11:00:03 -0700 Subject: [PATCH 173/255] F-15469 - https://fenrir.wolfssl.com/finding/15469 - Compact the address buffer when wildcarding the remote address --- src/routes.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index 7d053f13..ec660bbf 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3631,11 +3631,24 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_set_wildcard( } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD; - route->remote.addr_len = 0; + if (route->remote.addr_len > 0) { + /* the local address is stored immediately after the remote address, + * so dropping the remote address requires sliding the local bytes + * down to keep WOLFSENTRY_ROUTE_LOCAL_ADDR() valid. */ + if (route->local.addr_len > 0) + memmove(WOLFSENTRY_ROUTE_REMOTE_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR(route), + WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route)); + route->remote.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_LOCAL_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD; - route->local.addr_len = 0; + if (route->local.addr_len > 0) { + route->local.addr_len = 0; + route->data_addr_size = (uint16_t)WOLFSENTRY_ROUTE_REMOTE_ADDR_BYTES(route); + } } if (wildcards_to_set & WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD) { route->flags |= WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD; From 571d41e61518f01f18d4236708d6aec4c3559eff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 11:00:28 -0700 Subject: [PATCH 174/255] F-8766 - https://fenrir.wolfssl.com/finding/8766 - Copy the max purgeable idle time when cloning a route table header --- src/routes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes.c b/src/routes.c index ec660bbf..8b59c05e 100644 --- a/src/routes.c +++ b/src/routes.c @@ -5005,6 +5005,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( ((struct wolfsentry_route_table *)dest_table)->max_purgeable_routes = ((struct wolfsentry_route_table *)src_table)->max_purgeable_routes; + ((struct wolfsentry_route_table *)dest_table)->max_purgeable_idle_time = + ((struct wolfsentry_route_table *)src_table)->max_purgeable_idle_time; ((struct wolfsentry_route_table *)dest_table)->default_policy = ((struct wolfsentry_route_table *)src_table)->default_policy; From fec26964e57b757b4075136f992f5ec22b99d736 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 11:00:50 -0700 Subject: [PATCH 175/255] F-12516 - https://fenrir.wolfssl.com/finding/12516 - Reference the fallthrough route before publishing it into the cloned table --- src/routes.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index 8b59c05e..70bb73f9 100644 --- a/src/routes.c +++ b/src/routes.c @@ -5031,14 +5031,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_table_clone_header( } if (((struct wolfsentry_route_table *)src_table)->fallthrough_route != NULL) { + /* take the source reference before releasing the destination's own + * route, so a failed increment leaves the destination route intact + * rather than a freed, still-published pointer. */ + WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)src_table)->fallthrough_route->header.refcount, ret); + WOLFSENTRY_RERETURN_IF_ERROR(ret); if (((struct wolfsentry_route_table *)dest_table)->fallthrough_route != NULL) { WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_drop_reference_1( WOLFSENTRY_CONTEXT_ARGS_OUT_EX(dest_context), ((struct wolfsentry_route_table *)dest_table)->fallthrough_route, NULL /* action_results */)); } ((struct wolfsentry_route_table *)dest_table)->fallthrough_route = ((struct wolfsentry_route_table *)src_table)->fallthrough_route; - WOLFSENTRY_REFCOUNT_INCREMENT(((struct wolfsentry_route_table *)dest_table)->fallthrough_route->header.refcount, ret); - WOLFSENTRY_RERETURN_IF_ERROR(ret); } if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)) From 9b8d132e145f0c2a39c555b98356b4d3963e8964 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 11:01:11 -0700 Subject: [PATCH 176/255] F-10489 - https://fenrir.wolfssl.com/finding/10489 - Carry the route hit count across a metadata copy --- src/routes.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes.c b/src/routes.c index 70bb73f9..e72ad050 100644 --- a/src/routes.c +++ b/src/routes.c @@ -5095,6 +5095,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_route_copy_metadata( wolfsentry_list_ent_delete(&to_table->purge_list, &to_i->purge_links); to_i->flags = from_i->flags; to_i->meta = from_i->meta; + to_i->header.hitcount = from_i->header.hitcount; if (to_i->meta.purge_after) wolfsentry_route_purge_list_insert(to_table, to_i); } else { From ab4c102a5be065f760209a37f83879645002f926 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:38:42 -0700 Subject: [PATCH 177/255] F-3667 - https://fenrir.wolfssl.com/finding/3667 - Accept a reset default policy and carry the port reset into fallthrough verdicts --- src/routes.c | 4 ++++ tests/unittests.c | 25 +++++++++++++++++++++++++ wolfsentry/wolfsentry.h | 2 +- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/routes.c b/src/routes.c index e72ad050..7246974a 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2045,6 +2045,10 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_table_default_policy_set( WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (WOLFSENTRY_CHECK_BITS(default_policy, WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT)) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + /* a port reset only qualifies a reject verdict. */ + if (WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_PORT_RESET) && + (! WOLFSENTRY_MASKIN_BITS(default_policy, WOLFSENTRY_ACTION_RES_REJECT))) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); WOLFSENTRY_MUTEX_OR_RETURN(); table->default_policy = default_policy; if (table == wolfsentry->routes) diff --git a/tests/unittests.c b/tests/unittests.c index 8bd6c674..5ad9ef5d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6871,6 +6871,31 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { &id, &inexact_matches, NULL /* action_results */), INVALID_ARG)); + /* a "reset" default policy (reject plus port reset) is accepted and carried + * into unmatched verdicts; a port reset cannot qualify an accept. */ + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_PORT_RESET), + INVALID_ARG)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_default_policy_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, + WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + memcpy(remote.sa.addr, "\x0A\x63\x63\x63", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_SUCCESS_CODE_IS( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + USED_FALLBACK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 9702be76..d8a21d40 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -1003,7 +1003,7 @@ typedef wolfsentry_errcode_t (*wolfsentry_action_callback_t)( * @{ */ -#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR) +#define WOLFSENTRY_ROUTE_DEFAULT_POLICY_MASK (WOLFSENTRY_ACTION_RES_ACCEPT | WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_STOP | WOLFSENTRY_ACTION_RES_ERROR | WOLFSENTRY_ACTION_RES_PORT_RESET) /*!< \brief Bit mask spanning the bits allowed by wolfsentry_route_table_default_policy_set() */ /*! \brief bit field specifying attributes of a route/rule */ From 8869ef3e1bbd84ef47789da2f13e7c3f4ce4a760 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:41:25 -0700 Subject: [PATCH 178/255] F-2841 - https://fenrir.wolfssl.com/finding/2841 - Test that dispatch rejects caller-initialized verdict and status bits --- tests/unittests.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 5ad9ef5d..5e6f0aeb 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6725,6 +6725,14 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { char addr_fmt_buf[32]; int addr_fmt_len; byte link_addr[2]; + size_t i; + static const wolfsentry_action_res_t forbidden_inited[] = { + WOLFSENTRY_ACTION_RES_ACCEPT, + WOLFSENTRY_ACTION_RES_REJECT, + WOLFSENTRY_ACTION_RES_STOP, + WOLFSENTRY_ACTION_RES_DEALLOCATED, + WOLFSENTRY_ACTION_RES_ERROR + }; struct { struct wolfsentry_sockaddr sa; byte addr_buf[4]; @@ -6896,6 +6904,20 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { USED_FALLBACK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT | WOLFSENTRY_ACTION_RES_PORT_RESET)); + /* a caller may not pre-decide the verdict or pre-set internal status bits. */ + for (i = 0; i < sizeof forbidden_inited / sizeof forbidden_inited[0]; ++i) { + action_results = forbidden_inited[i]; + WOLFSENTRY_EXIT_ON_FALSE( + WOLFSENTRY_ERROR_CODE_IS( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &id, &inexact_matches, &action_results), + INVALID_ARG)); + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 8c469b8bad783bc781158090f317064609a2081e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:42:36 -0700 Subject: [PATCH 179/255] F-2465 - https://fenrir.wolfssl.com/finding/2465 - Test the exact stale purge time and idle limit boundaries --- tests/unittests.c | 179 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 5e6f0aeb..58f79cf2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6923,6 +6923,184 @@ static wolfsentry_errcode_t test_regression_route_api_invariants(void) { WOLFSENTRY_RETURN_OK; } +/* insert an inbound TCP/IPv4 route with wildcarded local side and ports; a NULL + * remote_addr leaves the remote address empty (pair with the address wildcard). */ +static wolfsentry_errcode_t regression_insert( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + unsigned int remote_prefix_bits, + const char *event_label, + wolfsentry_route_flags_t extra_flags, + wolfsentry_ent_id_t *id) +{ + wolfsentry_action_res_t action_results; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + if (remote_addr != NULL) { + remote.sa.addr_len = (wolfsentry_addr_bits_t)remote_prefix_bits; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + } + + return wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + extra_flags + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD, + event_label, event_label ? -1 : 0, + id, &action_results); +} + +/* dispatch an inbound TCP/IPv4 packet from a 4-byte remote address, with the + * caller-initialized result bits in inited_results. */ +static wolfsentry_errcode_t regression_dispatch( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *remote_addr, + wolfsentry_action_res_t inited_results, + wolfsentry_ent_id_t *id, + wolfsentry_action_res_t *action_results) +{ + wolfsentry_route_flags_t inexact_matches; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = 32; + memcpy(remote.sa.addr, remote_addr, sizeof remote.addr_buf); + *action_results = inited_results; + + return wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + id, &inexact_matches, action_results); +} + +static wolfsentry_time_t regression_fake_now; + +static wolfsentry_errcode_t regression_fake_get_time(void *context, wolfsentry_time_t *now) { + (void)context; + *now = regression_fake_now; + WOLFSENTRY_RETURN_OK; +} + +static wolfsentry_errcode_t regression_route_meta( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + struct wolfsentry_route_metadata_exports *meta) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_get_metadata(route, meta); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t regression_set_purge_time( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t id, + wolfsentry_time_t purge_after) +{ + struct wolfsentry_route *route; + wolfsentry_errcode_t ret, unlock_ret; + + ret = wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret < 0) + return ret; + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)&route); + if (ret >= 0) + ret = wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, purge_after); + unlock_ret = wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT); + if (ret >= 0) + ret = unlock_ret; + return ret; +} + +static wolfsentry_errcode_t test_regression_route_timing(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* drive the context clock by hand so time boundaries are exact. */ + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + /* a route expiring one tick from now survives a purge, and is purged once + * the clock reaches its purge time. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x01", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now + 1)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + /* an expired route with a live connection idle for exactly the limit is + * kept; one tick longer and it is purged. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + regression_fake_now += 100; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); @@ -6931,6 +7109,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_dispatch_error_not_masked(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); WOLFSENTRY_RETURN_OK; } From 85f13495805b196eb1969f1e73c6b05a77dba1a4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:43:09 -0700 Subject: [PATCH 180/255] F-2459 - https://fenrir.wolfssl.com/finding/2459 - Test that routes exempt from connection counting are never limited --- tests/unittests.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 58f79cf2..739e4951 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7101,6 +7101,49 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_connection_limits(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + unsigned int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limit2", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + + /* a route that opts out of connection counting is never limited. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); @@ -7110,6 +7153,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_copy_metadata_purge_list(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); WOLFSENTRY_RETURN_OK; } From ce08512f70c7ac182ecc2aeed8c298fb2edc8d69 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:43:32 -0700 Subject: [PATCH 181/255] F-6258 - https://fenrir.wolfssl.com/finding/6258 - Test connection limit enforcement and slot reuse at a positive limit --- tests/unittests.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 739e4951..98a7e307 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7139,6 +7139,23 @@ static wolfsentry_errcode_t test_regression_connection_limits(void) { WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + /* a counted route admits exactly the limit, rejects the next connection + * without counting it, and reopens a slot after a disconnect. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 0648b2d4196ebb02342d5c4a6e2e372a3c1caf8a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:44:11 -0700 Subject: [PATCH 182/255] F-5565 - https://fenrir.wolfssl.com/finding/5565 - Test that the most specific of equal-priority overlapping routes wins --- tests/unittests.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 98a7e307..b53c8b09 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7161,6 +7161,42 @@ static wolfsentry_errcode_t test_regression_connection_limits(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_prefix_specificity(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t broad_id, narrow_id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + /* equal-priority overlapping prefixes: the longer match must win inside + * the /25, and the /24 must still match outside it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x00", 24, NULL, WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &broad_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x80", 25, NULL, WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &narrow_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\xC8", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\xC0\xA8\x01\x0A", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(matched_id == broad_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_dynamic_rules_regressions(void) { #ifdef WOLFSENTRY_ADDR_BITMASK_MATCHING WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bitmask_match_specificity(), OK)); @@ -7171,6 +7207,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); WOLFSENTRY_RETURN_OK; } From a71802e4a7c32ea519ac8af49988c43a70ee66af Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:45:01 -0700 Subject: [PATCH 183/255] F-2456 - https://fenrir.wolfssl.com/finding/2456 - Test the exact penalty box duration boundary --- tests/unittests.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index b53c8b09..743d5909 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7043,6 +7043,24 @@ static wolfsentry_errcode_t regression_set_purge_time( return ret; } +static wolfsentry_errcode_t regression_insert_pbox_event( + WOLFSENTRY_CONTEXT_ARGS_IN, + const char *label, + wolfsentry_hitcount_t derog_threshold, + wolfsentry_time_t penaltybox_duration, + wolfsentry_eventconfig_flags_t flags) +{ + struct wolfsentry_eventconfig event_config; + + memset(&event_config, 0, sizeof event_config); + event_config.route_private_data_size = PRIVATE_DATA_SIZE; + event_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + event_config.derogatory_threshold_for_penaltybox = derog_threshold; + event_config.penaltybox_duration = penaltybox_duration; + event_config.flags = flags; + return wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, label, -1, 10, &event_config, WOLFSENTRY_EVENT_FLAG_NONE, NULL /* id */); +} + static wolfsentry_errcode_t test_regression_route_timing(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *table; @@ -7096,6 +7114,21 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + /* a route penalty-boxed by its second derogatory event stays boxed at + * exactly the box duration, and is released one tick later. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pbox", 2, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 50; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + ++regression_fake_now; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From efc7c7a05bb57168f3e0354ce5b96b7ed19d05ec Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:45:36 -0700 Subject: [PATCH 184/255] F-2461 - https://fenrir.wolfssl.com/finding/2461 - Test that a zero derogatory threshold disables penalty boxing --- tests/unittests.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 743d5909..a74f58cc 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7067,6 +7067,7 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { struct wolfsentry_route_metadata_exports meta; wolfsentry_action_res_t action_results; wolfsentry_ent_id_t id, matched_id; + unsigned int i; #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; #else @@ -7129,6 +7130,15 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x03", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + /* a zero derogatory threshold disables penalty boxing entirely. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "nothresh", 0, 50, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", 32, "nothresh", WOLFSENTRY_ROUTE_FLAG_GREENLISTED, &id)); + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x04", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 7ae6d451db3d59a210c32f4270d29115e5eb4e06 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:46:08 -0700 Subject: [PATCH 185/255] F-2464 - https://fenrir.wolfssl.com/finding/2464 - Test permanent penalty boxing and boxes with no recorded time --- tests/unittests.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index a74f58cc..a599b0dc 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7139,6 +7139,20 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); } + /* a zero box duration is permanent, and a route boxed at insert (no box + * time recorded) is never released by the timeout. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "pboxperm", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", 32, "pboxperm", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", 32, "pbox", WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + regression_fake_now += 1000000; + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x05", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From fe4dd791b5d60925bd849c3107ecf8bcb3e15305 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:46:40 -0700 Subject: [PATCH 186/255] F-3663 - https://fenrir.wolfssl.com/finding/3663 - Test the penalty box trigger when commendable events are ignored --- tests/unittests.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index a599b0dc..2e3d2e36 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7153,6 +7153,17 @@ static wolfsentry_errcode_t test_regression_route_timing(void) { WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x06", WOLFSENTRY_ACTION_RES_NONE, &matched_id, &action_results)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + /* with commendable events ignored, reaching the derogatory threshold boxes + * the route even though commendable events would otherwise offset it. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert_pbox_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "ignorecommend", 2, 0, WOLFSENTRY_EVENTCONFIG_FLAG_DEROGATORY_THRESHOLD_IGNORE_COMMENDABLE)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", 32, "ignorecommend", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 2; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_COMMENDABLE, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x05\x05\x07", WOLFSENTRY_ACTION_RES_DEROGATORY, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From 38ecde63762be9fff7d25de2e9d51fe94e5a16f0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 09:51:25 -0700 Subject: [PATCH 187/255] F-13287 - https://fenrir.wolfssl.com/finding/13287 - Count every connection so disconnects stay balanced across limit changes --- src/routes.c | 40 +++++++++++++++++++++----------------- tests/unittests.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/routes.c b/src/routes.c index 7246974a..ad05a442 100644 --- a/src/routes.c +++ b/src/routes.c @@ -2579,28 +2579,32 @@ static wolfsentry_errcode_t wolfsentry_route_event_dispatch_0( if (! (current_rule_route_flags & WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) { if (*action_results & WOLFSENTRY_ACTION_RES_CONNECT) { - /* the limit is only enforced when configured; a zero limit - * means unlimited, so no increment or rejection applies. */ - if (config->config.max_connection_count > 0) { - if (rule_route->meta.connection_count >= config->config.max_connection_count) { - *action_results |= WOLFSENTRY_ACTION_RES_REJECT; - ret = WOLFSENTRY_ERROR_ENCODE(OK); - goto done; - } - if (WOLFSENTRY_ATOMIC_INCREMENT_BY_ONE(rule_route->meta.connection_count) > config->config.max_connection_count) { + uint16_t new_connection_count; + /* count every connection so disconnects stay balanced when the + * limit changes; a zero limit only means nothing is rejected. */ + if ((config->config.max_connection_count > 0) && + (rule_route->meta.connection_count >= config->config.max_connection_count)) + { + *action_results |= WOLFSENTRY_ACTION_RES_REJECT; + ret = WOLFSENTRY_ERROR_ENCODE(OK); + goto done; + } + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(rule_route->meta.connection_count, new_connection_count); + /* with no limit a saturated counter simply stops counting; under a + * limit it cannot track another connection, so refuse it. */ + if ((config->config.max_connection_count > 0) && + ((new_connection_count == 0) || (new_connection_count > config->config.max_connection_count))) + { + if (new_connection_count != 0) WOLFSENTRY_ATOMIC_DECREMENT_BY_ONE(rule_route->meta.connection_count); - *action_results |= WOLFSENTRY_ACTION_RES_REJECT; - ret = WOLFSENTRY_ERROR_ENCODE(OK); - goto done; - } + *action_results |= WOLFSENTRY_ACTION_RES_REJECT; + ret = WOLFSENTRY_ERROR_ENCODE(OK); + goto done; } } else if (*action_results & WOLFSENTRY_ACTION_RES_DISCONNECT) { uint16_t new_connection_count; - /* decrement regardless of the limit, so counts seeded at insert - * time still drain when the limit is zero (unlimited); decrement - * safely because untimely route deletions can assign a decrement - * to a different route than the one originally incremented. - */ + /* decrement safely: an untimely route deletion can assign a + * decrement to a different route than the one incremented. */ WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY(rule_route->meta.connection_count, 1, new_connection_count); /* only an already-zero count under an enforced limit is * anomalous; with no limit, unconnected routes legitimately see diff --git a/tests/unittests.c b/tests/unittests.c index 2e3d2e36..09f836c2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7229,6 +7229,54 @@ static wolfsentry_errcode_t test_regression_connection_limits(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_connection_count_zero_limit(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_eventconfig limit_config; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(&limit_config, 0, sizeof limit_config); + limit_config.route_private_data_size = PRIVATE_DATA_SIZE; + limit_config.route_private_data_alignment = PRIVATE_DATA_ALIGNMENT; + limit_config.max_connection_count = 2; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", 32, "zerolimit", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + + /* a connection opened under a limit stays counted after the limit is + * removed, and connections opened afterward are counted too. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + limit_config.max_connection_count = 0; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_update_config(WOLFSENTRY_CONTEXT_ARGS_OUT, "zerolimit", -1, &limit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x09\x01", WOLFSENTRY_ACTION_RES_DISCONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_regression_prefix_specificity(void) { struct wolfsentry_context *wolfsentry; wolfsentry_action_res_t action_results; @@ -7275,6 +7323,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_api_invariants(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); WOLFSENTRY_RETURN_OK; } From 545f9947c3e6162e3222c261cfb76ee1edbe60b3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 10:58:08 -0700 Subject: [PATCH 188/255] F-15468 - https://fenrir.wolfssl.com/finding/15468 - Clear a route's purge links before reinserting it into the purge list --- src/routes.c | 3 +++ tests/unittests.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/routes.c b/src/routes.c index ad05a442..0f5e34a2 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3177,6 +3177,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_route_event_dispatch_by_route_wit WOLFSENTRY_LOCAL_VOID wolfsentry_route_purge_list_insert(struct wolfsentry_route_table *route_table, struct wolfsentry_route *route_to_insert) { struct wolfsentry_list_ent_header *point_ent; + /* deletion leaves an entry's own links stale, and a head insert keeps prev */ + route_to_insert->purge_links.prev = NULL; + route_to_insert->purge_links.next = NULL; for (wolfsentry_list_ent_get_first(&route_table->purge_list, &point_ent); point_ent; point_ent = point_ent->next) { const struct wolfsentry_route *point_route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(point_ent); if (point_route->meta.purge_after < route_to_insert->meta.purge_after) diff --git a/tests/unittests.c b/tests/unittests.c index 09f836c2..134656a3 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7277,6 +7277,55 @@ static wolfsentry_errcode_t test_regression_connection_count_zero_limit(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_purge_list_reinsert(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[3], matched_id; + static const char * const addrs[3] = { "\x0A\x08\x0A\x01", "\x0A\x08\x0A\x02", "\x0A\x08\x0A\x03" }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* three expired routes, each protected by a live connection */ + for (i = 0; i < 3; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, addrs[i], WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + } + /* moving the stalest entry to the head must not leave a stale back link, + * so a scan that visits every protected entry still terminates. */ + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[0], 40)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + for (i = 0; i < 3; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_regression_prefix_specificity(void) { struct wolfsentry_context *wolfsentry; wolfsentry_action_res_t action_results; @@ -7324,6 +7373,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_route_timing(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_list_reinsert(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); WOLFSENTRY_RETURN_OK; } From e71f8be2c9ffc3413b1df48d8b1ed445ac221e06 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 10:58:59 -0700 Subject: [PATCH 189/255] F-8764 - https://fenrir.wolfssl.com/finding/8764 - Bound the per-dispatch stale purge scan on the packet path --- src/routes.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes.c b/src/routes.c index 0f5e34a2..a087b938 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3264,11 +3264,15 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( while (table->purge_list.tail) { struct wolfsentry_route *route = NULL; int found = 0; + int examined = 0; /* re-scan from the most stale entry each pass, because a delete runs * application callbacks that can mutate the purge list; skip routes * that still have a live connection within the idle limit so one * protected route does not strand the expired routes behind it. */ for (cursor = table->purge_list.tail; cursor != NULL; cursor = cursor->prev) { + /* the per-dispatch purge runs on the packet path, so bound its scan */ + if ((mode == 2) && (++examined > 8)) + break; route = WOLFSENTRY_ROUTE_PURGE_HEADER_TO_TABLE_ENT_HEADER(cursor); if (mode != 3) { if (route->meta.purge_after > now) From 776333c01ade0da14fc1421cf5c4198d62f4cbd5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 11:23:29 -0700 Subject: [PATCH 190/255] F-8764 - https://fenrir.wolfssl.com/finding/8764 - Requeue connected routes so the bounded per-dispatch purge reaches the routes behind them --- src/routes.c | 27 ++++++++++++++++++++-- tests/unittests.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index a087b938..599e471d 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3216,6 +3216,8 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( int n = 0; wolfsentry_action_res_t fallback_action_results = 0; struct wolfsentry_list_ent_header *cursor; + int examined = 0; + wolfsentry_time_t requeue_delay = 0; #ifdef WOLFSENTRY_THREADSAFE int have_mutex = 0; int got_lock = 0; @@ -3233,6 +3235,11 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( WOLFSENTRY_ERROR_RETURN(ALREADY); } + if (mode == 2) { + if ((ret = WOLFSENTRY_FROM_EPOCH_TIME(WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS, 0 /* epoch_nsecs */, &requeue_delay)) < 0) + WOLFSENTRY_ERROR_RERETURN(ret); + } + if (action_results == NULL) action_results = &fallback_action_results; @@ -3264,7 +3271,7 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( while (table->purge_list.tail) { struct wolfsentry_route *route = NULL; int found = 0; - int examined = 0; + int requeue = 0; /* re-scan from the most stale entry each pass, because a delete runs * application callbacks that can mutate the purge list; skip routes * that still have a live connection within the idle limit so one @@ -3281,7 +3288,10 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time <= table->max_purgeable_idle_time))) { - continue; + if (mode != 2) + continue; + /* requeue it so later bounded scans reach the routes behind it */ + requeue = 1; } } found = 1; @@ -3303,6 +3313,19 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( have_mutex = 1; } #endif + if (requeue) { + wolfsentry_list_ent_delete(&table->purge_list, &route->purge_links); + /* not before the margin, unless the idle limit expires sooner */ + route->meta.purge_after = now + requeue_delay; + if ((table->max_purgeable_idle_time > 0) && + (now - route->meta.last_hit_time >= 0) && + (table->max_purgeable_idle_time - (now - route->meta.last_hit_time) < requeue_delay)) + { + route->meta.purge_after = route->meta.last_hit_time + table->max_purgeable_idle_time + 1; + } + wolfsentry_route_purge_list_insert(table, route); + continue; + } ret = wolfsentry_route_delete_0(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, table, NULL /* trigger_event */, route, action_results, 0 /* defer_p */); if (ret < 0) { #ifdef WOLFSENTRY_THREADSAFE diff --git a/tests/unittests.c b/tests/unittests.c index 134656a3..6494cbe9 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7326,6 +7326,64 @@ static wolfsentry_errcode_t test_regression_purge_list_reinsert(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t ids[9], matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0B, 0x00 }; + int i; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_table_max_purgeable_idle_time_set( + WOLFSENTRY_CONTEXT_ARGS_OUT, table, (wolfsentry_time_t)1 << 30)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* eight expired connected routes at the stale end, and one expired + * unconnected route queued behind them. */ + for (i = 0; i < 9; ++i) { + addr[3] = (byte)(i + 1); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &ids[i])); + if (i < 8) + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, (const char *)addr, WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + } + for (i = 0; i < 9; ++i) + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); + + for (i = 0; i < 3; ++i) + (void)wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[8], &meta)); + for (i = 0; i < 8; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_regression_prefix_specificity(void) { struct wolfsentry_context *wolfsentry; wolfsentry_action_res_t action_results; @@ -7374,6 +7432,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_limits(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_list_reinsert(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bounded_purge_progress(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); WOLFSENTRY_RETURN_OK; } From e1f1889d0f3fa8974e334aa91333c9f9950e811f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 12:01:45 -0700 Subject: [PATCH 191/255] F-4562 - https://fenrir.wolfssl.com/finding/4562 - Score bitmask route specificity by the bits its mask constrains --- src/routes.c | 21 +++++++-------------- tests/unittests.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/routes.c b/src/routes.c index 599e471d..8ab7dc55 100644 --- a/src/routes.c +++ b/src/routes.c @@ -295,28 +295,21 @@ static inline int addr_bitmask_match_size( const byte *b, unsigned int b_len) { - const byte *longer_addr, *shorter_addr; - unsigned int mask_len, i, padding_offset; + const byte *mask; + unsigned int mask_len, i; int ret = 0; + /* specificity is the number of bits the longer, maskful address's mask constrains */ if (a_len < b_len) { - longer_addr = b; mask_len = WOLFSENTRY_BITS_TO_BYTES(b_len >> 1); - shorter_addr = a; - padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(a_len); + mask = b + mask_len; } else { - longer_addr = a; mask_len = WOLFSENTRY_BITS_TO_BYTES(a_len >> 1); - shorter_addr = b; - padding_offset = mask_len - WOLFSENTRY_BITS_TO_BYTES(b_len); + mask = a + mask_len; } - for (ret = 0, i = 0; i < mask_len; ++i) { - if (i < padding_offset) - ret += popcount32(~(uint32_t)(longer_addr[i])) - 24; - else - ret += popcount32(~(uint32_t)(longer_addr[i] ^ shorter_addr[i - padding_offset])) - 24; - } + for (i = 0; i < mask_len; ++i) + ret += popcount32((uint32_t)mask[i]); return ret; } diff --git a/tests/unittests.c b/tests/unittests.c index 6494cbe9..51a068ca 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6448,6 +6448,40 @@ static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + /* non-nested masks: 192.0.0.1 inexactly matches a two-bit mask whose + * address is closer to it and a three-bit mask whose address is not; the + * three-bit mask constrains more bits, so it must win. + */ + memcpy(broad_addr, "\xC0\x00\x00\x00\xC0\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &broad_route, + &broad_id, &action_results)); + memcpy(narrow_addr, "\x00\x00\x00\x00\x38\x00\x00\x00", 8); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &narrow_route, + &narrow_id, &action_results)); + + memcpy(remote.sa.addr, "\xC0\x00\x00\x01", 4); /* NOLINT(bugprone-not-null-terminated-result) */ + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, &inexact_matches, &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(route_id == narrow_id); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); WOLFSENTRY_RETURN_OK; From e34098feb6dba2d177181f7ed74d7f6cc0321648 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 12:01:45 -0700 Subject: [PATCH 192/255] F-8764 - https://fenrir.wolfssl.com/finding/8764 - Compute the purge requeue deadline through the time callbacks --- src/routes.c | 17 +++++++++-------- tests/unittests.c | 9 +++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/routes.c b/src/routes.c index 8ab7dc55..87d7c0d9 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3229,7 +3229,7 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( } if (mode == 2) { - if ((ret = WOLFSENTRY_FROM_EPOCH_TIME(WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS, 0 /* epoch_nsecs */, &requeue_delay)) < 0) + if ((ret = WOLFSENTRY_INTERVAL_FROM_SECONDS(WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS, 0 /* howlong_nsecs */, &requeue_delay)) < 0) WOLFSENTRY_ERROR_RERETURN(ret); } @@ -3279,7 +3279,7 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( break; if ((route->meta.connection_count > 0) && (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && - ((table->max_purgeable_idle_time == 0) || (now - route->meta.last_hit_time <= table->max_purgeable_idle_time))) + ((table->max_purgeable_idle_time == 0) || (WOLFSENTRY_DIFF_TIME(now, route->meta.last_hit_time) <= table->max_purgeable_idle_time))) { if (mode != 2) continue; @@ -3307,15 +3307,16 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( } #endif if (requeue) { - wolfsentry_list_ent_delete(&table->purge_list, &route->purge_links); + wolfsentry_time_t idle = WOLFSENTRY_DIFF_TIME(now, route->meta.last_hit_time); + wolfsentry_time_t delay = requeue_delay; /* not before the margin, unless the idle limit expires sooner */ - route->meta.purge_after = now + requeue_delay; - if ((table->max_purgeable_idle_time > 0) && - (now - route->meta.last_hit_time >= 0) && - (table->max_purgeable_idle_time - (now - route->meta.last_hit_time) < requeue_delay)) + if ((table->max_purgeable_idle_time > 0) && (idle >= 0) && + (table->max_purgeable_idle_time - idle < requeue_delay)) { - route->meta.purge_after = route->meta.last_hit_time + table->max_purgeable_idle_time + 1; + delay = table->max_purgeable_idle_time - idle + 1; } + wolfsentry_list_ent_delete(&table->purge_list, &route->purge_links); + route->meta.purge_after = WOLFSENTRY_ADD_TIME(now, delay); wolfsentry_route_purge_list_insert(table, route); continue; } diff --git a/tests/unittests.c b/tests/unittests.c index 51a068ca..b07746ea 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7360,6 +7360,12 @@ static wolfsentry_errcode_t test_regression_purge_list_reinsert(void) { WOLFSENTRY_RETURN_OK; } +/* intervals in milliseconds, unlike the microsecond absolute clock. */ +static wolfsentry_errcode_t regression_ms_interval_from_seconds(time_t howlong_secs, long howlong_nsecs, wolfsentry_time_t *howlong) { + *howlong = ((wolfsentry_time_t)howlong_secs * 1000) + (howlong_nsecs / 1000000); + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *table; @@ -7386,6 +7392,7 @@ static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { regression_fake_now = 1000000; wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + wolfsentry_get_timecbs(wolfsentry)->interval_from_seconds = regression_ms_interval_from_seconds; WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); @@ -7408,9 +7415,11 @@ static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { for (i = 0; i < 3; ++i) (void)wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[8], &meta)); + /* the connected routes are requeued one purge margin out, as an interval */ for (i = 0; i < 8; ++i) { WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], &meta)); WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + WOLFSENTRY_EXIT_ON_FALSE(meta.purge_after == regression_fake_now + (WOLFSENTRY_ROUTE_PURGE_MARGIN_SECONDS * 1000)); } WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); From 842f12ac0d6eacf9369598022d7f825f9668600f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 12:06:43 -0700 Subject: [PATCH 193/255] F-4100 - https://fenrir.wolfssl.com/finding/4100 - Reject max_connection_count exceeding the per-route counter width --- src/wolfsentry_util.c | 2 ++ tests/unittests.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/wolfsentry_util.c b/src/wolfsentry_util.c index f4825115..152c121b 100644 --- a/src/wolfsentry_util.c +++ b/src/wolfsentry_util.c @@ -3697,6 +3697,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_eventconfig_check( WOLFSENTRY_ERROR_RETURN(INVALID_ARG); if (config->derogatory_threshold_for_penaltybox > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.derogatory_count))) WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + if (config->max_connection_count > MAX_UINT_OF(instance_of_field(struct wolfsentry_route, meta.connection_count))) + WOLFSENTRY_ERROR_RETURN(NUMERIC_ARG_TOO_BIG); ret = wolfsentry_route_check_flags_sensical(config->route_flags_to_add_on_insert); WOLFSENTRY_RERETURN_IF_ERROR(ret); diff --git a/tests/unittests.c b/tests/unittests.c index b07746ea..000c2978 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7232,6 +7232,23 @@ static wolfsentry_errcode_t test_regression_connection_limits(void) { limit_config.max_connection_count = 2; WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limit2", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &matched_id)); + /* a limit the 16-bit per-route counter cannot enforce is refused rather + * than silently capped, and the counter maximum is enforced exactly. */ + limit_config.max_connection_count = 0x10000U; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(NUMERIC_ARG_TOO_BIG, wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + limit_config.max_connection_count = 0xffffU; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "limitmax", -1, 10, &limit_config, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", 32, "limitmax", WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + for (i = 0; i < 0xffffU; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x03", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 0xffffU); + limit_config.max_connection_count = 2; + /* a route that opts out of connection counting is never limited. */ WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x08\x01", 32, "limit2", WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS, &id)); for (i = 0; i < 3; ++i) { From 286d0f872fa200d7feb7cbbee4679bda8e86ef3c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 12:40:38 -0700 Subject: [PATCH 194/255] F-8764 - https://fenrir.wolfssl.com/finding/8764 - Measure purge idle time from insertion for unhit routes and treat a negative idle limit as unlimited --- src/routes.c | 6 ++-- tests/unittests.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/routes.c b/src/routes.c index 87d7c0d9..70221517 100644 --- a/src/routes.c +++ b/src/routes.c @@ -3265,6 +3265,7 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( struct wolfsentry_route *route = NULL; int found = 0; int requeue = 0; + wolfsentry_time_t idle = 0; /* re-scan from the most stale entry each pass, because a delete runs * application callbacks that can mutate the purge list; skip routes * that still have a live connection within the idle limit so one @@ -3277,9 +3278,11 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( if (mode != 3) { if (route->meta.purge_after > now) break; + /* a route never hit since insertion has been idle since its insertion */ + idle = WOLFSENTRY_DIFF_TIME(now, (route->meta.last_hit_time > route->meta.insert_time) ? route->meta.last_hit_time : route->meta.insert_time); if ((route->meta.connection_count > 0) && (! (route->flags & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE)) && - ((table->max_purgeable_idle_time == 0) || (WOLFSENTRY_DIFF_TIME(now, route->meta.last_hit_time) <= table->max_purgeable_idle_time))) + ((table->max_purgeable_idle_time <= 0) || (idle <= table->max_purgeable_idle_time))) { if (mode != 2) continue; @@ -3307,7 +3310,6 @@ static wolfsentry_errcode_t wolfsentry_route_stale_purge_1( } #endif if (requeue) { - wolfsentry_time_t idle = WOLFSENTRY_DIFF_TIME(now, route->meta.last_hit_time); wolfsentry_time_t delay = requeue_delay; /* not before the margin, unless the idle limit expires sooner */ if ((table->max_purgeable_idle_time > 0) && (idle >= 0) && diff --git a/tests/unittests.c b/tests/unittests.c index 000c2978..59518147 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7444,6 +7444,77 @@ static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_regression_purge_idle_edges(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_route_table *table; + struct wolfsentry_route_metadata_exports meta; + struct wolfsentry_route_exports exports; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id, matched_id; + byte addr[4] = { 0x0A, 0x08, 0x0C, 0x01 }; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + regression_fake_now = 1000000; + wolfsentry_get_timecbs(wolfsentry)->get_time = regression_fake_get_time; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &table)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, 100)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* an imported route carrying a live connection but never hit is idle + * since its insertion, not since the epoch. */ + memset(&exports, 0, sizeof exports); + exports.sa_family = AF_INET; + exports.sa_proto = IPPROTO_TCP; + exports.remote_address = addr; + exports.remote.addr_len = 32; + exports.flags = WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN + | WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + exports.meta.connection_count = 1; + exports.meta.purge_after = regression_fake_now + 1; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_insert_by_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, &exports, &id, &action_results)); + regression_fake_now += 2; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_delete_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL /* caller_arg */, id, NULL /* trigger_label */, 0, &action_results)); + + /* a negative idle limit, like zero, never purges a connected route. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_idle_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, table, -1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", 32, NULL, WOLFSENTRY_ROUTE_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_dispatch(WOLFSENTRY_CONTEXT_ARGS_OUT, "\x0A\x08\x0C\x02", WOLFSENTRY_ACTION_RES_CONNECT, &matched_id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, id, regression_fake_now)); + ++regression_fake_now; + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ALREADY, wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL)); + WOLFSENTRY_EXIT_ON_FAILURE(regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, id, &meta)); + WOLFSENTRY_EXIT_ON_FALSE(meta.connection_count == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_regression_prefix_specificity(void) { struct wolfsentry_context *wolfsentry; wolfsentry_action_res_t action_results; @@ -7493,6 +7564,7 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_connection_count_zero_limit(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_list_reinsert(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_bounded_purge_progress(), OK)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_purge_idle_edges(), OK)); WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ERROR_CODE_IS(test_regression_prefix_specificity(), OK)); WOLFSENTRY_RETURN_OK; } From 69205d1ecfb918490e7a4bf1a56857dc1c8b1bda Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:38:35 -0700 Subject: [PATCH 195/255] Check stale purge results in the purge regression tests --- tests/unittests.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 59518147..01589f5a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -6488,6 +6488,11 @@ static wolfsentry_errcode_t test_regression_bitmask_match_specificity(void) { } #endif /* WOLFSENTRY_ADDR_BITMASK_MATCHING */ +/* a stale purge either purged something or had nothing due */ +static int regression_purge_ok(wolfsentry_errcode_t ret) { + return WOLFSENTRY_IS_SUCCESS(ret) || WOLFSENTRY_ERROR_CODE_IS(ret, ALREADY); +} + static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_route_table *table; @@ -6584,7 +6589,7 @@ static wolfsentry_errcode_t test_regression_stale_purge_connected_idle(void) { WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 2)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); - (void)wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); /* the route's live connection is within the idle limit, so it must survive. */ @@ -6732,7 +6737,7 @@ static wolfsentry_errcode_t test_regression_copy_metadata_purge_list(void) { /* the swapped-in route now has a stale purge_after, so it must be reachable * from the purge list and get purged. */ - (void)wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( @@ -7430,7 +7435,7 @@ static wolfsentry_errcode_t test_regression_bounded_purge_progress(void) { WOLFSENTRY_EXIT_ON_FAILURE(regression_set_purge_time(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (wolfsentry_time_t)(10 * (i + 1)))); for (i = 0; i < 3; ++i) - (void)wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL); + WOLFSENTRY_EXIT_ON_FALSE(regression_purge_ok(wolfsentry_route_stale_purge_one_opportunistically(WOLFSENTRY_CONTEXT_ARGS_OUT, NULL, NULL))); WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE(ITEM_NOT_FOUND, regression_route_meta(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[8], &meta)); /* the connected routes are requeued one purge margin out, as an interval */ for (i = 0; i < 8; ++i) { From de100188e0d8fbd4214aa3ea994dfe495c6d2e2c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:46:46 -0700 Subject: [PATCH 196/255] F-6255 - https://fenrir.wolfssl.com/finding/6255 - Set prev links when cloning coupled address family tables --- src/wolfsentry_internal.c | 1 + tests/unittests.c | 109 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/src/wolfsentry_internal.c b/src/wolfsentry_internal.c index 33f6e221..2da3331d 100644 --- a/src/wolfsentry_internal.c +++ b/src/wolfsentry_internal.c @@ -275,6 +275,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_coupled_table_clone( goto out; new1->parent_table = dest_table1; new2->parent_table = dest_table2; + new1->prev = prev; if (prev) prev->next = new1; else diff --git a/tests/unittests.c b/tests/unittests.c index 01589f5a..b002663f 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4490,6 +4490,107 @@ static int test_route_format_json_unknown_protocol_numeric(void) { #endif /* !WOLFSENTRY_NO_JSON || WOLFSENTRY_JSON_DUMP_UTILS */ +static int test_static_routes_clone_addr_family_links(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_context *clone = NULL; + wolfsentry_action_res_t action_results; + struct wolfsentry_table_ent_header *i, *prev; + wolfsentry_addr_bits_t bits; + unsigned int n; + static const char * const names[3] = { "clone_AF_0", "clone_AF_1", "clone_AF_2" }; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (n = 0; n < 3; ++n) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_install( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + n), + names[n], + WOLFSENTRY_LENGTH_NULL_TERMINATED, + my_addr_family_parser, + my_addr_family_formatter, + (int)(8 * (n + 1)) /* max_addr_bits */)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_context_clone( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &clone, + WOLFSENTRY_CLONE_FLAG_NONE)); + + prev = NULL; + n = 0; + for (i = clone->addr_families_bynumber->header.head; i != NULL; i = i->next) { + WOLFSENTRY_EXIT_ON_FALSE(i->prev == prev); + WOLFSENTRY_EXIT_ON_FALSE(i->parent_table == &clone->addr_families_bynumber->header); + prev = i; + ++n; + } + WOLFSENTRY_EXIT_ON_FALSE(n == 3); + WOLFSENTRY_EXIT_ON_FALSE(n == clone->addr_families_bynumber->header.n_ents); + WOLFSENTRY_EXIT_ON_FALSE(prev == clone->addr_families_bynumber->header.tail); + + /* removing the middle family from the clone must leave its neighbors + * reachable. + */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_handler_remove_bynumber( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)WOLFSENTRY_AF_USER_OFFSET, + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 8); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &bits)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(clone), + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 2), + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 24); + + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.n_ents == 2); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head != NULL); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head->prev == NULL); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.head->next == clone->addr_families_bynumber->header.tail); + WOLFSENTRY_EXIT_ON_FALSE(clone->addr_families_bynumber->header.tail->prev == clone->addr_families_bynumber->header.head); + + /* the source context must be untouched by edits to the clone. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_addr_family_max_addr_bits( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (wolfsentry_addr_family_t)(WOLFSENTRY_AF_USER_OFFSET + 1), + &bits)); + WOLFSENTRY_EXIT_ON_FALSE(bits == 16); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -11667,6 +11768,14 @@ int main(int argc, char* argv[]) { #endif #endif +#ifdef TEST_STATIC_ROUTES + ret = test_static_routes_clone_addr_family_links(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_clone_addr_family_links failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + #ifdef TEST_DYNAMIC_RULES ret = test_dynamic_rules(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { From 7bb193d10662b43a11d5c586e75f2fde81c46659 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:48:02 -0700 Subject: [PATCH 197/255] F-4280 - https://fenrir.wolfssl.com/finding/4280 - Skip the reserved null ID when the entry ID counter wraps --- src/wolfsentry_internal.c | 16 ++++++- tests/unittests.c | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_internal.c b/src/wolfsentry_internal.c index 2da3331d..110c7e88 100644 --- a/src/wolfsentry_internal.c +++ b/src/wolfsentry_internal.c @@ -318,6 +318,7 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_id_allocate( ) { wolfsentry_errcode_t ret; + wolfsentry_ent_id_t first_candidate = WOLFSENTRY_ENT_ID_NONE; WOLFSENTRY_HAVE_MUTEX_OR_RETURN(); @@ -327,7 +328,20 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_id_allocate( if (ret < 0) break; } else { - ent->id = ++wolfsentry->mk_id_cb_state.id_counter; + ++wolfsentry->mk_id_cb_state.id_counter; + /* the counter wraps past the reserved null ID; collisions with + * live IDs are handled by the ITEM_ALREADY_PRESENT retry. + */ + if (wolfsentry->mk_id_cb_state.id_counter == WOLFSENTRY_ENT_ID_NONE) + ++wolfsentry->mk_id_cb_state.id_counter; + if (first_candidate == WOLFSENTRY_ENT_ID_NONE) + first_candidate = wolfsentry->mk_id_cb_state.id_counter; + else if (wolfsentry->mk_id_cb_state.id_counter == first_candidate) { + ent->id = WOLFSENTRY_ENT_ID_NONE; + ret = WOLFSENTRY_ERROR_ENCODE(SYS_RESOURCE_FAILED); + break; + } + ent->id = wolfsentry->mk_id_cb_state.id_counter; } ret = wolfsentry_table_ent_insert_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, ent); diff --git a/tests/unittests.c b/tests/unittests.c index b002663f..9314ba43 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4591,6 +4591,89 @@ static int test_static_routes_clone_addr_family_links(void) { WOLFSENTRY_RETURN_OK; } +static int test_static_routes_id_wrap(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id1, id2; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + wolfsentry->mk_id_cb_state.id_counter = (wolfsentry_ent_id_t)MAX_UINT_OF(wolfsentry_ent_id_t) - 1U; + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id1, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id1 == (wolfsentry_ent_id_t)MAX_UINT_OF(wolfsentry_ent_id_t)); + + /* the next allocation wraps the counter and must still yield a valid, + * unique ID. + */ + local.sa.sa_port = 444; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 != WOLFSENTRY_ENT_ID_NONE); + WOLFSENTRY_EXIT_ON_FALSE(id2 != id1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id2, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + /* after the wrap the counter must never rest on the reserved null ID. */ + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter != WOLFSENTRY_ENT_ID_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -11774,6 +11857,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_static_routes_clone_addr_family_links failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_static_routes_id_wrap(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_id_wrap failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From f383969a52d683a1d860fab2c96c5f8c25b72d76 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:49:23 -0700 Subject: [PATCH 198/255] F-8755 - https://fenrir.wolfssl.com/finding/8755 - Reclaim the newest entry ID on delete instead of the second newest --- src/wolfsentry_internal.c | 4 +- tests/unittests.c | 123 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/wolfsentry_internal.c b/src/wolfsentry_internal.c index 110c7e88..039bb99b 100644 --- a/src/wolfsentry_internal.c +++ b/src/wolfsentry_internal.c @@ -444,8 +444,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_table_ent_delete_by_id_1(WOLFSE --wolfsentry->ents_by_id.n_ents; ++wolfsentry->ents_by_id.n_deletes; - /* if possible, reclaim the ID */ - if ((! wolfsentry->mk_id_cb) && (ent->id == (wolfsentry->mk_id_cb_state.id_counter - 1))) + /* if possible, reclaim the ID (the counter equals the newest issued ID). */ + if ((! wolfsentry->mk_id_cb) && (ent->id == wolfsentry->mk_id_cb_state.id_counter)) --wolfsentry->mk_id_cb_state.id_counter; ent->id = WOLFSENTRY_ENT_ID_NONE; diff --git a/tests/unittests.c b/tests/unittests.c index 9314ba43..6ffe31e1 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4674,6 +4674,124 @@ static int test_static_routes_id_wrap(void) { WOLFSENTRY_RETURN_OK; } +static int test_static_routes_id_reclaim(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id1, id2, id3, counter_after_insert; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + unsigned int n; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id1, &action_results)); + counter_after_insert = wolfsentry->mk_id_cb_state.id_counter; + WOLFSENTRY_EXIT_ON_FALSE(id1 == counter_after_insert); + + /* deleting and re-inserting the newest entry repeatedly must reuse its + * ID rather than grow the counter. + */ + for (n = 0; n < 8; ++n) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == counter_after_insert - 1U); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 == id1); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == counter_after_insert); + } + + /* deleting an older entry must not disturb the counter, and the next + * allocation must not collide with the surviving newest entry. + */ + local.sa.sa_port = 444; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id2, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id2 == counter_after_insert + 1U); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id1, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == id2); + local.sa.sa_port = 445; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id3, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(id3 == id2 + 1U); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id3, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id2, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(wolfsentry->mk_id_cb_state.id_counter == id2 - 1U); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -11862,6 +11980,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_static_routes_id_wrap failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_static_routes_id_reclaim(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_id_reclaim failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_DYNAMIC_RULES From c2e538e61713dc58d886a46bfbbbac1156ef735e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:50:27 -0700 Subject: [PATCH 199/255] F-10490 - https://fenrir.wolfssl.com/finding/10490 - Report entry destructor failures during table teardown instead of ignoring them --- src/wolfsentry_internal.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wolfsentry_internal.c b/src/wolfsentry_internal.c index 039bb99b..2555f4d0 100644 --- a/src/wolfsentry_internal.c +++ b/src/wolfsentry_internal.c @@ -570,7 +570,10 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_table_free_ents(WOLFSENTRY_CONT * e.g., a coupled wolfsentry_addr_family_byname is freed when the * corresponding wolfsentry_addr_family_bynumber is freed. */ - table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, i, NULL /* action_results */); + ret = table->free_fn(WOLFSENTRY_CONTEXT_ARGS_OUT, i, NULL /* action_results */); + /* teardown must run to completion and release its lock, so report a + * failed destructor and keep freeing instead of abandoning the table. */ + WOLFSENTRY_WARN_ON_FAILURE(ret); i = next; } WOLFSENTRY_RETURN_OK; From b440cf7c912d5b80f6e0cb3e41e30ba9bdb481a4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 14:46:27 -0700 Subject: [PATCH 200/255] F-4564 - https://fenrir.wolfssl.com/finding/4564 - Initialize track-peer insert results and merge them only on success --- src/action_builtins.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/action_builtins.c b/src/action_builtins.c index 19cdf255..f9884cfd 100644 --- a/src/action_builtins.c +++ b/src/action_builtins.c @@ -47,7 +47,7 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( wolfsentry_addr_bits_t rule_local_addr_len, rule_remote_addr_len; struct wolfsentry_route_exports target_exports; wolfsentry_errcode_t ret; - wolfsentry_action_res_t insert_action_results; + wolfsentry_action_res_t insert_action_results = WOLFSENTRY_ACTION_RES_NONE; (void)action; (void)handler_arg; @@ -117,14 +117,18 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( WOLFSENTRY_RERETURN_IF_ERROR(ret); /* can't pass action_results directly to _route_insert -- it would get zeroed. */ - WOLFSENTRY_WARN_ON_FAILURE( - wolfsentry_route_insert_by_exports_into_table( - WOLFSENTRY_CONTEXT_ARGS_OUT, - route_table, - NULL /* void *caller_arg*/, - &target_exports, - NULL /* id */, - &insert_action_results)); + ret = wolfsentry_route_insert_by_exports_into_table( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route_table, + NULL /* void *caller_arg*/, + &target_exports, + NULL /* id */, + &insert_action_results); + WOLFSENTRY_WARN_ON_FAILURE(ret); + /* no route survives a failed insert, so drop INSERTED (it would suppress generator + * accounting); other bits are the chain's decisions and are NONE on early failure. */ + if (ret < 0) + WOLFSENTRY_CLEAR_BITS(insert_action_results, WOLFSENTRY_ACTION_RES_INSERTED); *action_results |= insert_action_results; /* copying over the bits is crucial to inhibit double-accounting for * connection_count, derogatory_count, and commendable_count. */ From 6c504569a17a9622900a3b5ecbc6abf5b3cd565a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 14:46:28 -0700 Subject: [PATCH 201/255] F-2443 - https://fenrir.wolfssl.com/finding/2443 - Refuse track-peer routes that would never be purged --- src/action_builtins.c | 26 +++- tests/unittests.c | 270 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+), 5 deletions(-) diff --git a/src/action_builtins.c b/src/action_builtins.c index f9884cfd..61cfcefd 100644 --- a/src/action_builtins.c +++ b/src/action_builtins.c @@ -44,6 +44,8 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( { wolfsentry_route_flags_t rule_flags; const struct wolfsentry_event *rule_parent_event = NULL, *rule_aux_event = NULL; + const struct wolfsentry_event *ephemeral_parent_event = NULL; + const struct wolfsentry_eventconfig_internal *ephemeral_config; wolfsentry_addr_bits_t rule_local_addr_len, rule_remote_addr_len; struct wolfsentry_route_exports target_exports; wolfsentry_errcode_t ret; @@ -99,17 +101,31 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( rule_parent_event = wolfsentry_route_parent_event(rule_route); if (rule_parent_event) rule_aux_event = wolfsentry_event_get_aux_event(rule_parent_event); - if (rule_aux_event) { - target_exports.parent_event_label = wolfsentry_event_get_label(rule_aux_event); - target_exports.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; - } else if (rule_parent_event) { - target_exports.parent_event_label = wolfsentry_event_get_label(rule_parent_event); + if (rule_aux_event) + ephemeral_parent_event = rule_aux_event; + else + ephemeral_parent_event = rule_parent_event; + + if (ephemeral_parent_event != NULL) { + target_exports.parent_event_label = wolfsentry_event_get_label(ephemeral_parent_event); target_exports.parent_event_label_len = WOLFSENTRY_LENGTH_NULL_TERMINATED; } else { target_exports.parent_event_label = NULL; target_exports.parent_event_label_len = 0; } + if ((ephemeral_parent_event != NULL) && (ephemeral_parent_event->config != NULL)) + ephemeral_config = ephemeral_parent_event->config; + else + ephemeral_config = &wolfsentry->config; + + /* an ephemeral route with no purge time escapes both the idle purge and + * max_purgeable_routes, letting a remote peer grow the table without bound. + * skip the insert but return OK so the rest of the match chain still runs. + */ + if (ephemeral_config->config.route_idle_time_for_purge <= 0) + WOLFSENTRY_RETURN_OK; + target_exports.private_data = NULL; target_exports.private_data_size = 0; diff --git a/tests/unittests.c b/tests/unittests.c index 6ffe31e1..3fadf6f3 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7876,6 +7876,271 @@ static int test_dynamic_rules_regressions(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_dynamic_count_main_routes( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_hitcount_t *n_routes) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; + + *n_routes = 0; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + + ret = wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes); + if (ret >= 0) { + ret = wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + if (ret >= 0) { + for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); + ret >= 0; + ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) + { + ++*n_routes; + } + ret = wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + } + } + + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_ERROR_RERETURN(ret); +} + +static wolfsentry_errcode_t test_track_peer_sentinel_action( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_context, + void *caller_arg, + const struct wolfsentry_event *event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)caller_arg; + (void)event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + + ++*(int *)handler_context; + + WOLFSENTRY_RETURN_OK; +} + +static int test_track_peer_ephemeral_route_bound(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig aux_config; + int sentinel_hits = 0; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t rule_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t dispatch_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_hitcount_t n_routes; + int i; + +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* an aux event with no purge time would make every tracked peer a permanent route. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_unpurgeable", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &aux_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &aux_config.route_idle_time_for_purge)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_purgeable", + -1 /* label_len */, + 10, + &aux_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + WOLFSENTRY_BUILTIN_LABEL_PREFIX "track-peer-v1", + -1)); + + /* a refused insert must not stop the rest of the match chain. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_sentinel", + -1, + WOLFSENTRY_ACTION_FLAG_NONE, + test_track_peer_sentinel_action, + &sentinel_hits, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + "track_peer_sentinel", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + "track_peer_unpurgeable", + -1)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 0; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memset(remote.sa.addr, 0, sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + rule_flags, + "track_peer_generator", + -1, + &id, + &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 1); + + WOLFSENTRY_SET_BITS(dispatch_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + for (i = 1; i <= 3; ++i) { + remote.sa.sa_port = (wolfsentry_port_t)(1000 + i); + remote.sa.addr[0] = 10; + remote.sa.addr[1] = 0; + remote.sa.addr[2] = 0; + remote.sa.addr[3] = (byte)i; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 1); + WOLFSENTRY_EXIT_ON_FALSE(sentinel_hits == 3); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_generator", + -1, + "track_peer_purgeable", + -1)); + + for (i = 1; i <= 3; ++i) { + remote.sa.sa_port = (wolfsentry_port_t)(2000 + i); + remote.sa.addr[0] = 10; + remote.sa.addr[1] = 0; + remote.sa.addr[2] = 1; + remote.sa.addr[3] = (byte)i; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_count_main_routes(WOLFSENTRY_CONTEXT_ARGS_OUT, &n_routes)); + WOLFSENTRY_EXIT_ON_FALSE(n_routes == 4); + WOLFSENTRY_EXIT_ON_FALSE(sentinel_hits == 6); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -12053,6 +12318,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_dynamic_rules_regressions failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_track_peer_ephemeral_route_bound(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_track_peer_ephemeral_route_bound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 9b3c11c0beb0d3d2bfe3b7a4c835e0c158e69419 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 14:46:29 -0700 Subject: [PATCH 202/255] F-2466 - https://fenrir.wolfssl.com/finding/2466 - Count the connection that spawns a track-peer route on that route --- src/action_builtins.c | 47 +++++- tests/unittests.c | 357 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 402 insertions(+), 2 deletions(-) diff --git a/src/action_builtins.c b/src/action_builtins.c index 61cfcefd..9f588139 100644 --- a/src/action_builtins.c +++ b/src/action_builtins.c @@ -24,6 +24,43 @@ #define WOLFSENTRY_SOURCE_ID WOLFSENTRY_SOURCE_ID_ACTION_BUILTINS_C +/* the public insert wrappers zero action_results before the route table sees + * them, so the CONNECT that spawns a tracking route is counted here instead, + * on exactly the route that was just inserted. caller must hold the lock. + */ +static wolfsentry_errcode_t wolfsentry_builtin_track_peer_count_connect( + WOLFSENTRY_CONTEXT_ARGS_IN, + wolfsentry_ent_id_t route_id, + const struct wolfsentry_eventconfig_internal *config) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route *route = NULL; + uint16_t new_count; + + ret = wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, route_id, (struct wolfsentry_table_ent_header **)&route); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + if (route->header.parent_table == NULL) + WOLFSENTRY_ERROR_RETURN(INTERNAL_CHECK_FATAL); + if (route->header.parent_table->ent_type != WOLFSENTRY_OBJECT_TYPE_ROUTE) + WOLFSENTRY_ERROR_RETURN(WRONG_OBJECT); + + if (WOLFSENTRY_CHECK_BITS(WOLFSENTRY_ATOMIC_LOAD(route->flags), WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) + WOLFSENTRY_RETURN_OK; + + /* dispatch counts connections only under a limit, so match it: an unlimited route must stay at zero. */ + if (config->config.max_connection_count == 0) + WOLFSENTRY_RETURN_OK; + + if (WOLFSENTRY_ATOMIC_LOAD(route->meta.connection_count) >= config->config.max_connection_count) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(route->meta.connection_count, new_count); + if (new_count == 0) + WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + + WOLFSENTRY_RETURN_OK; +} + /* this simple action creates a new tracking rule, either as a fallthrough or as * a side effect. it can be used to open a pinhole route for bidirectional * traffic, or to track derogatory events on a locally bound port (e.g. crypto @@ -50,6 +87,7 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( struct wolfsentry_route_exports target_exports; wolfsentry_errcode_t ret; wolfsentry_action_res_t insert_action_results = WOLFSENTRY_ACTION_RES_NONE; + wolfsentry_ent_id_t new_route_id = WOLFSENTRY_ENT_ID_NONE; (void)action; (void)handler_arg; @@ -138,13 +176,18 @@ static wolfsentry_errcode_t wolfsentry_builtin_action_track_peer( route_table, NULL /* void *caller_arg*/, &target_exports, - NULL /* id */, + &new_route_id, &insert_action_results); WOLFSENTRY_WARN_ON_FAILURE(ret); /* no route survives a failed insert, so drop INSERTED (it would suppress generator * accounting); other bits are the chain's decisions and are NONE on early failure. */ - if (ret < 0) + if (ret < 0) { WOLFSENTRY_CLEAR_BITS(insert_action_results, WOLFSENTRY_ACTION_RES_INSERTED); + } else if (WOLFSENTRY_CHECK_BITS(insert_action_results, WOLFSENTRY_ACTION_RES_INSERTED) && + WOLFSENTRY_CHECK_BITS(*action_results, WOLFSENTRY_ACTION_RES_CONNECT)) + { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_builtin_track_peer_count_connect(WOLFSENTRY_CONTEXT_ARGS_OUT, new_route_id, ephemeral_config)); + } *action_results |= insert_action_results; /* copying over the bits is crucial to inhibit double-accounting for * connection_count, derogatory_count, and commendable_count. */ diff --git a/tests/unittests.c b/tests/unittests.c index 3fadf6f3..5957ce41 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8141,6 +8141,358 @@ static int test_track_peer_ephemeral_route_bound(void) { WOLFSENTRY_RETURN_OK; } +/* find the non-wildcard route for the peer whose remote address ends in remote_last_byte. */ +static wolfsentry_errcode_t test_dynamic_ephemeral_route_exports( + WOLFSENTRY_CONTEXT_ARGS_IN, + byte remote_last_byte, + struct wolfsentry_route_exports *exports_out) +{ + wolfsentry_errcode_t ret; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_cursor *cursor; + struct wolfsentry_route *route; + struct wolfsentry_route_exports exports; + int found = 0; + + ret = wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT); + WOLFSENTRY_RERETURN_IF_ERROR(ret); + + ret = wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes); + if (ret >= 0) { + ret = wolfsentry_route_table_iterate_start(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + if (ret >= 0) { + for (ret = wolfsentry_route_table_iterate_current(main_routes, cursor, &route); + ret >= 0; + ret = wolfsentry_route_table_iterate_next(main_routes, cursor, &route)) + { + ret = wolfsentry_route_export(WOLFSENTRY_CONTEXT_ARGS_OUT, route, &exports); + if (ret < 0) + break; + if ((! WOLFSENTRY_CHECK_BITS(exports.flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD)) && + (exports.remote.addr_len == 4 * BITS_PER_BYTE) && + (exports.remote_address[3] == remote_last_byte)) + { + *exports_out = exports; + found = 1; + break; + } + } + if (ret >= 0 || WOLFSENTRY_ERROR_CODE_IS(ret, ITEM_NOT_FOUND)) + ret = wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor); + else + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_route_table_iterate_end(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &cursor)); + } + } + + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + if ((ret >= 0) && (! found)) + ret = WOLFSENTRY_ERROR_ENCODE(ITEM_NOT_FOUND); + + WOLFSENTRY_ERROR_RERETURN(ret); +} + +static int test_track_peer_connect_count(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_eventconfig limit_config, nocount_config, nolimit_config; + struct wolfsentry_route_exports ephemeral_exports; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_route_flags_t rule_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t dispatch_flags = WOLFSENTRY_ROUTE_FLAG_NONE; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_default_policy_set(WOLFSENTRY_CONTEXT_ARGS_OUT, WOLFSENTRY_ACTION_RES_ACCEPT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1 /* label_len */, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &limit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &limit_config.route_idle_time_for_purge)); + limit_config.max_connection_count = 1; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_limit", + -1 /* label_len */, + 10, + &limit_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &nocount_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &nocount_config.route_idle_time_for_purge)); + nocount_config.max_connection_count = 1; + nocount_config.route_flags_to_add_on_insert = WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_nocount", + -1 /* label_len */, + 10, + &nocount_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + WOLFSENTRY_ACTION_TYPE_MATCH, + WOLFSENTRY_BUILTIN_LABEL_PREFIX "track-peer-v1", + -1)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_limit", + -1)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 0; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memset(remote.sa.addr, 0, sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD); + WOLFSENTRY_SET_BITS(rule_flags, WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + rule_flags, + "track_peer_cc_generator", + -1, + &id, + &action_results)); + + WOLFSENTRY_SET_BITS(dispatch_flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + + remote.sa.sa_port = 1234; + memcpy(remote.sa.addr, "\12\0\2\1", sizeof remote.addr_buf); + + /* the CONNECT that spawns the tracking route must be counted on that route. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* with a per-peer limit of 1, a second concurrent CONNECT from the same peer is rejected. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* the matching DISCONNECT brings the count back to zero. */ + action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + /* and the peer can connect again. */ + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 1, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); + + /* a tracking route flagged dont-count-current-connections stays at zero. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_nocount", + -1)); + + remote.sa.sa_port = 2345; + memcpy(remote.sa.addr, "\12\0\3\2", sizeof remote.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 2, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(ephemeral_exports.flags, WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + /* with the unlimited setting dispatch never counts connections, so the + * spawning CONNECT must not leave a count a later DISCONNECT can't undo. + */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &nolimit_config)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &nolimit_config.route_idle_time_for_purge)); + nolimit_config.max_connection_count = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_nolimit", + -1 /* label_len */, + 10, + &nolimit_config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "track_peer_cc_generator", + -1, + "track_peer_cc_nolimit", + -1)); + + remote.sa.sa_port = 3456; + memcpy(remote.sa.addr, "\12\0\4\3", sizeof remote.addr_buf); + + action_results = WOLFSENTRY_ACTION_RES_CONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_INSERTED)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 3, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch_with_inited_result( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + dispatch_flags, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &id, + &inexact_matches, + &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + + WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 3, &ephemeral_exports)); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -12323,6 +12675,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_track_peer_ephemeral_route_bound failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_track_peer_connect_count(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_track_peer_connect_count failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 0244f4b9a05d67c10f7f91cdf23eb93de6eae2ed Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:46:27 -0700 Subject: [PATCH 203/255] F-2443 - https://fenrir.wolfssl.com/finding/2443 - Set track-peer test addresses through a full-width buffer for C89 builds --- tests/unittests.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 5957ce41..8098db60 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -7951,6 +7951,7 @@ static int test_track_peer_ephemeral_route_bound(void) { wolfsentry_ent_id_t id; wolfsentry_hitcount_t n_routes; int i; + byte addr[4]; #ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; @@ -8074,10 +8075,11 @@ static int test_track_peer_ephemeral_route_bound(void) { for (i = 1; i <= 3; ++i) { remote.sa.sa_port = (wolfsentry_port_t)(1000 + i); - remote.sa.addr[0] = 10; - remote.sa.addr[1] = 0; - remote.sa.addr[2] = 0; - remote.sa.addr[3] = (byte)i; + addr[0] = 10; + addr[1] = 0; + addr[2] = 0; + addr[3] = (byte)i; + memcpy(remote.sa.addr, addr, sizeof addr); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_route_event_dispatch( @@ -8109,10 +8111,11 @@ static int test_track_peer_ephemeral_route_bound(void) { for (i = 1; i <= 3; ++i) { remote.sa.sa_port = (wolfsentry_port_t)(2000 + i); - remote.sa.addr[0] = 10; - remote.sa.addr[1] = 0; - remote.sa.addr[2] = 1; - remote.sa.addr[3] = (byte)i; + addr[0] = 10; + addr[1] = 0; + addr[2] = 1; + addr[3] = (byte)i; + memcpy(remote.sa.addr, addr, sizeof addr); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_route_event_dispatch( From 865f98752248779c1e57251a57b8a9707d1a221a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:30 -0700 Subject: [PATCH 204/255] F-6259 - https://fenrir.wolfssl.com/finding/6259 - Print doubles with 17 significant digits so they round-trip exactly --- src/json/centijson_sax.c | 6 +---- tests/unittests.c | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 1c71d536..842ebe75 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -1315,11 +1315,7 @@ json_dump_uint64(uint64_t u64, JSON_DUMP_CALLBACK write_func, void* user_data) WOLFSENTRY_API int json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) { -#if defined(FREERTOS) || (__STDC_VERSION__ < 199901L) - static const char fmt[] = "%.16f"; -#else - static const char fmt[] = "%.16lg"; -#endif + static const char fmt[] = "%.17g"; static const size_t extra_bytes = 2; /* Space reserved for ".0" */ #ifdef CENTIJSON_USE_LOCALE struct lconv* locale_info; diff --git a/tests/unittests.c b/tests/unittests.c index 8098db60..6b52d13d 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -12186,6 +12186,56 @@ static int test_json_af_local_registered_handler(void) { WOLFSENTRY_RETURN_OK; } +struct sax_dump_capture { + char text[128]; + size_t len; +}; + +static int sax_dump_capture_cb(const unsigned char *str, size_t size, void *user_data) { + struct sax_dump_capture *cap = (struct sax_dump_capture *)user_data; + if (size >= sizeof cap->text - cap->len) + return -1; + memcpy(cap->text + cap->len, str, size); + cap->len += size; + cap->text[cap->len] = '\0'; + return 0; +} + +static int sax_dump_double_text(double dbl, struct sax_dump_capture *cap) { + cap->len = 0; + cap->text[0] = '\0'; + return json_dump_double(dbl, sax_dump_capture_cb, cap); +} + +static wolfsentry_errcode_t test_json_sax_double_roundtrip(void) { + static const double corpus[] = { + 1.0 + 2.220446049250313e-16, + 0.1 + 0.2, + 0.1, + 1.0 / 3.0, + -2.0 / 3.0, + 1.7976931348623157e308, + 2.2250738585072014e-308, + 4.9406564584124654e-324, + 1e-300, + 123456789.12345679 + }; + struct sax_dump_capture cap; + size_t i; + double reparsed; + + for (i = 0; i < sizeof corpus / sizeof corpus[0]; ++i) { + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(corpus[i], &cap)); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + if (memcmp(&reparsed, &corpus[i], sizeof reparsed) != 0) { + (void)fprintf(stderr, "double did not survive dump and reparse: %s\n", cap.text); + return 1; + } + } + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -12853,6 +12903,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_af_local_registered_handler failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_double_roundtrip(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_double_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 8a15438f9aa904cb47f44b013ba71e65691f33da Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:38 -0700 Subject: [PATCH 205/255] F-8756 - https://fenrir.wolfssl.com/finding/8756 - Dump non-finite doubles as null instead of an invalid token --- src/json/centijson_sax.c | 5 +++++ tests/unittests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 842ebe75..b3009997 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -54,6 +54,7 @@ #ifdef CENTIJSON_USE_LOCALE #include /* localeconv() */ #endif +#include /* DBL_MAX */ #include /* snprintf() */ #include #include @@ -1330,6 +1331,10 @@ json_dump_double(double dbl, JSON_DUMP_CALLBACK write_func, void* user_data) char* fp; int ret; + /* JSON has no representation for infinity or NaN. */ + if(dbl != dbl || dbl > DBL_MAX || dbl < -DBL_MAX) + return write_func((const unsigned char *)"null", 4, user_data); + while(1) { n = snprintf(buffer, capacity, fmt, dbl); if(n >= 0 && (size_t)n < capacity) diff --git a/tests/unittests.c b/tests/unittests.c index 6b52d13d..867f3afa 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -12236,6 +12236,32 @@ static wolfsentry_errcode_t test_json_sax_double_roundtrip(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { + double inf; + double reparsed; + struct sax_dump_capture cap; + + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"1e999", 5, &inf)); + WOLFSENTRY_EXIT_ON_FALSE(inf > 1.7976931348623157e308); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(-inf, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)"nan", 3, &reparsed)); + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(reparsed, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "null") == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(sax_dump_double_text(1.0, &cap)); + WOLFSENTRY_EXIT_ON_FALSE(strcmp(cap.text, "1.0") == 0); + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double((const unsigned char *)cap.text, cap.len, &reparsed)); + WOLFSENTRY_EXIT_ON_FALSE(reparsed == 1.0); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -12909,6 +12935,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_sax_double_roundtrip failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_nonfinite_dump(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_nonfinite_dump failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 03e4bf1faf290dfbdcda9cf1aed1e0d607dbdfde Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:38 -0700 Subject: [PATCH 206/255] F-10508 - https://fenrir.wolfssl.com/finding/10508 - Reject incomplete true, false and null literals at end of input --- src/json/centijson_sax.c | 13 +++++-- tests/unittests.c | 78 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index b3009997..6e681879 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -405,8 +405,12 @@ json_literal_automaton(JSON_PARSER* parser, const unsigned char* input, size_t s if(off < size && !IS_TOKEN_BOUNDARY(input[off])) { memcpy(&parser->pos, &parser->value_pos, sizeof(JSON_INPUT_POS)); json_raise(parser, JSON_ERR_SYNTAX); - } else if(input == NULL /* EOF */ || - (off < size && IS_TOKEN_BOUNDARY(input[off]))) { + } else if(input == NULL /* EOF */) { + if(parser->substate < literal_size) + json_raise_for_value(parser, JSON_ERR_SYNTAX); + else + json_process(parser, type, NULL, 0); + } else if(off < size && IS_TOKEN_BOUNDARY(input[off])) { json_process(parser, type, NULL, 0); } return off; @@ -981,7 +985,10 @@ json_fini(JSON_PARSER* parser, JSON_INPUT_POS* p_pos) if(parser->automaton != AUTOMATON_MAIN) { json_raise(parser, JSON_ERR_SYNTAX); } - } else if(parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF)) { + } + + if(parser->errcode >= 0 && + (parser->nesting_level != 0 || !(parser->state & CAN_SEE_EOF))) { json_raise_unexpected(parser); } } diff --git a/tests/unittests.c b/tests/unittests.c index 867f3afa..260a9f4a 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -12201,6 +12201,14 @@ static int sax_dump_capture_cb(const unsigned char *str, size_t size, void *user return 0; } +static int sax_noop_process_cb(JSON_TYPE type, const unsigned char *data, size_t data_size, void *user_data) { + (void)type; + (void)data; + (void)data_size; + (void)user_data; + return 0; +} + static int sax_dump_double_text(double dbl, struct sax_dump_capture *cap) { cap->len = 0; cap->text[0] = '\0'; @@ -12262,6 +12270,70 @@ static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_truncated_input(void) { + static const JSON_CALLBACKS callbacks = { sax_noop_process_cb }; + static const JSON_CONFIG sax_config = { + 4096, /* max_total_len */ + 1000, /* max_total_values */ + 20, /* max_number_len */ + 256, /* max_string_len */ + 64, /* max_key_len */ + 10, /* max_nesting_level */ + 0 /* flags */ + }; + static const char * const truncated[] = { + "t", "tr", "tru", + "f", "fa", "fal", "fals", + "n", "nu", "nul", + "[t", "[fals", "[nul", + "{\"a\":t", "{\"a\":tru", "{\"a\":nu", + "[true", "[false", "[null", "[1", "{\"a\":true", "{\"a\":1", "{\"a\":[null" + }; + static const char * const complete[] = { + "true", "false", "null", "[true]", "[null, false]", "{\"a\":true}" + }; + struct wolfsentry_context *wolfsentry; + size_t i; + JSON_INPUT_POS pos; + int ret; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (i = 0; i < sizeof truncated / sizeof truncated[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)truncated[i], strlen(truncated[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret >= 0) { + (void)fprintf(stderr, "truncated JSON accepted: %s\n", truncated[i]); + return 1; + } + } + + for (i = 0; i < sizeof complete / sizeof complete[0]; ++i) { + ret = json_parse(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(wolfsentry_get_allocator(wolfsentry)), + (const unsigned char *)complete[i], strlen(complete[i]), + &callbacks, &sax_config, NULL, &pos); + if (ret < 0) { + (void)fprintf(stderr, "complete JSON rejected: %s\n", complete[i]); + return 1; + } + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_JSON */ #ifdef TEST_JSON_CORPUS @@ -12941,6 +13013,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_json_sax_nonfinite_dump failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_sax_truncated_input(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_truncated_input failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_JSON_CORPUS From 6f5f7b1f0ee16df5ff0689b06cc0b55a0a54ad01 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:39 -0700 Subject: [PATCH 207/255] F-8772 - https://fenrir.wolfssl.com/finding/8772 - Accept double text that exactly fills the local conversion buffer --- src/json/centijson_sax.c | 2 +- tests/unittests.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/json/centijson_sax.c b/src/json/centijson_sax.c index 6e681879..0bb42ba6 100644 --- a/src/json/centijson_sax.c +++ b/src/json/centijson_sax.c @@ -1229,7 +1229,7 @@ json_number_to_double(const unsigned char* num, size_t num_size, double* p_resul * requirements. */ - if(num_size + 1 < sizeof(local_buffer)) { + if(num_size < sizeof(local_buffer)) { /* The number is short enough so we can avoid heap allocation. */ buffer = local_buffer; } else { diff --git a/tests/unittests.c b/tests/unittests.c index 260a9f4a..87814ac8 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -12270,6 +12270,25 @@ static wolfsentry_errcode_t test_json_sax_nonfinite_dump(void) { WOLFSENTRY_RETURN_OK; } +static wolfsentry_errcode_t test_json_sax_number_text_boundary(void) { + unsigned char num[65]; + double result; + size_t len; + + memset(num, '0', sizeof num); + num[0] = '1'; + + for (len = 62; len <= 63; ++len) { + result = 0.0; + WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double(num, len, &result)); + WOLFSENTRY_EXIT_ON_FALSE(result == ((len == 62) ? 1e61 : 1e62)); + } + + WOLFSENTRY_EXIT_ON_TRUE(json_number_to_double(num, 64, &result) >= 0); + + WOLFSENTRY_RETURN_OK; +} + static wolfsentry_errcode_t test_json_sax_truncated_input(void) { static const JSON_CALLBACKS callbacks = { sax_noop_process_cb }; static const JSON_CONFIG sax_config = { @@ -13014,6 +13033,12 @@ int main(int argc, char* argv[]) { err = 1; } + ret = test_json_sax_number_text_boundary(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_sax_number_text_boundary failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_json_sax_truncated_input(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_json_sax_truncated_input failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From e0fe2fc8e8c0684297f9c3e2895cfc235ef3ee74 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:51:22 -0700 Subject: [PATCH 208/255] F-2438 - https://fenrir.wolfssl.com/finding/2438 - Deny lwIP packets when the wolfSentry dispatch fails --- src/lwip/packet_filter_glue.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index f8d3b603..f96b8c7e 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -186,7 +186,10 @@ static err_t ethernet_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -326,7 +329,10 @@ static err_t ip4_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -461,7 +467,10 @@ static err_t ip6_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -662,7 +671,10 @@ static err_t tcp_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) ret = ERR_RST; else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; @@ -851,7 +863,10 @@ static err_t udp_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_PORT_RESET)) ret = ERR_RST; else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; @@ -999,7 +1014,10 @@ static err_t icmp4_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; @@ -1135,7 +1153,10 @@ static err_t icmp6_filter_with_wolfsentry( WOLFSENTRY_WARN_ON_FAILURE(ws_ret); - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) + /* fail closed: a failed dispatch leaves no trustworthy verdict. */ + if (WOLFSENTRY_IS_FAILURE(ws_ret)) + ret = ERR_ABRT; + else if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) ret = ERR_ABRT; else ret = ERR_OK; From b024dee101f7a1d2d6a326098c0a5043efc5a556 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:51:38 -0700 Subject: [PATCH 209/255] F-3866 - https://fenrir.wolfssl.com/finding/3866 - Deny lwIP packets when the filter callback has no wolfSentry context --- src/lwip/packet_filter_glue.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index f96b8c7e..710fcc41 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -105,7 +105,7 @@ static err_t ethernet_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -245,7 +245,7 @@ static err_t ip4_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -383,7 +383,7 @@ static err_t ip6_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -524,7 +524,7 @@ static err_t tcp_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_ACCEPTING: @@ -739,7 +739,7 @@ static err_t udp_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_BINDING: @@ -929,7 +929,7 @@ static err_t icmp4_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: @@ -1068,7 +1068,7 @@ static err_t icmp6_filter_with_wolfsentry( #endif if (wolfsentry == NULL) - WOLFSENTRY_RETURN_VALUE(ERR_OK); + WOLFSENTRY_RETURN_VALUE(ERR_ABRT); switch(event->reason) { case FILT_RECEIVING: From 5a23110cb0ad323211d46ce5b71471bdd73960b3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:51:55 -0700 Subject: [PATCH 210/255] F-10500 - https://fenrir.wolfssl.com/finding/10500 - Set the close-wait action result for lwIP TCP close-wait events --- src/lwip/packet_filter_glue.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index 710fcc41..c1a00853 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -549,6 +549,7 @@ static err_t tcp_filter_with_wolfsentry( route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; else route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT; + action_results = WOLFSENTRY_ACTION_RES_CLOSE_WAIT; break; case FILT_PORT_UNREACHABLE: route_flags |= WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; From a8e1a1d81a315a498d5c9ed231983d023a187c34 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:52:08 -0700 Subject: [PATCH 211/255] F-3864 - https://fenrir.wolfssl.com/finding/3864 - Clear the lwIP ICMPv4 filter mask and context when disabling the filter --- src/lwip/packet_filter_glue.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index c1a00853..602a841d 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -1262,8 +1262,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback icmp_filter(icmp4_filter_with_wolfsentry); icmp_filter_mask(icmp_mask); icmp_filter_arg((void *)wolfsentry); - } else + } else { icmp_filter(NULL); + icmp_filter_mask(0); + icmp_filter_arg(NULL); + } #endif /* LWIP_ICMP */ #if LWIP_ICMP6 From e37b446406bc7795626fda5e2c60c076e7f7ed1f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:52:42 -0700 Subject: [PATCH 212/255] F-3868 - https://fenrir.wolfssl.com/finding/3868 - Install the lwIP filter context and mask before the callback pointer --- src/lwip/packet_filter_glue.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index 602a841d..3b98bda9 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -1184,9 +1184,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ethernet_call if (ethernet_mask) { wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - ethernet_filter(ethernet_filter_with_wolfsentry); - ethernet_filter_mask(ethernet_mask); ethernet_filter_arg((void *)wolfsentry); + ethernet_filter_mask(ethernet_mask); + ethernet_filter(ethernet_filter_with_wolfsentry); } else { ethernet_filter(NULL); ethernet_filter_mask(0); @@ -1214,9 +1214,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ip_callbacks( #endif #if LWIP_IPV4 if (ip_mask) { - ip4_filter(ip4_filter_with_wolfsentry); - ip4_filter_mask(ip_mask); ip4_filter_arg((void *)wolfsentry); + ip4_filter_mask(ip_mask); + ip4_filter(ip4_filter_with_wolfsentry); } else { ip4_filter(NULL); ip4_filter_mask(0); @@ -1226,9 +1226,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ip_callbacks( #if LWIP_IPV6 if (ip_mask) { - ip6_filter(ip6_filter_with_wolfsentry); - ip6_filter_mask(ip_mask); ip6_filter_arg((void *)wolfsentry); + ip6_filter_mask(ip_mask); + ip6_filter(ip6_filter_with_wolfsentry); } else { ip6_filter(NULL); ip6_filter_mask(0); @@ -1259,9 +1259,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback #endif #if LWIP_ICMP if (icmp_mask) { - icmp_filter(icmp4_filter_with_wolfsentry); - icmp_filter_mask(icmp_mask); icmp_filter_arg((void *)wolfsentry); + icmp_filter_mask(icmp_mask); + icmp_filter(icmp4_filter_with_wolfsentry); } else { icmp_filter(NULL); icmp_filter_mask(0); @@ -1271,9 +1271,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback #if LWIP_ICMP6 if (icmp_mask) { - icmp6_filter(icmp6_filter_with_wolfsentry); - icmp6_filter_mask(icmp_mask); icmp6_filter_arg((void *)wolfsentry); + icmp6_filter_mask(icmp_mask); + icmp6_filter(icmp6_filter_with_wolfsentry); } else { icmp6_filter(NULL); icmp6_filter_mask(0); @@ -1300,14 +1300,14 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_tcp_callback( if (tcp_mask) { wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - tcp_filter(tcp_filter_with_wolfsentry); /* make sure wolfSentry sees the close/reset events that balance earlier * accepts, for concurrent-connection accounting purposes. */ if (tcp_mask & (FILT_MASK(ACCEPTING) | FILT_MASK(CLOSED) | FILT_MASK(REMOTE_RESET))) tcp_mask |= FILT_MASK(ACCEPTING) | FILT_MASK(CLOSED) | FILT_MASK(REMOTE_RESET); - tcp_filter_mask(tcp_mask); tcp_filter_arg((void *)wolfsentry); + tcp_filter_mask(tcp_mask); + tcp_filter(tcp_filter_with_wolfsentry); } else { tcp_filter(NULL); tcp_filter_mask(0); @@ -1331,9 +1331,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_udp_callback( if (udp_mask) { wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - udp_filter(udp_filter_with_wolfsentry); - udp_filter_mask(udp_mask); udp_filter_arg((void *)wolfsentry); + udp_filter_mask(udp_mask); + udp_filter(udp_filter_with_wolfsentry); } else { udp_filter(NULL); udp_filter_mask(0); From 8a82314db47a7a262993c9c6bfb3db6d2cb5c0ab Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:54:25 -0700 Subject: [PATCH 213/255] F-4762 - https://fenrir.wolfssl.com/finding/4762 - Only clear lwIP filter slots when the owning context releases them --- src/lwip/packet_filter_glue.c | 175 +++++++++++++++++++++++++++------- wolfsentry/wolfsentry_lwip.h | 2 + 2 files changed, 141 insertions(+), 36 deletions(-) diff --git a/src/lwip/packet_filter_glue.c b/src/lwip/packet_filter_glue.c index 3b98bda9..a7cca6d1 100644 --- a/src/lwip/packet_filter_glue.c +++ b/src/lwip/packet_filter_glue.c @@ -1170,8 +1170,118 @@ static err_t icmp6_filter_with_wolfsentry( #endif /* LWIP_ICMP6 */ +#define LWIP_FILTER_SLOT_ETHERNET (1U << 0) +#define LWIP_FILTER_SLOT_IP (1U << 1) +#define LWIP_FILTER_SLOT_ICMP (1U << 2) +#define LWIP_FILTER_SLOT_TCP (1U << 3) +#define LWIP_FILTER_SLOT_UDP (1U << 4) + +/* lwIP has no getters for its filter slots, so the owning context is tracked + * here. Contexts hold only their own mutex, so ownership changes atomically + * and only the owner touches the slots and the lwIP setters. */ +static struct wolfsentry_context *lwip_filter_owner = NULL; +static unsigned int lwip_filter_slots = 0; + +static wolfsentry_errcode_t lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_IN, unsigned int slot) +{ + wolfsentry_errcode_t ret; +#ifdef WOLFSENTRY_THREADSAFE + struct wolfsentry_context *expected = NULL; + + if ((! WOLFSENTRY_ATOMIC_CMPXCHG(&lwip_filter_owner, &expected, wolfsentry, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) && + (expected != wolfsentry)) + { + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + } +#else + if ((lwip_filter_owner != NULL) && (lwip_filter_owner != wolfsentry)) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + lwip_filter_owner = wolfsentry; +#endif + ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + if (ret < 0) { + if (lwip_filter_slots == 0) + WOLFSENTRY_ATOMIC_STORE(lwip_filter_owner, NULL); + WOLFSENTRY_ERROR_RERETURN(ret); + } + lwip_filter_slots |= slot; + WOLFSENTRY_RETURN_OK; +} + +static void lwip_filter_clear_slot(unsigned int slot) +{ + switch (slot) { + case LWIP_FILTER_SLOT_ETHERNET: +#if LWIP_ARP || LWIP_ETHERNET + ethernet_filter(NULL); + ethernet_filter_mask(0); + ethernet_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_IP: +#if LWIP_IPV4 + ip4_filter(NULL); + ip4_filter_mask(0); + ip4_filter_arg(NULL); +#endif +#if LWIP_IPV6 + ip6_filter(NULL); + ip6_filter_mask(0); + ip6_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_ICMP: +#if LWIP_ICMP + icmp_filter(NULL); + icmp_filter_mask(0); + icmp_filter_arg(NULL); +#endif +#if LWIP_ICMP6 + icmp6_filter(NULL); + icmp6_filter_mask(0); + icmp6_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_TCP: +#if LWIP_TCP + tcp_filter(NULL); + tcp_filter_mask(0); + tcp_filter_arg(NULL); +#endif + break; + case LWIP_FILTER_SLOT_UDP: +#if LWIP_UDP + udp_filter(NULL); + udp_filter_mask(0); + udp_filter_arg(NULL); +#endif + break; + } +} + +static wolfsentry_errcode_t lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_IN, unsigned int slot) +{ + struct wolfsentry_context *owner = WOLFSENTRY_ATOMIC_LOAD(lwip_filter_owner); + + WOLFSENTRY_CONTEXT_ARGS_THREAD_NOT_USED; + /* with no owner, no wolfSentry callbacks are installed, so there is nothing to clear. */ + if (owner == NULL) + WOLFSENTRY_RETURN_OK; + if (owner != wolfsentry) + WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); + lwip_filter_clear_slot(slot); + lwip_filter_slots &= ~slot; + /* ownership is released only after the last slot is fully cleared. */ + if (lwip_filter_slots == 0) + WOLFSENTRY_ATOMIC_STORE(lwip_filter_owner, NULL); + WOLFSENTRY_RETURN_OK; +} + WOLFSENTRY_API_VOID wolfsentry_cleanup_lwip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_IN, void *cleanup_arg) { (void)cleanup_arg; + /* a destroyed non-owner must not disable the surviving owner's filters. */ + if (WOLFSENTRY_ATOMIC_LOAD(lwip_filter_owner) != wolfsentry) + return; WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_install_lwip_filter_callbacks(WOLFSENTRY_CONTEXT_ARGS_OUT, 0, 0, 0, 0, 0)); } @@ -1180,17 +1290,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ethernet_call packet_filter_event_mask_t ethernet_mask) { #if LWIP_ARP || LWIP_ETHERNET + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (ethernet_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ETHERNET); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); ethernet_filter_arg((void *)wolfsentry); ethernet_filter_mask(ethernet_mask); ethernet_filter(ethernet_filter_with_wolfsentry); } else { - ethernet_filter(NULL); - ethernet_filter_mask(0); - ethernet_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ETHERNET); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else @@ -1206,21 +1317,20 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ip_callbacks( packet_filter_event_mask_t ip_mask) { #if LWIP_IPV4 || LWIP_IPV6 + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); - if (ip_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); - WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - } + if (ip_mask) + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_IP); + else + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_IP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); #endif #if LWIP_IPV4 if (ip_mask) { ip4_filter_arg((void *)wolfsentry); ip4_filter_mask(ip_mask); ip4_filter(ip4_filter_with_wolfsentry); - } else { - ip4_filter(NULL); - ip4_filter_mask(0); - ip4_filter_arg(NULL); } #endif /* LWIP_IPV4 */ @@ -1229,10 +1339,6 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_ip_callbacks( ip6_filter_arg((void *)wolfsentry); ip6_filter_mask(ip_mask); ip6_filter(ip6_filter_with_wolfsentry); - } else { - ip6_filter(NULL); - ip6_filter_mask(0); - ip6_filter_arg(NULL); } #endif /* LWIP_IPV6 */ @@ -1251,21 +1357,20 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback packet_filter_event_mask_t icmp_mask) { #if LWIP_ICMP || LWIP_ICMP6 + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); - if (icmp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); - WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - } + if (icmp_mask) + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ICMP); + else + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_ICMP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); #endif #if LWIP_ICMP if (icmp_mask) { icmp_filter_arg((void *)wolfsentry); icmp_filter_mask(icmp_mask); icmp_filter(icmp4_filter_with_wolfsentry); - } else { - icmp_filter(NULL); - icmp_filter_mask(0); - icmp_filter_arg(NULL); } #endif /* LWIP_ICMP */ @@ -1274,10 +1379,6 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_icmp_callback icmp6_filter_arg((void *)wolfsentry); icmp6_filter_mask(icmp_mask); icmp6_filter(icmp6_filter_with_wolfsentry); - } else { - icmp6_filter(NULL); - icmp6_filter_mask(0); - icmp6_filter_arg(NULL); } #endif /* LWIP_ICMP6 */ @@ -1296,9 +1397,11 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_tcp_callback( packet_filter_event_mask_t tcp_mask) { #if LWIP_TCP + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (tcp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_TCP); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); /* make sure wolfSentry sees the close/reset events that balance earlier * accepts, for concurrent-connection accounting purposes. @@ -1309,9 +1412,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_tcp_callback( tcp_filter_mask(tcp_mask); tcp_filter(tcp_filter_with_wolfsentry); } else { - tcp_filter(NULL); - tcp_filter_mask(0); - tcp_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_TCP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else @@ -1327,17 +1429,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_install_lwip_filter_udp_callback( packet_filter_event_mask_t udp_mask) { #if LWIP_UDP + wolfsentry_errcode_t ret; + WOLFSENTRY_MUTEX_OR_RETURN(); if (udp_mask) { - wolfsentry_errcode_t ret = wolfsentry_cleanup_push(WOLFSENTRY_CONTEXT_ARGS_OUT, wolfsentry_cleanup_lwip_filter_callbacks, NULL); + ret = lwip_filter_claim_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_UDP); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); udp_filter_arg((void *)wolfsentry); udp_filter_mask(udp_mask); udp_filter(udp_filter_with_wolfsentry); } else { - udp_filter(NULL); - udp_filter_mask(0); - udp_filter_arg(NULL); + ret = lwip_filter_release_slot(WOLFSENTRY_CONTEXT_ARGS_OUT, LWIP_FILTER_SLOT_UDP); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); } WOLFSENTRY_UNLOCK_AND_RETURN_OK; #else diff --git a/wolfsentry/wolfsentry_lwip.h b/wolfsentry/wolfsentry_lwip.h index 2a459f94..202d7c4f 100644 --- a/wolfsentry/wolfsentry_lwip.h +++ b/wolfsentry/wolfsentry_lwip.h @@ -39,6 +39,8 @@ * `FILT_PORT_UNREACHABLE` -- Call into wolfSentry when inbound traffic attempts to reach an unlistened/unbound port
* `FILT_INBOUND_ERR` -- Call into wolfSentry when inbound traffic results in detection of an error by lwIP
* `FILT_OUTBOUND_ERR` -- Call into wolfSentry when outbound traffic results in detection of an error by lwIP
+ * + * lwIP has a single process-wide set of filter slots, so at most one wolfSentry context can own them at a time. The first context to install a callback with a nonzero mask becomes the owner of all slots. While it owns them, any installation or disabling call from a different context fails with `WOLFSENTRY_ERROR_ID_INCOMPATIBLE_STATE` and leaves the owner's callbacks in place. Ownership is released when the owner disables every slot it installed (mask zero), or when the owner is shut down, which runs `wolfsentry_cleanup_lwip_filter_callbacks()`; a replacement context can install only after that. A filter callback denies the packet whenever the wolfSentry dispatch fails or no context is attached. */ #ifndef WOLFSENTRY_LWIP_H From 5459984ad1dc2fe95cafb42092de8dc5416f2e6f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:34:06 -0700 Subject: [PATCH 214/255] F-5004 - https://fenrir.wolfssl.com/finding/5004 - Publish the STM32 wolfSentry context only after its policy is loaded --- examples/STM32-LWIP/README.md | 5 +- examples/STM32-LWIP/Src/sentry.c | 231 +++++++++++++++++++------------ 2 files changed, 150 insertions(+), 86 deletions(-) diff --git a/examples/STM32-LWIP/README.md b/examples/STM32-LWIP/README.md index 430cc6e7..f2c24b61 100644 --- a/examples/STM32-LWIP/README.md +++ b/examples/STM32-LWIP/README.md @@ -68,7 +68,10 @@ In `StartDefaultTask()`: ```c /* USER CODE BEGIN 5 */ printf("Sentry init\n"); - sentry_init(); + if (sentry_init() != 0) { + printf("Sentry init failed, all traffic will be denied\n"); + Error_Handler(); + } printf("Echo init\n"); echo_init(); printf("Ping init\n"); diff --git a/examples/STM32-LWIP/Src/sentry.c b/examples/STM32-LWIP/Src/sentry.c index 69b7f6ac..035f60a0 100644 --- a/examples/STM32-LWIP/Src/sentry.c +++ b/examples/STM32-LWIP/Src/sentry.c @@ -135,25 +135,28 @@ static wolfsentry_errcode_t test_action( int sentry_init(void) { wolfsentry_errcode_t ret; + wolfsentry_errcode_t fini_ret; wolfsentry_ent_id_t id; + struct wolfsentry_context *ctx = NULL; struct wolfsentry_eventconfig config = { 0 }; char err_buf[512]; - struct wolfsentry_json_process_state *jps; + struct wolfsentry_json_process_state *jps = NULL; config.route_private_data_size = 32; config.route_private_data_alignment = 16; ret = wolfsentry_init(wolfsentry_build_settings, WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(NULL /* hpi */, NULL /* thread */), &config, - &wolfsentry); + &ctx); if (ret < 0) { printf("unable to initialize wolfSentry, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + return ret; } /* Insert the possible actions into wolfSentry */ - wolfsentry_action_insert( WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + ret = wolfsentry_action_insert(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), "handle-insert", WOLFSENTRY_LENGTH_NULL_TERMINATED, WOLFSENTRY_ACTION_FLAG_NONE, @@ -161,83 +164,115 @@ int sentry_init(void) NULL, &id); - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-delete", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "notify-on-match", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); - - wolfsentry_action_insert( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - "handle-connect2", - WOLFSENTRY_LENGTH_NULL_TERMINATED, - WOLFSENTRY_ACTION_FLAG_NONE, - test_action, - NULL, - &id); + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-delete", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "notify-on-match", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-connect", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + + if (ret >= 0) + ret = wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + "handle-connect2", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action, + NULL, + &id); + if (ret < 0) + { + printf("unable to insert wolfSentry actions, " WOLFSENTRY_ERROR_FMT "\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret)); + } /* Initalize the wolfSentry JSON parser */ - if ((ret = wolfsentry_config_json_init( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), - WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, - &jps)) < 0) + if (ret >= 0) { - printf("error while initializing wolfSentry config parser, " - WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + ret = wolfsentry_config_json_init( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + &jps); + if (ret < 0) + { + printf("error while initializing wolfSentry config parser, " + WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + } } /* wolfSentry uses a streaming reader/parser for the config file */ /* Send the read data into the JSON parser */ - ret = wolfsentry_config_json_feed(jps, - (const unsigned char *)wolfsentry_config_data, - strlen(wolfsentry_config_data), err_buf, sizeof err_buf); - - if (ret < 0) - { - printf("error while loading wolfSentry config data, " - WOLFSENTRY_ERROR_FMT ", msg: %s\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); - } - - /* Clean up the JSON parser */ - if ((ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf)) < 0) + if (ret >= 0) { - printf("error while loading wolfSentry config data, " - WOLFSENTRY_ERROR_FMT ", msg: %s\n", - WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); + ret = wolfsentry_config_json_feed(jps, + (const unsigned char *)wolfsentry_config_data, + strlen(wolfsentry_config_data), err_buf, sizeof err_buf); + if (ret < 0) + { + printf("error while loading wolfSentry config data, " + WOLFSENTRY_ERROR_FMT ", msg: %s\n", + WOLFSENTRY_ERROR_FMT_ARGS(ret), err_buf); + } } + /* Clean up the JSON parser. This commits the parsed config. */ + if (jps != NULL) + { + fini_ret = wolfsentry_config_json_fini(&jps, err_buf, sizeof err_buf); + if (fini_ret < 0) + { + printf("error while loading wolfSentry config data, " + WOLFSENTRY_ERROR_FMT ", msg: %s\n", + WOLFSENTRY_ERROR_FMT_ARGS(fini_ret), err_buf); + if (ret >= 0) + ret = fini_ret; + } + } + + if (ret < 0) + { + (void)wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(&ctx, NULL)); + return ret; + } + + /* Publish the context only once the policy is committed, so a concurrent + * packet filter never dispatches against an empty route table. */ + WOLFSENTRY_ATOMIC_STORE(wolfsentry, ctx); + return 0; } @@ -247,7 +282,7 @@ int sentry_init(void) int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port, uint16_t remote_port, sentry_action_type action) { wolfsentry_errcode_t ret; - wolfsentry_action_res_t action_results; + wolfsentry_action_res_t action_results = WOLFSENTRY_ACTION_RES_NONE; /* Note that sa.addr is 0 bytes, addr_buf essentially enlarges this to the correct size */ struct { struct wolfsentry_sockaddr sa; @@ -255,6 +290,11 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port } remote, local; u32_t remoteip = remote_ip->addr; u32_t localip = local_ip->addr; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* Connect will increment the connection count in wolfSentry, disconnect * will decrement it */ @@ -289,7 +329,7 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port /* Send the details of this to wolfSentry and get the result */ ret = wolfsentry_route_event_dispatch_with_inited_result( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &local.sa, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -304,11 +344,14 @@ int sentry_action(ip_addr_t *local_ip, ip_addr_t *remote_ip, uint16_t local_port WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); + /* Fail closed: a dispatch error denies the connection */ + if (ret < 0) { + return -1; + } + /* Check the result, if it contains "reject" then notify the caller */ - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; @@ -324,6 +367,11 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) struct wolfsentry_sockaddr sa; byte addr_buf[4]; } remote; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* ICMP protocol check. The ICMP packet type is the port number for this * check */ @@ -335,7 +383,7 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) memcpy(remote.sa.addr, &addr->addr, 4); ret = wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &remote.sa, /* Reuse for now */ WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -348,10 +396,14 @@ int sentry_action_ping(const ip_addr_t *addr, u8_t type) printf("PING Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + + /* Fail closed: a dispatch error denies the packet */ + if (ret < 0) { + return -1; + } + + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; @@ -367,6 +419,11 @@ int sentry_action_mac(struct eth_addr *addr) struct wolfsentry_sockaddr sa; byte addr_buf[6]; } remote; + struct wolfsentry_context *ctx = WOLFSENTRY_ATOMIC_LOAD(wolfsentry); + + /* Fail closed until initialization has published a configured context */ + if (ctx == NULL) + return -1; /* We only really care about the data and length, the family is AF_LINK */ remote.sa.sa_family = WOLFSENTRY_AF_LINK; @@ -376,7 +433,7 @@ int sentry_action_mac(struct eth_addr *addr) memcpy(remote.sa.addr, &addr->addr, 6); ret = wolfsentry_route_event_dispatch( - WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(wolfsentry, NULL), + WOLFSENTRY_CONTEXT_ARGS_OUT_EX4(ctx, NULL), &remote.sa, &remote.sa, /* Reuse for now */ WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, @@ -390,10 +447,14 @@ int sentry_action_mac(struct eth_addr *addr) printf("MAC Sentry action returned " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); fflush(stdout); - if (WOLFSENTRY_ERROR_DECODE_ERROR_CODE(ret) >= 0) { - if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { - return -1; - } + + /* Fail closed: a dispatch error denies the frame */ + if (ret < 0) { + return -1; + } + + if (WOLFSENTRY_MASKIN_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)) { + return -1; } return 0; From aa1fdbc15f2fd254ff1033c6a1b4018893814040 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:45:36 -0700 Subject: [PATCH 215/255] F-5558 - https://fenrir.wolfssl.com/finding/5558 - Size the event lookup buffer for a maximum-length label and its terminator --- src/events.c | 2 +- tests/unittests.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/events.c b/src/events.c index 6d92a88d..5234ed1e 100644 --- a/src/events.c +++ b/src/events.c @@ -319,7 +319,7 @@ static wolfsentry_errcode_t wolfsentry_event_get_1(WOLFSENTRY_CONTEXT_ARGS_IN, c WOLFSENTRY_STACKBUF( struct wolfsentry_event, label, - WOLFSENTRY_MAX_LABEL_BYTES, + WOLFSENTRY_MAX_LABEL_BYTES + 1, target); struct wolfsentry_event *event_1 = &target.target; diff --git a/tests/unittests.c b/tests/unittests.c index 87814ac8..2aad8f31 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8496,6 +8496,80 @@ static int test_track_peer_connect_count(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_max_length_label(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_event *event; + struct wolfsentry_eventconfig eventconfig; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'm', WOLFSENTRY_MAX_LABEL_BYTES); + label[WOLFSENTRY_MAX_LABEL_BYTES] = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_FALSE(id == wolfsentry_get_object_id(event)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_MAX_LABEL_BYTES, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_NOT_FOUND, + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -12850,6 +12924,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_track_peer_connect_count failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_max_length_label(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_max_length_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 88ca8cc6e9017ae6c8248fa8cd9766aee4774dff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:46:09 -0700 Subject: [PATCH 216/255] F-13883 - https://fenrir.wolfssl.com/finding/13883 - Cover maximum-length event labels across the event lookup APIs --- tests/unittests.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 2aad8f31..2576d81c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8570,6 +8570,123 @@ static int test_event_max_length_label(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_max_length_label_lookups(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_action_list_ent *cursor; + const char *action_label; + int action_label_len; + struct wolfsentry_eventconfig eventconfig; + int n_actions = 0; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'n', WOLFSENTRY_MAX_LABEL_BYTES); + label[WOLFSENTRY_MAX_LABEL_BYTES] = 0; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.route_private_data_size == config.route_private_data_size); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_start( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + &cursor)); + + while (wolfsentry_event_action_list_next( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor, + &action_label, + &action_label_len) >= 0) { + WOLFSENTRY_EXIT_ON_FALSE(strcmp(action_label, "max-label-action") == 0); + ++n_actions; + } + + WOLFSENTRY_EXIT_ON_FALSE(n_actions == 1); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_list_done( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &cursor)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_MATCH, + "max-label-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -12930,6 +13047,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_max_length_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_max_length_label_lookups(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_max_length_label_lookups failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 1c8daa69d6929cdfbb68b57587eb0604ccacc433 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:46:33 -0700 Subject: [PATCH 217/255] F-8758 - https://fenrir.wolfssl.com/finding/8758 - Set the event id output only after the event is inserted --- src/events.c | 4 ++-- tests/unittests.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/events.c b/src/events.c index 5234ed1e..bcd5d300 100644 --- a/src/events.c +++ b/src/events.c @@ -289,12 +289,12 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_insert( wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_OUT, new); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } - if (id) - *id = new->header.id; if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header, &wolfsentry->events->header, 1 /* unique_p */)) < 0) { wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_OUT, new); WOLFSENTRY_ERROR_UNLOCK_AND_RETURN_RECODED(ret); } + if (id) + *id = new->header.id; WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } diff --git a/tests/unittests.c b/tests/unittests.c index 2576d81c..dbecc4cb 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8687,6 +8687,52 @@ static int test_event_max_length_label_lookups(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_insert_failure_leaves_id_untouched(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id, id_before; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + id_before = id; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_ALREADY_PRESENT, + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FALSE(id == id_before); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13053,6 +13099,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_max_length_label_lookups failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_insert_failure_leaves_id_untouched(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 00ff7b213f17802e32a5b8d9d3515e21a6cc88ba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:47:00 -0700 Subject: [PATCH 218/255] F-11606 - https://fenrir.wolfssl.com/finding/11606 - Reject a NULL configuration in event config updates --- src/events.c | 3 ++ tests/unittests.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/events.c b/src/events.c index bcd5d300..0207ef3b 100644 --- a/src/events.c +++ b/src/events.c @@ -360,6 +360,9 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_update_config(WOLFSENTRY_CO struct wolfsentry_event *event; wolfsentry_errcode_t ret; + if (config == NULL) + WOLFSENTRY_ERROR_RETURN(INVALID_ARG); + WOLFSENTRY_MUTEX_OR_RETURN(); ret = wolfsentry_event_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, label, label_len, &event); diff --git a/tests/unittests.c b/tests/unittests.c index dbecc4cb..eb865584 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8733,6 +8733,70 @@ static int test_event_insert_failure_leaves_id_untouched(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_update_config_null(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_eventconfig global_eventconfig, eventconfig; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &global_eventconfig)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* config */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "null_config_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == global_eventconfig.route_private_data_size) && + (eventconfig.route_private_data_alignment == global_eventconfig.route_private_data_alignment) && + (eventconfig.max_connection_count == global_eventconfig.max_connection_count)); + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.max_connection_count == config.max_connection_count); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13105,6 +13169,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_update_config_null(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_update_config_null failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 890d40f063aef704b366a73d07dd992c5c74c13c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:48:04 -0700 Subject: [PATCH 219/255] F-11619 - https://fenrir.wolfssl.com/finding/11619 - Reject route layout changes in event config updates once routes use the event --- src/events.c | 40 +++++-- tests/unittests.c | 250 ++++++++++++++++++++++++++++++++++++++++ wolfsentry/wolfsentry.h | 2 +- 3 files changed, 283 insertions(+), 9 deletions(-) diff --git a/src/events.c b/src/events.c index 0207ef3b..55bfb6c6 100644 --- a/src/events.c +++ b/src/events.c @@ -149,6 +149,10 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_bare( memcpy(*new_event, src_event, new_size); WOLFSENTRY_TABLE_ENT_HEADER_RESET(**new_ent); + /* no route in a route-free clone can depend on the copied layout */ + if (WOLFSENTRY_CHECK_BITS(flags, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)) + WOLFSENTRY_CLEAR_BITS((*new_event)->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); + WOLFSENTRY_LIST_HEADER_RESET((*new_event)->post_action_list.header); WOLFSENTRY_LIST_HEADER_RESET((*new_event)->insert_action_list.header); WOLFSENTRY_LIST_HEADER_RESET((*new_event)->match_action_list.header); @@ -356,8 +360,22 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_config(WOLFSENTRY_CONTE WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } +static int wolfsentry_event_has_routes(const struct wolfsentry_context *wolfsentry, const struct wolfsentry_event *event) { + const struct wolfsentry_table_ent_header *ent; + + if (WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) + return 1; + for (ent = wolfsentry_table_first(&wolfsentry->routes->header); ent != NULL; ent = ent->next) { + if (((const struct wolfsentry_route *)ent)->parent_event == event) + return 1; + } + return 0; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_update_config(WOLFSENTRY_CONTEXT_ARGS_IN, const char *label, int label_len, const struct wolfsentry_eventconfig *config) { struct wolfsentry_event *event; + const struct wolfsentry_eventconfig_internal *current_config; + struct wolfsentry_eventconfig_internal new_config; wolfsentry_errcode_t ret; if (config == NULL) @@ -368,18 +386,24 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_update_config(WOLFSENTRY_CO ret = wolfsentry_event_get_1(WOLFSENTRY_CONTEXT_ARGS_OUT, label, label_len, &event); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + ret = wolfsentry_eventconfig_load(config, &new_config); + WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); + + current_config = (event->config != NULL) ? event->config : &wolfsentry->config; + + /* existing routes were allocated, and are accessed and freed, using the event's current layout */ + if (wolfsentry_event_has_routes(wolfsentry, event) && + ((new_config.config.route_private_data_size != current_config->config.route_private_data_size) || + (new_config.config.route_private_data_alignment != current_config->config.route_private_data_alignment))) + WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(INCOMPATIBLE_STATE); + if (event->config == NULL) { if ((event->config = (struct wolfsentry_eventconfig_internal *)WOLFSENTRY_MALLOC(sizeof *event->config)) == NULL) WOLFSENTRY_ERROR_UNLOCK_AND_RETURN(SYS_RESOURCE_FAILED); - if ((ret = wolfsentry_eventconfig_load(config, event->config)) < 0) { - WOLFSENTRY_FREE(event->config); - event->config = NULL; - } - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - } else { - ret = wolfsentry_eventconfig_load(config, event->config); - WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } + memcpy(event->config, &new_config, sizeof *event->config); + + WOLFSENTRY_UNLOCK_AND_RETURN_OK; } WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_IN, const char *label, int label_len, struct wolfsentry_event **event) { diff --git a/tests/unittests.c b/tests/unittests.c index eb865584..292e0b49 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8797,6 +8797,250 @@ static int test_event_update_config_null(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_update_config_layout_with_routes(void) { + struct wolfsentry_context *wolfsentry, *ctx_clone; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_eventconfig eventconfig; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; + static const struct wolfsentry_eventconfig config2_more_connections = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 20 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2_more_connections = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + memcpy(remote.sa.addr, "\4\5\6\7", sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config2.route_private_data_size) && + (eventconfig.route_private_data_alignment == config2.route_private_data_alignment) && + (eventconfig.max_connection_count == config2.max_connection_count)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2_more_connections)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE(eventconfig.max_connection_count == config2_more_connections.max_connection_count); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_inherited", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + wolfsentry_action_dummy_callback, + NULL /* handler_context */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config2, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "layout-insert-action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + memcpy(remote.sa.addr, "\10\11\12\13", sizeof remote.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_routes_with_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owns_no_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &eventconfig)); + + WOLFSENTRY_EXIT_ON_FALSE((eventconfig.route_private_data_size == config.route_private_data_size) && + (eventconfig.route_private_data_alignment == config.route_private_data_alignment)); + + /* a clone without routes has no route using the old layout */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NO_ROUTES)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "owns_routes", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13175,6 +13419,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_update_config_null failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_update_config_layout_with_routes(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_update_config_layout_with_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index d8a21d40..ec749036 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2801,7 +2801,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_config( \param label_len the length of the label \param config the updated configuration - \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. + \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. Once any route has used the event as its parent, changes to `route_private_data_size` or `route_private_data_alignment` are rejected with `INCOMPATIBLE_STATE`, even after that route is deleted, because a deleted route can still be held by a checked-out reference. \sa WOLFSENTRY_CONTEXT_ARGS_IN */ From a7f0a5877ccee040dabb43f2c3416cde9fad065a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:48:42 -0700 Subject: [PATCH 220/255] F-11602 - https://fenrir.wolfssl.com/finding/11602 - Reject an event as its own auxiliary event --- src/events.c | 3 +- tests/unittests.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/events.c b/src/events.c index 55bfb6c6..b035ad0f 100644 --- a/src/events.c +++ b/src/events.c @@ -656,7 +656,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_set_aux_event( aux_event = NULL; goto out; } - if (WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) { + if ((aux_event == event) || + WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) { ret = WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); goto out; } diff --git a/tests/unittests.c b/tests/unittests.c index 292e0b49..b1178d74 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9041,6 +9041,70 @@ static int test_event_update_config_layout_with_routes(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_event_self_reference(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + struct wolfsentry_event *event; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &event)); + + WOLFSENTRY_EXIT_ON_TRUE(wolfsentry_event_get_aux_event(event) != NULL); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(event), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + event, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "self_aux_event", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13425,6 +13489,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_update_config_layout_with_routes failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_aux_event_self_reference(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_self_reference failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 83936b7b3ed93bbae73bedcb5668c26127531b4e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:49:54 -0700 Subject: [PATCH 221/255] F-5566 - https://fenrir.wolfssl.com/finding/5566 - Clear the subevent mark when an event stops being any event's auxiliary event --- src/events.c | 67 +++++-- src/wolfsentry_internal.h | 1 + tests/unittests.c | 364 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 420 insertions(+), 12 deletions(-) diff --git a/src/events.c b/src/events.c index b035ad0f..6e602e2e 100644 --- a/src/events.c +++ b/src/events.c @@ -77,6 +77,37 @@ static wolfsentry_errcode_t wolfsentry_event_init_1(const char *label, int label WOLFSENTRY_RETURN_OK; } +/* callers hold the context lock, which is what the plain flag write relies on */ +static wolfsentry_errcode_t wolfsentry_event_aux_owner_add(struct wolfsentry_event *aux_event) { + wolfsentry_refcount_t owners; + + WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(aux_event->aux_owner_count, owners); + if (owners == 0) + WOLFSENTRY_ERROR_RETURN(OVERFLOW_AVERTED); + if (! WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) + WOLFSENTRY_SET_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + WOLFSENTRY_RETURN_OK; +} + +/* may run without the context lock when a deleted owner is finally freed, so only the atomic count changes here; the mark is resynced under the lock */ +static wolfsentry_errcode_t wolfsentry_event_aux_owner_remove(struct wolfsentry_event *aux_event) { + wolfsentry_refcount_t owners; + + WOLFSENTRY_ATOMIC_DECREMENT_UNSIGNED_SAFELY_BY_ONE(aux_event->aux_owner_count, owners); + if (owners == MAX_UINT_OF(aux_event->aux_owner_count)) + WOLFSENTRY_ERROR_RETURN(INTERNAL_CHECK_FATAL); + WOLFSENTRY_RETURN_OK; +} + +static void wolfsentry_event_aux_owner_resync_mark(struct wolfsentry_event *event) { + if ((WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) == 0) && + WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) + { + WOLFSENTRY_CLEAR_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + } + WOLFSENTRY_RETURN_VOID; +} + static void wolfsentry_event_free(WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_event *event) { WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_delete_all(WOLFSENTRY_CONTEXT_ARGS_OUT, &event->post_action_list)); WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_action_list_delete_all(WOLFSENTRY_CONTEXT_ARGS_OUT, &event->insert_action_list)); @@ -161,6 +192,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_bare( WOLFSENTRY_LIST_HEADER_RESET((*new_event)->decision_action_list.header); (*new_event)->aux_event = NULL; + (*new_event)->aux_owner_count = 0; + WOLFSENTRY_CLEAR_BITS((*new_event)->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); if (src_event->config) { if (((*new_event)->config = WOLFSENTRY_MALLOC_1(dest_context->hpi.allocator, sizeof *(*new_event)->config)) == NULL) { @@ -261,6 +294,8 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_event_clone_resolve( } WOLFSENTRY_REFCOUNT_INCREMENT(new_event->aux_event->header.refcount, ret); WOLFSENTRY_RERETURN_IF_ERROR(ret); + ret = wolfsentry_event_aux_owner_add(new_event->aux_event); + WOLFSENTRY_RERETURN_IF_ERROR(ret); } WOLFSENTRY_RETURN_OK; @@ -445,6 +480,8 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_drop_reference(WOLFSENTRY_C if (refs_left > 0) WOLFSENTRY_RETURN_OK; if (event->aux_event) { + ret = wolfsentry_event_aux_owner_remove(event->aux_event); + WOLFSENTRY_RERETURN_IF_ERROR(ret); ret = wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event->aux_event, NULL); WOLFSENTRY_RERETURN_IF_ERROR(ret); event->aux_event = NULL; @@ -640,16 +677,18 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_set_aux_event( int aux_event_label_len) { wolfsentry_errcode_t ret; - struct wolfsentry_event *event, *aux_event = NULL; + struct wolfsentry_event *event, *aux_event = NULL, *old_aux_event; WOLFSENTRY_MUTEX_OR_RETURN(); ret = wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event_label, event_label_len, &event); WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); - if (WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) { + /* the owner count is authoritative; a mark left by an owner freed without the lock is cleared here */ + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) != 0) { ret = WOLFSENTRY_ERROR_ENCODE(INCOMPATIBLE_STATE); goto out; } + wolfsentry_event_aux_owner_resync_mark(event); if (aux_event_label) { ret = wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event_label, aux_event_label_len, &aux_event); if (ret < 0) { @@ -664,22 +703,26 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_set_aux_event( } else aux_event = NULL; - if (event->aux_event) - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event->aux_event, NULL /* action_results */)); + if (aux_event != NULL) { + ret = wolfsentry_event_aux_owner_add(aux_event); + if (ret < 0) + goto out; + } + + old_aux_event = event->aux_event; event->aux_event = aux_event; + if (old_aux_event != NULL) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_aux_owner_remove(old_aux_event)); + wolfsentry_event_aux_owner_resync_mark(old_aux_event); + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old_aux_event, NULL /* action_results */)); + } ret = WOLFSENTRY_ERROR_ENCODE(OK); out: WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, event, NULL /* action_results */)); - if (aux_event) { - if (ret < 0) - WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event, NULL /* action_results */)); - else { - if (! WOLFSENTRY_CHECK_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) - WOLFSENTRY_SET_BITS(aux_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); - } - } + if ((aux_event != NULL) && (ret < 0)) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux_event, NULL /* action_results */)); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); } diff --git a/src/wolfsentry_internal.h b/src/wolfsentry_internal.h index 9984d052..c13556a4 100644 --- a/src/wolfsentry_internal.h +++ b/src/wolfsentry_internal.h @@ -340,6 +340,7 @@ struct wolfsentry_event { * policy for the ephemeral route. */ + wolfsentry_refcount_t aux_owner_count; /* events whose aux_event is this one */ byte label_len; char label[WOLFSENTRY_FLEXIBLE_ARRAY_SIZE]; diff --git a/tests/unittests.c b/tests/unittests.c index b1178d74..2fb34118 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9105,6 +9105,364 @@ static int test_event_aux_event_self_reference(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_event_reuse_after_clear(void) { + struct wolfsentry_context *wolfsentry, *ctx_clone; + struct wolfsentry_event *deleted_owner; + struct wolfsentry_event *held_aux; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* a -> b, then clear: b is free to own an aux event of its own */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* c is now an aux event, so it cannot own one */ + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* replacing a's aux event releases the old one */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_d", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* owner_one is deleted from the table but stays alive, and still owns shared_aux */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &deleted_owner)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_one", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* action_results */)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + deleted_owner, + NULL /* action_results */)); + + /* freeing the deleted owner released shared_aux, which can now own an aux event */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "shared_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "other_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + /* an ordinary reference on the aux event is not ownership */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &held_aux)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "held_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "held_target", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + held_aux, + NULL /* action_results */)); + + /* a clone rebuilds the aux ownership graph, independently of the source */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_clone(WOLFSENTRY_CONTEXT_ARGS_OUT, &ctx_clone, WOLFSENTRY_CLONE_FLAG_NONE)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(ctx_clone), + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "aux_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "owner_two", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_free(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&ctx_clone))); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13495,6 +13853,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_aux_event_self_reference failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_aux_event_reuse_after_clear(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_reuse_after_clear failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 74f2c64dc451067a2f63f208d212cd5ba3b1f292 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 12:50:21 -0700 Subject: [PATCH 222/255] F-3870 - https://fenrir.wolfssl.com/finding/3870 - Test that an event serving as an auxiliary event cannot have its own auxiliary event --- tests/unittests.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 2fb34118..7a609ad2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9463,6 +9463,74 @@ static int test_event_aux_event_reuse_after_clear(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_event_rejects_subevent_owner(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_a", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "subevent_b", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "subevent_c", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13859,6 +13927,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_aux_event_reuse_after_clear failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_aux_event_rejects_subevent_owner(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_rejects_subevent_owner failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 742b9aaf75f06d222b7ae2204e4e22b85f8bf0f3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:29:40 -0700 Subject: [PATCH 223/255] F-3871 - https://fenrir.wolfssl.com/finding/3871 - Test that a route parent event cannot be set as an auxiliary event --- tests/unittests.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 7a609ad2..1ccb16b5 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9531,6 +9531,83 @@ static int test_event_aux_event_rejects_subevent_owner(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_event_rejects_parent_event(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "route_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "route_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -13933,6 +14010,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_aux_event_rejects_subevent_owner failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_aux_event_rejects_parent_event(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_rejects_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 27e469bb7e03a752207d68fb6b27ddeb90c93caf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:30:58 -0700 Subject: [PATCH 224/255] F-10506 - https://fenrir.wolfssl.com/finding/10506 - Document the default event priority and test that omitted priority behaves as zero --- doc/json_configuration.md | 2 +- tests/unittests.c | 113 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/doc/json_configuration.md b/doc/json_configuration.md index 9aa1bc86..dcb00239 100644 --- a/doc/json_configuration.md +++ b/doc/json_configuration.md @@ -239,7 +239,7 @@ Each event is composed of the following elements, all of which are optional exce * `label` -- The name by which the event is identified. See the definition of `label` in the ABNF grammar below for permissible values. -* `priority` -- The priority of routes that have this event as their `parent-event` (see `routes` below). Lower number means higher priority. +* `priority` -- The priority of routes that have this event as their `parent-event` (see `routes` below). Lower number means higher priority. If omitted, the priority is 0, the highest priority. * `config` -- The configuration to associate with routes with this `parent-event`, as above for `config-update`. diff --git a/tests/unittests.c b/tests/unittests.c index 1ccb16b5..1effe278 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9608,6 +9608,111 @@ static int test_event_aux_event_rejects_parent_event(void) { WOLFSENTRY_RETURN_OK; } +#ifndef WOLFSENTRY_NO_JSON + +static int event_priority_check_winner(const char *priority_clause, int expect_accept) { + struct wolfsentry_context *wolfsentry; + wolfsentry_errcode_t ret; + wolfsentry_ent_id_t route_id; + wolfsentry_route_flags_t inexact_matches; + wolfsentry_action_res_t action_results; + char err_buf[512]; + char json[2048]; + int json_len; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1," + " \"events-insert\" : [" + " { \"label\" : \"first-event\" %s }," + " { \"label\" : \"second-event\", \"priority\" : 1 } ]," + " \"static-routes-insert\" : [" + " { \"parent-event\" : \"first-event\", \"direction-in\" : true," + " \"green-listed\" : true, \"penalty-boxed\" : false, \"family\" : %u," + " \"remote\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 }," + " \"local\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 } }," + " { \"parent-event\" : \"second-event\", \"direction-in\" : true," + " \"green-listed\" : false, \"penalty-boxed\" : true, \"family\" : %u," + " \"remote\" : { \"address\" : \"10.1.2.3\" }," + " \"local\" : { \"address\" : \"10.0.0.0\", \"prefix-bits\" : 8 } } ] }", + priority_clause, (unsigned int)AF_INET, (unsigned int)AF_INET); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf); + if (ret < 0) + (void)fprintf(stderr, "%s\n", err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\12\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\12\11\11\11", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_event_dispatch( + WOLFSENTRY_CONTEXT_ARGS_OUT, + &remote.sa, + &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, + 0 /* event_label_len */, + NULL /* caller_arg */, + &route_id, + &inexact_matches, + &action_results)); + + if (expect_accept) { + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } else { + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_ACCEPT)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); + } + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_default_priority(void) { + /* an omitted priority behaves as priority 0, the highest */ + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner("", 1)); + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner(", \"priority\" : 0", 1)); + WOLFSENTRY_EXIT_ON_FAILURE(event_priority_check_winner(", \"priority\" : 2", 0)); + + WOLFSENTRY_RETURN_OK; +} + +#endif /* !WOLFSENTRY_NO_JSON */ + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -14016,6 +14121,14 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_aux_event_rejects_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + +#ifndef WOLFSENTRY_NO_JSON + ret = test_event_default_priority(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_default_priority failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif #endif #ifdef TEST_USER_VALUES From 972f5ce18466ff6313869fda0672a81bdad44fcd Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 09:04:04 -0700 Subject: [PATCH 225/255] F-13883 - https://fenrir.wolfssl.com/finding/13883 - Accept a maximum-length event label in the JSON loader --- src/json/load_config.c | 2 +- tests/unittests.c | 71 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index dc142d26..ad74d30d 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -1337,7 +1337,7 @@ static wolfsentry_errcode_t handle_event_clause(struct wolfsentry_json_process_s } if ((jps->cur_depth == 3) && (type == JSON_STRING) && (! strcmp(jps->cur_keyname, "label"))) { - if (data_size >= sizeof jps->o_u_c.event.label) + if (data_size > sizeof jps->o_u_c.event.label) WOLFSENTRY_ERROR_RETURN(STRING_ARG_TOO_LONG); memcpy(jps->o_u_c.event.label, data, data_size); jps->o_u_c.event.label_len = (int)data_size; diff --git a/tests/unittests.c b/tests/unittests.c index 1effe278..9881a9e1 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9711,6 +9711,71 @@ static int test_event_default_priority(void) { WOLFSENTRY_RETURN_OK; } +static int test_json_event_max_length_label(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_errcode_t ret; + struct wolfsentry_eventconfig config; + char label[WOLFSENTRY_MAX_LABEL_BYTES + 1]; + char err_buf[512]; + char json[WOLFSENTRY_MAX_LABEL_BYTES + 128]; + int json_len; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + memset(label, 'L', sizeof label); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"events-insert\" : [ { \"label\" : \"%.*s\" } ] }", + (int)WOLFSENTRY_MAX_LABEL_BYTES, label); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + ret = wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf); + if (ret < 0) + (void)fprintf(stderr, "%s\n", err_buf); + WOLFSENTRY_EXIT_ON_FAILURE(ret); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_get_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + label, + (int)WOLFSENTRY_MAX_LABEL_BYTES, + &config)); + + json_len = snprintf( + json, sizeof json, + "{ \"wolfsentry-config-version\" : 1, \"events-insert\" : [ { \"label\" : \"%.*s\" } ] }", + (int)WOLFSENTRY_MAX_LABEL_BYTES + 1, label); + WOLFSENTRY_EXIT_ON_TRUE((json_len < 0) || ((size_t)json_len >= sizeof json)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + STRING_ARG_TOO_LONG, + wolfsentry_config_json_oneshot( + WOLFSENTRY_CONTEXT_ARGS_OUT, + (unsigned char *)json, + (size_t)json_len, + WOLFSENTRY_CONFIG_LOAD_FLAG_NONE, + err_buf, + sizeof err_buf)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* !WOLFSENTRY_NO_JSON */ #endif /* TEST_DYNAMIC_RULES */ @@ -14128,6 +14193,12 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_default_priority failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_json_event_max_length_label(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_json_event_max_length_label failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #endif From fc25c39b49905af7f972e8ccf2fb6110f8c9cdaf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 09:05:12 -0700 Subject: [PATCH 226/255] F-8758 - https://fenrir.wolfssl.com/finding/8758 - Set the action id output only after the action is inserted --- src/actions.c | 4 +-- tests/unittests.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/actions.c b/src/actions.c index 89eef45a..50969ec1 100644 --- a/src/actions.c +++ b/src/actions.c @@ -143,12 +143,12 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_action_insert_1( goto out; if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header)) < 0) goto out; - if (id) - *id = new->header.id; if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &new->header, &wolfsentry->actions->header, 1 /* unique_p */)) < 0) { ret = WOLFSENTRY_ERROR_RECODE(ret); goto out; } + if (id != NULL) + *id = new->header.id; ret = WOLFSENTRY_ERROR_ENCODE(OK); out: diff --git a/tests/unittests.c b/tests/unittests.c index 9881a9e1..0a58bcb2 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9778,6 +9778,77 @@ static int test_json_event_max_length_label(void) { #endif /* !WOLFSENTRY_NO_JSON */ +static wolfsentry_errcode_t test_action_insert_noop( + WOLFSENTRY_CONTEXT_ARGS_IN, + const struct wolfsentry_action *action, + void *handler_arg, + void *caller_arg, + const struct wolfsentry_event *trigger_event, + wolfsentry_action_type_t action_type, + const struct wolfsentry_route *target_route, + struct wolfsentry_route_table *route_table, + struct wolfsentry_route *rule_route, + wolfsentry_action_res_t *action_results) +{ + WOLFSENTRY_CONTEXT_ARGS_NOT_USED; + (void)action; + (void)handler_arg; + (void)caller_arg; + (void)trigger_event; + (void)action_type; + (void)target_route; + (void)route_table; + (void)rule_route; + (void)action_results; + WOLFSENTRY_RETURN_OK; +} + +static int test_action_insert_failure_leaves_id_untouched(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id, id_before; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + id_before = id; + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + ITEM_ALREADY_PRESENT, + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "id_out_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + WOLFSENTRY_EXIT_ON_FALSE(id == id_before); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -14200,6 +14271,12 @@ int main(int argc, char* argv[]) { err = 1; } #endif + + ret = test_action_insert_failure_leaves_id_untouched(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_action_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES From 3fe03d5320ad7f7dfef133ee72a1c76b07bf19ed Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 09:16:12 -0700 Subject: [PATCH 227/255] F-11619 - https://fenrir.wolfssl.com/finding/11619 - Treat aux-event use and held references as route ownership in the event layout guard --- src/events.c | 6 ++ tests/unittests.c | 202 ++++++++++++++++++++++++++++++++++++++++ wolfsentry/wolfsentry.h | 2 +- 3 files changed, 209 insertions(+), 1 deletion(-) diff --git a/src/events.c b/src/events.c index 6e602e2e..435a7a27 100644 --- a/src/events.c +++ b/src/events.c @@ -400,6 +400,12 @@ static int wolfsentry_event_has_routes(const struct wolfsentry_context *wolfsent if (WOLFSENTRY_CHECK_BITS(event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT)) return 1; + /* an aux event parents the ephemeral routes its owners generate */ + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) != 0) + return 1; + /* beyond the table's own reference, each live route, deleted or not, holds one */ + if (WOLFSENTRY_ATOMIC_LOAD(event->header.refcount) > 1) + return 1; for (ent = wolfsentry_table_first(&wolfsentry->routes->header); ent != NULL; ent = ent->next) { if (((const struct wolfsentry_route *)ent)->parent_event == event) return 1; diff --git a/tests/unittests.c b/tests/unittests.c index 0a58bcb2..d13d2d4e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9849,6 +9849,196 @@ static int test_action_insert_failure_leaves_id_untouched(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_event_layout_pinned(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + NULL /* aux_event_label */, + 0)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + +static int test_event_layout_guard_held_deleted_route(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + struct wolfsentry_route *route = NULL; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; +#ifdef WOLFSENTRY_HAVE_DESIGNATED_INITIALIZERS + static const struct wolfsentry_eventconfig config = { .route_private_data_size = PRIVATE_DATA_SIZE, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT, .max_connection_count = 10 }; + static const struct wolfsentry_eventconfig config2 = { .route_private_data_size = PRIVATE_DATA_SIZE * 2, .route_private_data_alignment = PRIVATE_DATA_ALIGNMENT * 2, .max_connection_count = 15 }; +#else + static const struct wolfsentry_eventconfig config = { PRIVATE_DATA_SIZE, PRIVATE_DATA_ALIGNMENT, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + static const struct wolfsentry_eventconfig config2 = { PRIVATE_DATA_SIZE * 2, PRIVATE_DATA_ALIGNMENT * 2, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#endif + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_action_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_FLAG_NONE, + test_action_insert_noop, + NULL /* handler_arg */, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + &config, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_action_append( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + WOLFSENTRY_ACTION_TYPE_INSERT, + "layout_insert_action", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_and_check_out( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &route, + &action_results)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + route->header.id, + NULL /* trigger_label */, + 0, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_update_config( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "layout_held_parent", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &config2)); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_drop_reference( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_DYNAMIC_RULES */ #ifdef TEST_USER_VALUES @@ -14277,6 +14467,18 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_action_insert_failure_leaves_id_untouched failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + + ret = test_event_aux_event_layout_pinned(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_event_layout_pinned failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + + ret = test_event_layout_guard_held_deleted_route(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_layout_guard_held_deleted_route failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #endif #ifdef TEST_USER_VALUES diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index ec749036..262a984a 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2801,7 +2801,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_get_config( \param label_len the length of the label \param config the updated configuration - \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. Once any route has used the event as its parent, changes to `route_private_data_size` or `route_private_data_alignment` are rejected with `INCOMPATIBLE_STATE`, even after that route is deleted, because a deleted route can still be held by a checked-out reference. + \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. Once any route has used the event as its parent, or while the event is another event's aux event or is otherwise referenced, changes to `route_private_data_size` or `route_private_data_alignment` are rejected with `INCOMPATIBLE_STATE`, even after that route is deleted, because a deleted route can still be held by a checked-out reference. \sa WOLFSENTRY_CONTEXT_ARGS_IN */ From a352556ad739faa556a22feb97aa6ff623b1f819 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 18:23:07 -0700 Subject: [PATCH 228/255] F-5566 - https://fenrir.wolfssl.com/finding/5566 - Derive the reported subevent mark from the aux owner count --- src/events.c | 12 +++++++++++- tests/unittests.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/events.c b/src/events.c index 435a7a27..1c4c2767 100644 --- a/src/events.c +++ b/src/events.c @@ -345,7 +345,17 @@ WOLFSENTRY_API const char *wolfsentry_event_get_label(const struct wolfsentry_ev WOLFSENTRY_API wolfsentry_event_flags_t wolfsentry_event_get_flags(const struct wolfsentry_event *event) { - return event ? event->flags : WOLFSENTRY_EVENT_FLAG_NONE; + wolfsentry_event_flags_t flags; + + if (event == NULL) + return WOLFSENTRY_EVENT_FLAG_NONE; + /* an owner freed without the context lock only drops the count, so derive the mark from it */ + flags = event->flags; + if (WOLFSENTRY_ATOMIC_LOAD(event->aux_owner_count) > 0) + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + else + WOLFSENTRY_CLEAR_BITS(flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT); + return flags; } WOLFSENTRY_API const struct wolfsentry_event *wolfsentry_event_get_aux_event(const struct wolfsentry_event *event) diff --git a/tests/unittests.c b/tests/unittests.c index d13d2d4e..bf0fdc8b 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -9463,6 +9463,37 @@ static int test_event_aux_event_reuse_after_clear(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_aux_mark_cleared_on_owner_free(void) { + struct wolfsentry_context *wolfsentry; + struct wolfsentry_event *aux; + wolfsentry_ent_id_t id; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED, 10, NULL /* config */, WOLFSENTRY_EVENT_FLAG_NONE, &id)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_set_aux_event(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_get_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_aux", WOLFSENTRY_LENGTH_NULL_TERMINATED, &aux)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(aux), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + /* freeing the only owner leaves the aux event with no owners */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_delete(WOLFSENTRY_CONTEXT_ARGS_OUT, "mark_owner", WOLFSENTRY_LENGTH_NULL_TERMINATED, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(wolfsentry_event_get_flags(aux), WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_event_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, aux, NULL /* action_results */)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + WOLFSENTRY_RETURN_OK; +} + static int test_event_aux_event_rejects_subevent_owner(void) { struct wolfsentry_context *wolfsentry; wolfsentry_ent_id_t id; @@ -14436,6 +14467,12 @@ int main(int argc, char* argv[]) { err = 1; } + ret = test_event_aux_mark_cleared_on_owner_free(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_aux_mark_cleared_on_owner_free failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } + ret = test_event_aux_event_rejects_subevent_owner(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_event_aux_event_rejects_subevent_owner failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From f942fef807d8e5ec48778332f913148a6fa31b2b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:41:21 -0700 Subject: [PATCH 229/255] F-2846 - https://fenrir.wolfssl.com/finding/2846 - Test that route insert rejects a route with no direction flag --- tests/unittests.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index bf0fdc8b..850f3a7c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4792,6 +4792,78 @@ static int test_static_routes_id_reclaim(void) { WOLFSENTRY_RETURN_OK; } +static int test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4001; + local.sa.sa_port = 4002; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x0d", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x0e", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + /* the same route is accepted once it has a direction. */ + WOLFSENTRY_SET_BITS(flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + +static int test_static_routes_guards(void) { + struct wolfsentry_context *wolfsentry; + static const struct wolfsentry_eventconfig config = { 0 }; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + &config, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #endif /* TEST_STATIC_ROUTES */ #ifdef TEST_DYNAMIC_RULES @@ -14348,6 +14420,14 @@ int main(int argc, char* argv[]) { } #endif +#ifdef TEST_STATIC_ROUTES + ret = test_static_routes_guards(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_static_routes_guards failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } +#endif + #ifdef TEST_DYNAMIC_RULES ret = test_dynamic_rules(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { From 0f3ee6320e466fb95b501187678ff722bfcbedeb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:41:56 -0700 Subject: [PATCH 230/255] F-2848 - https://fenrir.wolfssl.com/finding/2848 - Test that route insert by exports rejects a route with no direction flag --- tests/unittests.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 850f3a7c..8e0c7550 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4841,6 +4841,54 @@ static int test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_IN) { return 0; } +static int test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route_exports exp_route; + byte exp_remote_addr[4]; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + + memset(&exp_route, 0, sizeof exp_route); + exp_route.sa_family = AF_INET; + exp_route.sa_proto = IPPROTO_TCP; + exp_route.flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_LOCAL_INTERFACE_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_REMOTE_INTERFACE_WILDCARD; + memcpy(exp_remote_addr, "\x0a\x0b\x0c\x0d", sizeof exp_remote_addr); + exp_route.remote_address = exp_remote_addr; + exp_route.remote.addr_len = sizeof exp_remote_addr * BITS_PER_BYTE; + exp_route.local.addr_len = 0; + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + + /* the same route is accepted once it has a direction. */ + WOLFSENTRY_SET_BITS(exp_route.flags, WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert_by_exports( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &exp_route, + &id, &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -4857,6 +4905,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 039b90267b96f48e0adce797cd50fbb6ac21272e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:44:00 -0700 Subject: [PATCH 231/255] F-3440 - https://fenrir.wolfssl.com/finding/3440 - Test that route flag validation rejects inconsistent wildcard combinations --- tests/unittests.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 8e0c7550..fefa0de6 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4889,6 +4889,86 @@ static int test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_AR return 0; } +static int test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t proto_flags, all_flags; + unsigned int i; + static const wolfsentry_route_flags_t family_deps[] = { + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD + }; + static const wolfsentry_route_flags_t proto_deps[] = { + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD + }; + + proto_flags = WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_PORT_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD; + all_flags = proto_flags + | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD + | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_ADDR_WILDCARD; + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(WOLFSENTRY_ROUTE_FLAG_NONE)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(proto_flags)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_check_flags_sensical(all_flags)); + + /* a family wildcard needs both address wildcards and the protocol wildcard. */ + for (i = 0; i < sizeof family_deps / sizeof family_deps[0]; ++i) { + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(all_flags & ~family_deps[i])); + } + + /* a protocol wildcard needs both port wildcards. */ + for (i = 0; i < sizeof proto_deps / sizeof proto_deps[0]; ++i) { + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(proto_flags & ~proto_deps[i])); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_check_flags_sensical(all_flags & ~proto_deps[i])); + } + + /* the validation also applies on the insert path. */ + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x0f", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x10", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | WOLFSENTRY_ROUTE_FLAG_SA_PROTO_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_LOCAL_PORT_WILDCARD, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN | WOLFSENTRY_ROUTE_FLAG_SA_FAMILY_WILDCARD | WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD | proto_flags, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -4907,6 +4987,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_insert_by_exports_requires_direction(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 50cc0c8121f72a18d24b37663cc48d1bb3502860 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:44:40 -0700 Subject: [PATCH 232/255] F-2842 - https://fenrir.wolfssl.com/finding/2842 - Test that route flag update rejects setting both penaltybox and greenlist --- tests/unittests.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index fefa0de6..fa43461e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -4969,6 +4969,82 @@ static int test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_IN) { return 0; } +static int insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_IN, struct wolfsentry_route **route_out, wolfsentry_ent_id_t *id_out) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + wolfsentry_action_res_t action_results; + wolfsentry_ent_id_t id; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4003; + local.sa.sa_port = 4004; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x11", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\x0a\x0b\x0c\x12", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &id, &action_results)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, id, (struct wolfsentry_table_ent_header **)route_out)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + *id_out = id; + + return 0; +} + +static int test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INVALID_ARG, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED | WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_ATOMIC_LOAD(route->flags) & (WOLFSENTRY_ROUTE_FLAG_PENALTYBOXED | WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); + + /* either flag alone is accepted. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) & WOLFSENTRY_ROUTE_FLAG_GREENLISTED); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -4989,6 +5065,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_flags_sensical_validation(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 1dacf1c2827cdc7ef56ba45f3fddd36492a798b9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:45:09 -0700 Subject: [PATCH 233/255] F-2844 - https://fenrir.wolfssl.com/finding/2844 - Test that route flag update rejects changes to immutable flags on an inserted route --- tests/unittests.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index fa43461e..8c87716e 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5045,6 +5045,64 @@ static int test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_C return 0; } +static int test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after, flags_at_entry; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + flags_at_entry = WOLFSENTRY_ATOMIC_LOAD(route->flags); + WOLFSENTRY_EXIT_ON_FALSE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_IN_TABLE); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_OUT, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_SA_REMOTE_ADDR_WILDCARD, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) == flags_at_entry); + + /* mutable flags can still be changed. */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_GREENLISTED, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(flags_after == (flags_at_entry | WOLFSENTRY_ROUTE_FLAG_GREENLISTED)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -5067,6 +5125,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_rejects_penaltybox_and_greenlist(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 10f695e989760ff9f678edc47a05b235c060803a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:45:39 -0700 Subject: [PATCH 234/255] F-2847 - https://fenrir.wolfssl.com/finding/2847 - Test that route flag update rejects changes to internal table flags --- tests/unittests.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index 8c87716e..e0663cb9 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5103,6 +5103,63 @@ static int test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_A return 0; } +static int test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct wolfsentry_route *route; + wolfsentry_ent_id_t id; + wolfsentry_route_flags_t flags_before, flags_after, flags_at_entry; + wolfsentry_action_res_t action_results; + + WOLFSENTRY_EXIT_ON_FALSE(insert_route_for_update_flags(WOLFSENTRY_CONTEXT_ARGS_OUT, &route, &id) == 0); + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + flags_at_entry = WOLFSENTRY_ATOMIC_LOAD(route->flags); + WOLFSENTRY_EXIT_ON_FALSE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_IN_TABLE); + WOLFSENTRY_EXIT_ON_TRUE(flags_at_entry & WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_IN_TABLE, + WOLFSENTRY_ROUTE_FLAG_NONE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_IN_TABLE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + NOT_PERMITTED, + wolfsentry_route_update_flags( + WOLFSENTRY_CONTEXT_ARGS_OUT, + route, + WOLFSENTRY_ROUTE_FLAG_NONE, + WOLFSENTRY_ROUTE_FLAG_PENDING_DELETE, + &flags_before, &flags_after, &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_ATOMIC_LOAD(route->flags) == flags_at_entry); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_delete_by_id( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + id, + NULL /* event_label */, 0 /* event_label_len */, + &action_results)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -5127,6 +5184,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_immutable_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 6bb0e6ae58bf66cf3e9258bbd0c614d11ee3796a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 22 Oct 2026 13:47:26 -0700 Subject: [PATCH 235/255] F-4563 - https://fenrir.wolfssl.com/finding/4563 - Test that lowering the purgeable route limit purges excess stale routes --- tests/unittests.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/unittests.c b/tests/unittests.c index e0663cb9..0d98e741 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5160,6 +5160,73 @@ static int test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_AR return 0; } +static int test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_IN) { + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + struct wolfsentry_route_table *main_routes; + struct wolfsentry_route *route; + wolfsentry_action_res_t action_results; + wolfsentry_hitcount_t max_purgeable_routes; + wolfsentry_ent_id_t ids[3]; + unsigned int i; + + memset(&remote, 0, sizeof remote); + memset(&local, 0, sizeof local); + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 4005; + local.sa.sa_port = 4006; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + memcpy(local.sa.addr, "\x0a\x0b\x0c\x14", sizeof local.addr_buf); + + WOLFSENTRY_CLEAR_ALL_BITS(action_results); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_shared(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_get_main_table(WOLFSENTRY_CONTEXT_ARGS_OUT, &main_routes)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* three routes with no connections, all already due for purging. */ + for (i = 0; i < sizeof ids / sizeof ids[0]; ++i) { + memcpy(remote.sa.addr, "\x0a\x0b\x0c\x00", sizeof remote.addr_buf); + remote.sa.addr[3] = (byte)(0x20 + i); + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, &local.sa, + WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN, + NULL /* event_label */, 0 /* event_label_len */, + &ids[i], &action_results)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_table_ent_get_by_id(WOLFSENTRY_CONTEXT_ARGS_OUT, ids[i], (struct wolfsentry_table_ent_header **)&route)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_purge_time_set(WOLFSENTRY_CONTEXT_ARGS_OUT, route, 1)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + } + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_lock_mutex(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + /* raising the limit, or lowering it to exactly the current count, purges nothing. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 5)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 3)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 10)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len == 3); + + /* lowering the limit below the current count purges immediately. */ + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 2)); + WOLFSENTRY_EXIT_ON_FALSE(main_routes->purge_list.len <= 2); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_get(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, &max_purgeable_routes)); + WOLFSENTRY_EXIT_ON_FALSE(max_purgeable_routes == 2); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_route_table_max_purgeable_routes_set(WOLFSENTRY_CONTEXT_ARGS_OUT, main_routes, 0)); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_context_unlock(WOLFSENTRY_CONTEXT_ARGS_OUT)); + + return 0; +} + static int test_static_routes_guards(void) { struct wolfsentry_context *wolfsentry; static const struct wolfsentry_eventconfig config = { 0 }; @@ -5186,6 +5253,8 @@ static int test_static_routes_guards(void) { WOLFSENTRY_EXIT_ON_FALSE(test_route_update_flags_protects_internal_flags(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_OUT) == 0); + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); From 42543cb67b972ab45cd3cbf44fda3245b3efb22f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 23 Oct 2026 13:46:29 -0700 Subject: [PATCH 236/255] F-4563 - https://fenrir.wolfssl.com/finding/4563 - Set purge limit test addresses through a full-width buffer for C89 builds --- tests/unittests.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 0d98e741..24c21384 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -5171,6 +5171,7 @@ static int test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_ wolfsentry_hitcount_t max_purgeable_routes; wolfsentry_ent_id_t ids[3]; unsigned int i; + byte addr[4] = { 0x0a, 0x0b, 0x0c, 0x00 }; memset(&remote, 0, sizeof remote); memset(&local, 0, sizeof local); @@ -5188,8 +5189,8 @@ static int test_route_table_max_purgeable_routes_shrink(WOLFSENTRY_CONTEXT_ARGS_ /* three routes with no connections, all already due for purging. */ for (i = 0; i < sizeof ids / sizeof ids[0]; ++i) { - memcpy(remote.sa.addr, "\x0a\x0b\x0c\x00", sizeof remote.addr_buf); - remote.sa.addr[3] = (byte)(0x20 + i); + addr[3] = (byte)(0x20 + i); + memcpy(remote.sa.addr, addr, sizeof addr); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_route_insert( WOLFSENTRY_CONTEXT_ARGS_OUT, From f148db48d8dea14a554a87538d2b02a85c5b70e7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:44:02 -0800 Subject: [PATCH 237/255] F-3442 - https://fenrir.wolfssl.com/finding/3442 - Render KV integers without a 64-bit printf conversion so C89 builds keep every digit --- src/kv.c | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/kv.c b/src/kv.c index bfcd355b..c5e892a8 100644 --- a/src/kv.c +++ b/src/kv.c @@ -512,6 +512,31 @@ static int wolfsentry_kv_render_string_escaped( #endif /* !WOLFSENTRY_HAVE_JSON_DOM */ +/* snprintf() semantics; C89 has no portable 64-bit conversion specifier. */ +static int wolfsentry_kv_render_uint64(uint64_t magnitude, int negative, char *out, size_t out_space) +{ + char digits[20]; + size_t n_digits = 0; + size_t needed; + size_t pos = 0; + + do { + digits[n_digits++] = (char)('0' + (int)(magnitude % 10U)); + magnitude /= 10U; + } while (magnitude != 0); + needed = n_digits + (negative ? 1U : 0U); + + if (out_space > 0) { + if (negative && (pos < out_space - 1)) + out[pos++] = '-'; + while ((n_digits > 0) && (pos < out_space - 1)) + out[pos++] = digits[--n_digits]; + out[pos] = 0; + } + + return (int)needed; +} + WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( WOLFSENTRY_CONTEXT_ARGS_IN, const struct wolfsentry_kv_pair *kv, @@ -535,19 +560,16 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_kv_render_value( *out_len = snprintf(out, out_space, "false"); break; case WOLFSENTRY_KV_UINT: -#ifdef PRIu64 - *out_len = snprintf(out, out_space, "%" PRIu64, WOLFSENTRY_KV_V_UINT(kv)); -#else - *out_len = snprintf(out, out_space, "%lu", (long unsigned int)WOLFSENTRY_KV_V_UINT(kv)); -#endif + *out_len = wolfsentry_kv_render_uint64(WOLFSENTRY_KV_V_UINT(kv), 0, out, out_space); break; - case WOLFSENTRY_KV_SINT: -#ifdef PRId64 - *out_len = snprintf(out, out_space, "%" PRId64, WOLFSENTRY_KV_V_SINT(kv)); -#else - *out_len = snprintf(out, out_space, "%ld", (long int)WOLFSENTRY_KV_V_SINT(kv)); -#endif + case WOLFSENTRY_KV_SINT: { + int64_t v_sint = WOLFSENTRY_KV_V_SINT(kv); + if (v_sint < 0) + *out_len = wolfsentry_kv_render_uint64((uint64_t)(-(v_sint + 1)) + 1U, 1, out, out_space); + else + *out_len = wolfsentry_kv_render_uint64((uint64_t)v_sint, 0, out, out_space); break; + } case WOLFSENTRY_KV_FLOAT: { double v_float = WOLFSENTRY_KV_V_FLOAT(kv); /* non-finite values have no bare JSON token, so emit a quoted string From dbef6d7ccdd3f434b12317a4de5d62f8591e7beb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:44:02 -0800 Subject: [PATCH 238/255] F-10491 - https://fenrir.wolfssl.com/finding/10491 - Suppress the cppcheck float cast warning on the intentional round-trip check --- src/json/centijson_value.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index 27d9e619..fada244a 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -319,7 +319,7 @@ json_value_is_compatible(const JSON_VALUE* v, JSON_VALUE_TYPE type) (type == JSON_VALUE_UINT64 && (double)json_value_float(v) >= 0.0 && (double)json_value_float(v) < 18446744073709551616.0 && json_value_float(v) == (float)json_value_uint64(v)); case JSON_VALUE_DOUBLE: - return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || + return (type == JSON_VALUE_FLOAT && json_value_double(v) >= -(double)FLT_MAX && json_value_double(v) <= (double)FLT_MAX && (double)(float)json_value_double(v) == json_value_double(v)) || /* cppcheck-suppress suspiciousFloatingPointCast */ (type == JSON_VALUE_INT32 && json_value_double(v) >= -2147483648.0 && json_value_double(v) <= 2147483647.0 && json_value_double(v) == (double)json_value_int32(v)) || (type == JSON_VALUE_UINT32 && json_value_double(v) >= 0.0 && json_value_double(v) <= 4294967295.0 && json_value_double(v) == (double)json_value_uint32(v)) || (type == JSON_VALUE_INT64 && json_value_double(v) >= -9223372036854775808.0 && json_value_double(v) < 9223372036854775808.0 && json_value_double(v) == (double)json_value_int64(v)) || From e62e4ae4d045847ba83e2fcf98bc970a9d688504 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:44:09 -0800 Subject: [PATCH 239/255] F-8772 - https://fenrir.wolfssl.com/finding/8772 - Compare the boundary test against double constants so x87 excess precision cannot fail it --- tests/unittests.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unittests.c b/tests/unittests.c index 24c21384..82ed208c 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -14326,7 +14326,8 @@ static wolfsentry_errcode_t test_json_sax_number_text_boundary(void) { for (len = 62; len <= 63; ++len) { result = 0.0; WOLFSENTRY_EXIT_ON_FAILURE(json_number_to_double(num, len, &result)); - WOLFSENTRY_EXIT_ON_FALSE(result == ((len == 62) ? 1e61 : 1e62)); + /* the cast drops x87 excess precision from the constants */ + WOLFSENTRY_EXIT_ON_FALSE(result == (double)((len == 62) ? 1e61 : 1e62)); } WOLFSENTRY_EXIT_ON_TRUE(json_number_to_double(num, 64, &result) >= 0); From 37a1cd1410ea15f599442678e1560e574a8f6f18 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:44:09 -0800 Subject: [PATCH 240/255] F-6511 - https://fenrir.wolfssl.com/finding/6511 - Build non-finite test values with strtod so cppcheck can parse the test --- tests/unittests.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index 82ed208c..ac085390 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -11295,6 +11295,7 @@ static int test_kv_render_float_roundtrip(void) { static int test_kv_render_nonfinite_float(void) { struct wolfsentry_context *wolfsentry; char buf[64]; + double reparsed; WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); @@ -11310,13 +11311,14 @@ static int test_kv_render_nonfinite_float(void) { wolfsentry_user_value_store_double( WOLFSENTRY_CONTEXT_ARGS_OUT, "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, - (double)NAN, 0)); + strtod("nan", NULL), 0)); WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); /* a bare nan/inf is not valid JSON; the rendered value must be quoted and * must reparse to the same non-finite value. */ WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); - WOLFSENTRY_EXIT_ON_FALSE(isnan(strtod(buf + 1, NULL))); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isnan(reparsed)); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_user_value_delete( WOLFSENTRY_CONTEXT_ARGS_OUT, "nan_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); @@ -11325,10 +11327,11 @@ static int test_kv_render_nonfinite_float(void) { wolfsentry_user_value_store_double( WOLFSENTRY_CONTEXT_ARGS_OUT, "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, - (double)INFINITY, 0)); + strtod("inf", NULL), 0)); WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); - WOLFSENTRY_EXIT_ON_FALSE(isinf(strtod(buf + 1, NULL)) && (strtod(buf + 1, NULL) > 0.0)); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isinf(reparsed) && (reparsed > 0.0)); WOLFSENTRY_EXIT_ON_FAILURE( wolfsentry_user_value_delete( WOLFSENTRY_CONTEXT_ARGS_OUT, "inf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED)); @@ -11337,10 +11340,11 @@ static int test_kv_render_nonfinite_float(void) { wolfsentry_user_value_store_double( WOLFSENTRY_CONTEXT_ARGS_OUT, "ninf_val", WOLFSENTRY_LENGTH_NULL_TERMINATED, - -(double)INFINITY, 0)); + -strtod("inf", NULL), 0)); WOLFSENTRY_EXIT_ON_FAILURE(render_only_user_value(WOLFSENTRY_CONTEXT_ARGS_OUT, buf, (int)sizeof buf)); WOLFSENTRY_EXIT_ON_FALSE(buf[0] == '"'); - WOLFSENTRY_EXIT_ON_FALSE(isinf(strtod(buf + 1, NULL)) && (strtod(buf + 1, NULL) < 0.0)); + reparsed = strtod(buf + 1, NULL); + WOLFSENTRY_EXIT_ON_FALSE(isinf(reparsed) && (reparsed < 0.0)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); From 305f8a9504bb64e995efbf99ddb3df37a74da13a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:44:09 -0800 Subject: [PATCH 241/255] Fix cppcheck style findings in the new unit tests --- tests/unittests.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/unittests.c b/tests/unittests.c index ac085390..b7fb2c64 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -1910,7 +1910,7 @@ static int test_context_exchange_default_policy(void) { static wolfsentry_errcode_t test_ll_insert_before_head_links(void) { struct wolfsentry_list_header list; struct wolfsentry_list_ent_header a, b, c, d; - struct wolfsentry_list_ent_header *cursor; + const struct wolfsentry_list_ent_header *cursor; wolfsentry_hitcount_t n; WOLFSENTRY_LIST_HEADER_RESET(list); @@ -2029,7 +2029,7 @@ static wolfsentry_errcode_t test_ll_insert_after_tail_links(void) { struct wolfsentry_list_header list; struct wolfsentry_list_ent_header a, b, c; struct wolfsentry_list_ent_header sentinel; - struct wolfsentry_list_ent_header *cursor; + const struct wolfsentry_list_ent_header *cursor; wolfsentry_hitcount_t n; WOLFSENTRY_LIST_HEADER_RESET(list); @@ -4494,7 +4494,7 @@ static int test_static_routes_clone_addr_family_links(void) { struct wolfsentry_context *wolfsentry; struct wolfsentry_context *clone = NULL; wolfsentry_action_res_t action_results; - struct wolfsentry_table_ent_header *i, *prev; + const struct wolfsentry_table_ent_header *i, *prev; wolfsentry_addr_bits_t bits; unsigned int n; static const char * const names[3] = { "clone_AF_0", "clone_AF_1", "clone_AF_2" }; @@ -11398,7 +11398,6 @@ static int test_kv_readonly_overwrite_rejected(void) { struct wolfsentry_kv_pair_internal *kv_ref = NULL; const char *value = NULL; int value_len = -1; - wolfsentry_errcode_t ret; static const char key[] = "ro_key"; static const char orig[] = "orig"; @@ -11428,7 +11427,7 @@ static int test_kv_readonly_overwrite_rejected(void) { /* overwriting a read-only value must be rejected. */ WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( NOT_PERMITTED, - ret = wolfsentry_user_value_store_string( + wolfsentry_user_value_store_string( WOLFSENTRY_CONTEXT_ARGS_OUT, key, WOLFSENTRY_LENGTH_NULL_TERMINATED, "different", WOLFSENTRY_LENGTH_NULL_TERMINATED, @@ -12737,7 +12736,7 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT }; JSON_VALUE dupkey_root; - JSON_VALUE *dupkey_v; + const JSON_VALUE *dupkey_v; JSON_INPUT_POS dupkey_pos; WOLFSENTRY_EXIT_ON_TRUE( @@ -12774,7 +12773,7 @@ static int test_json(const char *fname, const char *extra_fname) { WOLFSENTRY_MAX_LABEL_BYTES, 10, JSON_NOSCALARROOT }; JSON_VALUE dupnest_root; - JSON_VALUE *dupnest_v; + const JSON_VALUE *dupnest_v; JSON_INPUT_POS dupnest_pos; WOLFSENTRY_EXIT_ON_TRUE( @@ -13305,7 +13304,7 @@ static int test_cjvalue_path(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_all JSON_VALUE root; JSON_VALUE *list; JSON_VALUE *elem; - JSON_VALUE *found; + const JSON_VALUE *found; WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &root) < 0); From 7214539808bbcaf7cae917dd958eeea0d9dd1d9a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:47:00 -0800 Subject: [PATCH 242/255] F-11617 - https://fenrir.wolfssl.com/finding/11617 - Name the BUSY error in plain text so doxygen has no unresolved link --- wolfsentry/wolfsentry.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfsentry/wolfsentry.h b/wolfsentry/wolfsentry.h index 262a984a..dbb52436 100644 --- a/wolfsentry/wolfsentry.h +++ b/wolfsentry/wolfsentry.h @@ -2922,7 +2922,7 @@ WOLFSENTRY_API wolfsentry_errcode_t wolfsentry_event_action_insert_after( \return WOLFSENTRY_IS_SUCCESS(ret) is true on success. An action handler invoked during event dispatch must not call this from - within the dispatch; it returns #WOLFSENTRY_ERROR_ID_BUSY while any dispatch + within the dispatch; it returns `BUSY` while any dispatch is in progress. Perform action-list deletions outside dispatch; append/prepend/insert remain permitted. From 7f454cb338e45dd7fcea08ba425e43c51af6c2db Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:47:00 -0800 Subject: [PATCH 243/255] Allow the glibc C23 strto aliases in the library dependency tests --- Makefile.analyzers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.analyzers b/Makefile.analyzers index fb91cb1c..a851e280 100644 --- a/Makefile.analyzers +++ b/Makefile.analyzers @@ -432,7 +432,7 @@ minimal-threaded-build-test: @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-minimal-threaded-builds" clean @echo "passed: minimal threaded build test." -LIBRARY_DEPENDENCY_SUPPRESSIONS_BASE := U wolfsentry_|U json_|U (inet_ntop|inet_pton|memchr|memcmp|memcpy|__memcpy_chk|memmove|memset|__popcountdi2|snprintf|__snprintf_chk|strchr|strcmp|strlen|strncasecmp|strncmp|strtod|strtol|strtoll|strtoul|strtoull)$$ +LIBRARY_DEPENDENCY_SUPPRESSIONS_BASE := U wolfsentry_|U json_|U (inet_ntop|inet_pton|memchr|memcmp|memcpy|__memcpy_chk|memmove|memset|__popcountdi2|snprintf|__snprintf_chk|strchr|strcmp|strlen|strncasecmp|strncmp|strtod|strtol|strtoll|strtoul|strtoull|__isoc23_strtol|__isoc23_strtoll|__isoc23_strtoul|__isoc23_strtoull)$$ .PHONY: library-dependency-singlethreaded-build-test library-dependency-singlethreaded-build-test: From 32a86105f032e1140f71b1b42de373fd15bb2dc0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 18:21:48 -0800 Subject: [PATCH 244/255] F-4277 - https://fenrir.wolfssl.com/finding/4277 - Widen hex digit values before adding the offset in the config magnitude check --- src/json/load_config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json/load_config.c b/src/json/load_config.c index ad74d30d..f691c5e8 100644 --- a/src/json/load_config.c +++ b/src/json/load_config.c @@ -184,9 +184,9 @@ static wolfsentry_errcode_t convert_check_magnitude(const char *buf, uint64_t li if ((*p >= '0') && (*p <= '9')) digit = (uint64_t)(*p - '0'); else if ((*p >= 'a') && (*p <= 'f')) - digit = (uint64_t)(*p - 'a' + 10); + digit = (uint64_t)(*p - 'a') + 10U; else if ((*p >= 'A') && (*p <= 'F')) - digit = (uint64_t)(*p - 'A' + 10); + digit = (uint64_t)(*p - 'A') + 10U; else WOLFSENTRY_ERROR_RETURN(CONFIG_INVALID_VALUE); if (digit >= base) From 428c39c5748b57bccb865939d45d3c7bb60e0bac Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 18:36:13 -0800 Subject: [PATCH 245/255] Raise the FreeRTOS arm32 image size limit to fit the added validation code --- Makefile.analyzers | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile.analyzers b/Makefile.analyzers index a851e280..a5184265 100644 --- a/Makefile.analyzers +++ b/Makefile.analyzers @@ -574,7 +574,7 @@ freertos-arm32-thumb-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 230000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 240000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-thumb-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. @@ -592,7 +592,7 @@ freertos-arm32-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 230000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 240000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. From a8ab5f9a6eb2c28de8901b1e21e3148378a7cc36 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 13:30:18 -0800 Subject: [PATCH 246/255] Run make check-all in CI --- .../docker/wolfsentry-check-all/Dockerfile | 38 ++++++++++ .../wolfsentry-check-all/run-check-all.sh | 36 +++++++++ .github/workflows/check-all.yml | 74 +++++++++++++++++++ .github/workflows/nightly.yml | 1 + 4 files changed, 149 insertions(+) create mode 100644 .github/docker/wolfsentry-check-all/Dockerfile create mode 100755 .github/docker/wolfsentry-check-all/run-check-all.sh create mode 100644 .github/workflows/check-all.yml diff --git a/.github/docker/wolfsentry-check-all/Dockerfile b/.github/docker/wolfsentry-check-all/Dockerfile new file mode 100644 index 00000000..19033d68 --- /dev/null +++ b/.github/docker/wolfsentry-check-all/Dockerfile @@ -0,0 +1,38 @@ +FROM ubuntu:26.04@sha256:157dc3d6e4a3e4f2c68d12df9df9ed6e808b59af6d4c3757b432b4f519916725 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + autoconf \ + automake \ + ca-certificates \ + clang \ + clang-tidy \ + cppcheck \ + curl \ + doxygen \ + dwarves \ + gcc \ + gcc-15 \ + gcc-15-multilib \ + gcc-arm-none-eabi \ + gcc-multilib \ + git \ + graphviz \ + libnewlib-arm-none-eabi \ + libclang-rt-21-dev \ + libnotify-dev \ + libtool \ + llvm \ + make \ + patch \ + texlive-fonts-recommended \ + texlive-latex-base \ + texlive-latex-extra \ + texlive-latex-recommended \ + texlive-plain-generic \ + util-linux \ + valgrind \ + zip \ + && rm -rf /var/lib/apt/lists/* diff --git a/.github/docker/wolfsentry-check-all/run-check-all.sh b/.github/docker/wolfsentry-check-all/run-check-all.sh new file mode 100755 index 00000000..4babd38c --- /dev/null +++ b/.github/docker/wolfsentry-check-all/run-check-all.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Usage: run-check-all.sh , from a directory +# holding wolfsentry/, third/ (FreeRTOS, patched lwIP) and wolfssl/. + +set -euo pipefail + +group="$1" +top="$(pwd)" + +git config --global --add safe.directory '*' +mkdir -p "$top/analyzer-root/scripts" +: > "$top/analyzer-root/scripts/analyzer-config.sh" + +cd "$top/wolfsentry" + +case "$group" in +analyze-all) + # Ubuntu packages no big-endian ARM cross toolchain. + targets=$(sed -n 's/^analyze-all: //p' Makefile.analyzers | sed 's/\//') + ;; +check-extra) + # Ubuntu's arm-none-eabi-gcc 14 output exceeds the fixed 230000 byte image limit. + targets=$(sed -n 's/^check-extra: //p' Makefile.analyzers | sed -E 's/\//g') + ;; +*) + targets="$group" + ;; +esac + +# .WAIT is only valid in a prerequisite list, so honor it by running each side separately. +IFS='|' read -r -a phases <<< "${targets//.WAIT/|}" +for phase in "${phases[@]}"; do + # shellcheck disable=SC2086 + make -j"$(nproc)" TMPDIR=/tmp REPO_ROOT="$top/analyzer-root" CLANG_PATH=/usr/bin \ + NOTIFICATION_SANDBOX= UNITTEST_LIST_JSON_DOM_EXTRAS=test_json_corpus $phase +done diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml new file mode 100644 index 00000000..b5b93d72 --- /dev/null +++ b/.github/workflows/check-all.yml @@ -0,0 +1,74 @@ +name: make check-all + +run-name: ${{ inputs.nightly_run_id || github.workflow }} + +on: + push: + branches: [ 'master', 'release/**' ] + pull_request: + branches: [ '**' ] + workflow_dispatch: + inputs: + nightly_run_id: + description: Nightly coordinator run ID + required: false + type: string + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash -e {0} + +jobs: + check-all: + name: ${{ matrix.group }} + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + group: [ analyze-all, check, check-extra ] + + steps: + - uses: actions/checkout@v4 + with: + path: wolfsentry + fetch-depth: 0 + - uses: actions/checkout@v4 + with: + repository: FreeRTOS/FreeRTOS-Kernel + ref: dbf70559b27d39c1fdb68dfb9a32140b6a6777a0 + path: third/FreeRTOS/FreeRTOS/Source + - uses: actions/checkout@v4 + with: + repository: lwip-tcpip/lwip + ref: 3fe8d2fc43a9b69f7ed28c63d44a7744f9c0def9 + path: third/lwip + - uses: actions/checkout@v4 + if: matrix.group == 'check-extra' + with: + repository: wolfSSL/wolfssl + ref: master + path: wolfssl + - name: Apply the lwIP packet filter patch + run: patch --directory third/lwip --strip 1 < wolfsentry/lwip/LWIP_PACKET_FILTER_API.patch + - name: Lower mmap randomization for ThreadSanitizer + run: sudo sysctl -w vm.mmap_rnd_bits=28 + - name: Build the check-all image + run: | + docker build \ + --file wolfsentry/.github/docker/wolfsentry-check-all/Dockerfile \ + --tag wolfsentry-check-all:local wolfsentry + - name: Run ${{ matrix.group }} + run: | + docker run --rm \ + --volume "$GITHUB_WORKSPACE:/workspace" \ + --workdir /workspace \ + wolfsentry-check-all:local \ + wolfsentry/.github/docker/wolfsentry-check-all/run-check-all.sh "${{ matrix.group }}" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9ef5f4f1..a2bff711 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -33,6 +33,7 @@ jobs: const POLL_TIMEOUT_MS = 120 * 60 * 1000; const dispatchToken = `nightly-${context.runId}-${context.runAttempt}`; const workflows = [ + 'check-all.yml', 'codeql.yml', 'coverage.yml', 'examples.yml', From 77250b92d49b5c1dd37d08665027dbf935852c57 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 19:52:25 -0800 Subject: [PATCH 247/255] Retry the lwIP contrib clone in the examples workflow --- .github/workflows/examples.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5020f7f6..5d503190 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -195,7 +195,8 @@ jobs: path: lwip - name: Check out lwIP contrib run: | - git clone --branch STABLE-2_1_0_RELEASE --depth 1 \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry git clone --branch STABLE-2_1_0_RELEASE --depth 1 \ https://git.savannah.nongnu.org/git/lwip/lwip-contrib.git \ lwip/contrib test "$(git -C lwip/contrib rev-parse HEAD)" = \ From 298eed4846468646ca86c08a99ac6b55e9a594c5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 5 Nov 2026 20:11:25 -0800 Subject: [PATCH 248/255] Fix connection accounting, KV replacement, and JSON cleanup leaks --- src/action_builtins.c | 8 +- src/json/centijson_value.c | 53 +++++-------- src/kv.c | 19 ++--- tests/unittests.c | 150 +++++++++++++++++++++++++++++-------- 4 files changed, 151 insertions(+), 79 deletions(-) diff --git a/src/action_builtins.c b/src/action_builtins.c index 9f588139..b4df29c4 100644 --- a/src/action_builtins.c +++ b/src/action_builtins.c @@ -47,11 +47,9 @@ static wolfsentry_errcode_t wolfsentry_builtin_track_peer_count_connect( if (WOLFSENTRY_CHECK_BITS(WOLFSENTRY_ATOMIC_LOAD(route->flags), WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)) WOLFSENTRY_RETURN_OK; - /* dispatch counts connections only under a limit, so match it: an unlimited route must stay at zero. */ - if (config->config.max_connection_count == 0) - WOLFSENTRY_RETURN_OK; - - if (WOLFSENTRY_ATOMIC_LOAD(route->meta.connection_count) >= config->config.max_connection_count) + /* dispatch counts every connection and enforces the limit only when it is nonzero, so match it. */ + if ((config->config.max_connection_count > 0) && + (WOLFSENTRY_ATOMIC_LOAD(route->meta.connection_count) >= config->config.max_connection_count)) WOLFSENTRY_ERROR_RETURN(INCOMPATIBLE_STATE); WOLFSENTRY_ATOMIC_INCREMENT_UNSIGNED_SAFELY_BY_ONE(route->meta.connection_count, new_count); diff --git a/src/json/centijson_value.c b/src/json/centijson_value.c index fada244a..b9d52fa2 100644 --- a/src/json/centijson_value.c +++ b/src/json/centijson_value.c @@ -1852,55 +1852,41 @@ json_value_dict_clean( JSON_VALUE* v) { DICT* d = json_value_dict_payload(v); - RBTREE **stack; - int stack_size; RBTREE* node; - RBTREE* right; + RBTREE* left; int ret; if(d == NULL) return 0; - /* An empty tree needs no stack; a conforming allocator may return NULL for a - * zero-size request, so do not treat that as an error. */ - if(d->root != NULL) { - stack = (RBTREE **)malloc(rbtree_stack_size_needed(d)); - if (stack == NULL) - return JSON_ERR_OUTOFMEMORY; - } else { - stack = NULL; - } - - stack_size = (stack != NULL) ? json_value_dict_leftmost_path(stack, d->root) : 0; - - while(stack_size > 0) { - node = stack[--stack_size]; - right = node->right; + /* Rotate left children up instead of keeping a traversal stack, so cleanup + * cannot fail on allocation; d->root always holds every remaining node. */ + node = d->root; + while(node != NULL) { + if(node->left != NULL) { + left = node->left; + node->left = left->right; + left->right = node; + node = left; + d->root = node; + continue; + } ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->key); - if (ret < 0) { - free((void *)stack); + if (ret < 0) return ret; - } ret = json_value_fini( #ifdef WOLFSENTRY WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), #endif &node->json_value); - if (ret < 0) { - free((void *)stack); + if (ret < 0) return ret; - } - /* unlink before freeing so a failed clean leaves only live nodes for a - * retry; size is kept, as it still bounds the retry's stack */ - if(stack_size > 0) - stack[stack_size - 1]->left = right; - else - d->root = right; + d->root = node->right; if(v->data.data_bytes[0] & HAS_ORDERLIST) { if(node->order_prev != NULL) node->order_prev->order_next = node->order_next; @@ -1912,8 +1898,7 @@ json_value_dict_clean( d->order_tail = node->order_prev; } free(node); - - stack_size += json_value_dict_leftmost_path(stack + stack_size, right); + node = d->root; } if(v->data.data_bytes[0] & HAS_ORDERLIST) @@ -1921,8 +1906,6 @@ json_value_dict_clean( else memset(d, 0, OFFSETOF(DICT, order_head)); - free((void *)stack); - return 0; } /* NOLINTEND(misc-no-recursion) */ @@ -2005,10 +1988,10 @@ json_value_clone(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allo if (ret == 0) { for(i = 0; i < n; i++) { json_value_init_null(&dest_values[i]); + dest_a->size = i + 1; ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &src_values[i], &dest_values[i]); if (ret < 0) break; - dest_a->size = i + 1; } } diff --git a/src/kv.c b/src/kv.c index c5e892a8..c453f2fe 100644 --- a/src/kv.c +++ b/src/kv.c @@ -257,16 +257,17 @@ WOLFSENTRY_LOCAL wolfsentry_errcode_t wolfsentry_kv_set( WOLFSENTRY_UNLOCK_AND_RERETURN_IF_ERROR(ret); WOLFSENTRY_UNLOCK_AND_RETURN_OK; } - if ((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) + /* reserve the replacement ID first, so an ID allocation failure leaves the old entry in place. */ + if ((ret = wolfsentry_id_allocate(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)) < 0) WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); - /* insert the replacement before dropping the old entry, so a failure - * on either step never leaves the key absent; restore the old entry if - * the insert fails. - */ - if ((ret = wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, kv)) < 0) { - /* re-insert with a fresh id, since the delete released the old one; - * drop it if even that fails, so the detached record is not leaked. - */ + if ((ret = wolfsentry_table_ent_delete_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &old->header)) < 0) { + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)); + WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); + } + if ((ret = wolfsentry_table_ent_insert(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header, &kv_table->header, 1 /* unique_p */)) < 0) { + /* the old ID was released by the delete, so restoring needs a fresh one; drop the old entry if that fails. */ + if (kv->header.id != WOLFSENTRY_ENT_ID_NONE) + WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_table_ent_delete_by_id_1(WOLFSENTRY_CONTEXT_ARGS_OUT, &kv->header)); if (wolfsentry_kv_insert_1(WOLFSENTRY_CONTEXT_ARGS_OUT, kv_table, old) < 0) WOLFSENTRY_WARN_ON_FAILURE(wolfsentry_kv_drop_reference(WOLFSENTRY_CONTEXT_ARGS_OUT, old, NULL)); WOLFSENTRY_ERROR_UNLOCK_AND_RERETURN(ret); diff --git a/tests/unittests.c b/tests/unittests.c index b7fb2c64..248a2a32 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -8895,9 +8895,7 @@ static int test_track_peer_connect_count(void) { WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(ephemeral_exports.flags, WOLFSENTRY_ROUTE_FLAG_DONT_COUNT_CURRENT_CONNECTIONS)); WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); - /* with the unlimited setting dispatch never counts connections, so the - * spawning CONNECT must not leave a count a later DISCONNECT can't undo. - */ + /* an unlimited route still counts the spawning CONNECT, so a later DISCONNECT balances it. */ WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_eventconfig_init(wolfsentry, &nolimit_config)); WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_interval_from_seconds(wolfsentry, 60, 0, &nolimit_config.route_idle_time_for_purge)); nolimit_config.max_connection_count = 0; @@ -8940,7 +8938,7 @@ static int test_track_peer_connect_count(void) { WOLFSENTRY_EXIT_ON_TRUE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_REJECT)); WOLFSENTRY_EXIT_ON_FAILURE(test_dynamic_ephemeral_route_exports(WOLFSENTRY_CONTEXT_ARGS_OUT, 3, &ephemeral_exports)); - WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 0); + WOLFSENTRY_EXIT_ON_FALSE(ephemeral_exports.meta.connection_count == 1); action_results = WOLFSENTRY_ACTION_RES_DISCONNECT; WOLFSENTRY_EXIT_ON_FAILURE( @@ -11354,6 +11352,47 @@ static int test_kv_render_nonfinite_float(void) { } #endif /* !WOLFSENTRY_C89 */ +static wolfsentry_errcode_t failing_mk_id_cb(void *context, wolfsentry_ent_id_t *id) { + (void)context; + (void)id; + WOLFSENTRY_ERROR_RETURN(SYS_RESOURCE_FAILED); +} + +static int test_kv_replace_keeps_value_on_id_failure(void) { + struct wolfsentry_context *wolfsentry; + int64_t value = 0; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, 1, 1 /* overwrite_p */)); + + wolfsentry->mk_id_cb = failing_mk_id_cb; + WOLFSENTRY_EXIT_ON_SUCCESS( + wolfsentry_user_value_store_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, 2, 1 /* overwrite_p */)); + wolfsentry->mk_id_cb = NULL; + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_user_value_get_sint( + WOLFSENTRY_CONTEXT_ARGS_OUT, "replaced", WOLFSENTRY_LENGTH_NULL_TERMINATED, &value)); + WOLFSENTRY_EXIT_ON_FALSE(value == 1); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + static int test_kv_empty_key_rejected(void) { struct wolfsentry_context *wolfsentry; @@ -13563,7 +13602,6 @@ static int test_cjvalue_compatible_float_to_int(WOLFSENTRY_CONTEXT_ARGS_IN_EX(st static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) { JSON_VALUE outer; - JSON_VALUE nested_copy; JSON_VALUE *nested; JSON_VALUE *slot; char keybuf[8]; @@ -13588,24 +13626,13 @@ static int test_cjvalue_dict_remove_orphan(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 2); - /* Keep an alias to the nested dictionary so the test can reclaim it after - * removal frees the owning node (see below). */ - nested = json_value_dict_get(&outer, (const unsigned char *)"a"); - WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); - memcpy(&nested_copy, nested, sizeof nested_copy); - - /* Force value finalization (nested dictionary cleanup) to fail during removal. */ + /* Removing a nested dictionary needs no allocation, so it succeeds and frees the subtree with none available. */ test_cjv_budget = 0; ret = json_value_dict_remove_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); test_cjv_budget = -1; - WOLFSENTRY_EXIT_ON_FALSE(ret < 0); - - /* The removed node must not remain counted in the dictionary. */ + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); WOLFSENTRY_EXIT_ON_FALSE(json_value_dict_size(&outer) == 1); - /* Reclaim the nested dictionary, whose finalization the fault interrupted. */ - WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &nested_copy) < 0); - WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); @@ -13635,15 +13662,11 @@ static int test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_IN_E WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, 7) < 0); - /* the earlier keys are freed before the nested dictionary's stack - * allocation fails on the last key. */ - test_cjv_budget = 1; + /* cleanup allocates nothing, so the earlier keys and the nested dictionary are all freed with no budget */ + test_cjv_budget = 0; ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); test_cjv_budget = -1; - WOLFSENTRY_EXIT_ON_FALSE(ret < 0); - - /* a retry must not reach the nodes freed before the failure */ - WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); return 0; @@ -13669,14 +13692,73 @@ static int test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wo WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (int32_t)i) < 0); } - /* First cleanup allocates the outer traversal stack, then fails on the - * nested dictionary's stack allocation. */ - test_cjv_budget = 1; + /* Cleanup allocates nothing, so it must succeed and free every node even + * with no allocation budget left. */ + test_cjv_budget = 0; ret = json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer); test_cjv_budget = -1; - WOLFSENTRY_EXIT_ON_FALSE(ret < 0); + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); + + return 0; +} + +static int test_cjvalue_nested_cleanup_no_leak(WOLFSENTRY_CONTEXT_ARGS_IN_EX(struct wolfsentry_allocator *allocator)) +{ + JSON_VALUE outer; + JSON_VALUE clone; + JSON_VALUE *nested; + JSON_VALUE *arr; + JSON_VALUE *slot; + JSON_VALUE *member; + char keybuf[8]; + size_t live_before; + long budget; + int i; + int ret; + + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); + nested = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a", 1); + WOLFSENTRY_EXIT_ON_TRUE(nested == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested) < 0); + arr = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"b", 1); + WOLFSENTRY_EXIT_ON_TRUE(arr == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_array(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr) < 0); + slot = json_value_array_append(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr); + WOLFSENTRY_EXIT_ON_TRUE(slot == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_dict(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot) < 0); + for (i = 0; i < 4; ++i) { + (void)snprintf(keybuf, sizeof keybuf, "k%03d", i); + member = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), nested, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(member == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), member, (int32_t)i) < 0); + member = json_value_dict_add_(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), slot, (const unsigned char *)keybuf, strlen(keybuf)); + WOLFSENTRY_EXIT_ON_TRUE(member == NULL); + WOLFSENTRY_EXIT_ON_TRUE(json_value_init_int32(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), member, (int32_t)i) < 0); + } + + /* Cloning the array must release everything it built at every possible allocation failure. */ + live_before = test_cjv_live; + for (budget = 0; ; ++budget) { + json_value_init_null(&clone); + test_cjv_budget = budget; + ret = json_value_clone(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), arr, &clone); + test_cjv_budget = -1; + if (ret == 0) { + WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &clone) < 0); + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == live_before); + break; + } + WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == live_before); + WOLFSENTRY_EXIT_ON_TRUE(budget > 64); + } + + /* Removing a nested dictionary must free it even with no allocation budget. */ + test_cjv_budget = 0; + ret = json_value_dict_remove(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer, (const unsigned char *)"a"); + test_cjv_budget = -1; + WOLFSENTRY_EXIT_ON_FALSE(ret == 0); - /* A retry must succeed: no node may have been freed while still linked. */ WOLFSENTRY_EXIT_ON_TRUE(json_value_fini(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(allocator), &outer) < 0); WOLFSENTRY_EXIT_ON_FALSE(test_cjv_live == 0); @@ -13774,6 +13856,9 @@ static wolfsentry_errcode_t test_cjvalue_fixes(void) { test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); + WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_nested_cleanup_no_leak(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); + test_cjv_reset(); WOLFSENTRY_EXIT_ON_TRUE(test_cjvalue_dict_clean_retry_later_node(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&test_allocator)) != 0); @@ -15039,6 +15124,11 @@ int main(int argc, char* argv[]) { err = 1; } #endif /* !WOLFSENTRY_C89 */ + ret = test_kv_replace_keeps_value_on_id_failure(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_kv_replace_keeps_value_on_id_failure failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } ret = test_kv_readonly_overwrite_rejected(); if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { (void)fprintf(stderr, "test_kv_readonly_overwrite_rejected failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); From 019950a36c9e586558ffc2df7ed0fb379b473cac Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 10:56:59 -0800 Subject: [PATCH 249/255] Derive the route parent event check from the aux owner count --- src/routes.c | 3 +- tests/unittests.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/routes.c b/src/routes.c index 70221517..9ccaffed 100644 --- a/src/routes.c +++ b/src/routes.c @@ -1337,9 +1337,10 @@ static wolfsentry_errcode_t wolfsentry_route_insert_1( /* an aux event parents the ephemeral routes it generates, so it keeps its * subevent role rather than taking on the mutually exclusive parent role. + * the owner count is authoritative, since a freed owner leaves the stored mark stale. */ if ((route_to_insert->parent_event != NULL) && - (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_SUBEVENT)) && + (WOLFSENTRY_ATOMIC_LOAD(route_to_insert->parent_event->aux_owner_count) == 0) && (! WOLFSENTRY_CHECK_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT))) { WOLFSENTRY_SET_BITS(route_to_insert->parent_event->flags, WOLFSENTRY_EVENT_FLAG_IS_PARENT_EVENT); diff --git a/tests/unittests.c b/tests/unittests.c index 248a2a32..824840b1 100644 --- a/tests/unittests.c +++ b/tests/unittests.c @@ -10108,6 +10108,94 @@ static int test_event_aux_event_rejects_parent_event(void) { WOLFSENTRY_RETURN_OK; } +static int test_event_former_aux_becomes_route_parent(void) { + struct wolfsentry_context *wolfsentry; + wolfsentry_ent_id_t id; + wolfsentry_action_res_t action_results; + wolfsentry_route_flags_t flags = WOLFSENTRY_ROUTE_FLAG_TCPLIKE_PORT_NUMBERS | WOLFSENTRY_ROUTE_FLAG_DIRECTION_IN; + static const char * const labels[3] = { "former_aux", "first_owner", "later_owner" }; + unsigned int n; + struct { + struct wolfsentry_sockaddr sa; + byte addr_buf[4]; + } remote, local; + + WOLFSENTRY_THREAD_HEADER_CHECKED(WOLFSENTRY_THREAD_FLAG_NONE); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_init_ex( + wolfsentry_build_settings, + WOLFSENTRY_CONTEXT_ARGS_OUT_EX(WOLFSENTRY_TEST_HPI), + NULL /* default_config */, + &wolfsentry, + WOLFSENTRY_INIT_FLAG_NONE)); + + for (n = 0; n < 3; ++n) { + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + labels[n], + WOLFSENTRY_LENGTH_NULL_TERMINATED, + 10, + NULL /* config */, + WOLFSENTRY_EVENT_FLAG_NONE, + &id)); + } + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "first_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + /* freeing the owner drops the aux owner count without clearing the stored subevent mark */ + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_event_delete( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "first_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &action_results)); + WOLFSENTRY_EXIT_ON_FALSE(WOLFSENTRY_CHECK_BITS(action_results, WOLFSENTRY_ACTION_RES_DEALLOCATED)); + + remote.sa.sa_family = local.sa.sa_family = AF_INET; + remote.sa.sa_proto = local.sa.sa_proto = IPPROTO_TCP; + remote.sa.sa_port = 12345; + local.sa.sa_port = 443; + remote.sa.addr_len = local.sa.addr_len = sizeof remote.addr_buf * BITS_PER_BYTE; + remote.sa.interface = local.sa.interface = 1; + memcpy(remote.sa.addr, "\0\1\2\3", sizeof remote.addr_buf); + memcpy(local.sa.addr, "\377\376\375\374", sizeof local.addr_buf); + + WOLFSENTRY_EXIT_ON_FAILURE( + wolfsentry_route_insert( + WOLFSENTRY_CONTEXT_ARGS_OUT, + NULL /* caller_arg */, + &remote.sa, + &local.sa, + flags, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + &id, + &action_results)); + + WOLFSENTRY_EXIT_UNLESS_EXPECTED_FAILURE( + INCOMPATIBLE_STATE, + wolfsentry_event_set_aux_event( + WOLFSENTRY_CONTEXT_ARGS_OUT, + "later_owner", + WOLFSENTRY_LENGTH_NULL_TERMINATED, + "former_aux", + WOLFSENTRY_LENGTH_NULL_TERMINATED)); + + WOLFSENTRY_EXIT_ON_FAILURE(wolfsentry_shutdown(WOLFSENTRY_CONTEXT_ARGS_OUT_EX(&wolfsentry))); + + WOLFSENTRY_EXIT_ON_FAILURE(WOLFSENTRY_THREAD_TAILER(WOLFSENTRY_THREAD_FLAG_NONE)); + + WOLFSENTRY_RETURN_OK; +} + #ifndef WOLFSENTRY_NO_JSON static int event_priority_check_winner(const char *priority_clause, int expect_accept) { @@ -15052,6 +15140,11 @@ int main(int argc, char* argv[]) { (void)fprintf(stderr, "test_event_aux_event_rejects_parent_event failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); err = 1; } + ret = test_event_former_aux_becomes_route_parent(); + if (! WOLFSENTRY_ERROR_CODE_IS(ret, OK)) { + (void)fprintf(stderr, "test_event_former_aux_becomes_route_parent failed, " WOLFSENTRY_ERROR_FMT "\n", WOLFSENTRY_ERROR_FMT_ARGS(ret)); + err = 1; + } #ifndef WOLFSENTRY_NO_JSON ret = test_event_default_priority(); From 22d86451ccee4a82ee9044012ffb7588ed11a04a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 12:02:49 -0800 Subject: [PATCH 250/255] Run the big-endian ARM sanitizer and JSON corpus tests in check-all CI --- .../docker/wolfsentry-check-all/Dockerfile | 23 +++++++++++++++++++ .../wolfsentry-check-all/run-check-all.sh | 14 +++++------ .github/workflows/check-all.yml | 5 ++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/.github/docker/wolfsentry-check-all/Dockerfile b/.github/docker/wolfsentry-check-all/Dockerfile index 19033d68..d67ff46f 100644 --- a/.github/docker/wolfsentry-check-all/Dockerfile +++ b/.github/docker/wolfsentry-check-all/Dockerfile @@ -27,6 +27,7 @@ RUN apt-get update \ llvm \ make \ patch \ + qemu-user \ texlive-fonts-recommended \ texlive-latex-base \ texlive-latex-extra \ @@ -34,5 +35,27 @@ RUN apt-get update \ texlive-plain-generic \ util-linux \ valgrind \ + xz-utils \ zip \ && rm -rf /var/lib/apt/lists/* + +ARG ARMEB_TOOLCHAIN=armebv7-eabihf--glibc--stable-2026.08-1 +ARG ARMEB_TOOLCHAIN_SHA256=4d93a7b26c0909ea7574e98246522cf6b77491d43a7ca7aa08ae1c29ecf07b0d + +# Buildroot's compiler wrapper finds its real binary from argv[0], so expose the +# armeb-linux-gnueabihf names as exec shims, and put the sanitizer runtimes in the sysroot qemu loads from. +RUN curl -fsSL --retry 3 -o /tmp/armeb.tar.xz \ + "https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/${ARMEB_TOOLCHAIN}.tar.xz" \ + && echo "${ARMEB_TOOLCHAIN_SHA256} /tmp/armeb.tar.xz" | sha256sum -c - \ + && tar -xJf /tmp/armeb.tar.xz -C /opt \ + && rm /tmp/armeb.tar.xz \ + && tc="/opt/${ARMEB_TOOLCHAIN}" \ + && target="${tc}/armeb-buildroot-linux-gnueabihf" \ + && for f in "${tc}"/bin/armeb-buildroot-linux-gnueabihf-*; do \ + case "$f" in *.br_real) continue ;; esac; \ + shim="/usr/local/bin/$(basename "$f" | sed 's/^armeb-buildroot-/armeb-/')"; \ + printf '#!/bin/sh\nexec %s "$@"\n' "$f" > "$shim"; \ + chmod +x "$shim"; \ + done \ + && cp -a "${target}"/lib/libasan.so* "${target}"/lib/libubsan.so* "${target}"/lib/libatomic.so* "${target}/sysroot/lib/" \ + && ln -s "${target}/sysroot" /usr/armeb-linux-gnueabihf diff --git a/.github/docker/wolfsentry-check-all/run-check-all.sh b/.github/docker/wolfsentry-check-all/run-check-all.sh index 4babd38c..f5bc7b17 100755 --- a/.github/docker/wolfsentry-check-all/run-check-all.sh +++ b/.github/docker/wolfsentry-check-all/run-check-all.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Usage: run-check-all.sh , from a directory -# holding wolfsentry/, third/ (FreeRTOS, patched lwIP) and wolfssl/. +# Usage: run-check-all.sh , from a directory holding +# wolfsentry/, third/ (FreeRTOS, patched lwIP), wolfssl/ and JSON-Schema-Test-Suite/. set -euo pipefail @@ -11,15 +11,15 @@ git config --global --add safe.directory '*' mkdir -p "$top/analyzer-root/scripts" : > "$top/analyzer-root/scripts/analyzer-config.sh" +if [ -d "$top/JSON-Schema-Test-Suite/tests/latest/." ]; then + export JSON_TEST_CORPUS_DIR="$top/JSON-Schema-Test-Suite/tests/latest/." +fi + cd "$top/wolfsentry" case "$group" in -analyze-all) - # Ubuntu packages no big-endian ARM cross toolchain. - targets=$(sed -n 's/^analyze-all: //p' Makefile.analyzers | sed 's/\//') - ;; check-extra) - # Ubuntu's arm-none-eabi-gcc 14 output exceeds the fixed 230000 byte image limit. + # The FreeRTOS image size limits are tuned to a different arm-none-eabi-gcc than Ubuntu ships. targets=$(sed -n 's/^check-extra: //p' Makefile.analyzers | sed -E 's/\//g') ;; *) diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index b5b93d72..f9082335 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -50,6 +50,11 @@ jobs: repository: lwip-tcpip/lwip ref: 3fe8d2fc43a9b69f7ed28c63d44a7744f9c0def9 path: third/lwip + - uses: actions/checkout@v4 + with: + repository: json-schema-org/JSON-Schema-Test-Suite + ref: 245a45abc90190c0bff04d97e6d2b541b4bb9f12 + path: JSON-Schema-Test-Suite - uses: actions/checkout@v4 if: matrix.group == 'check-extra' with: From 27103effce9f7dca1bfafccc0b6260da3227721c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 12:31:12 -0800 Subject: [PATCH 251/255] Make the FreeRTOS image size limits overridable --- Makefile.analyzers | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Makefile.analyzers b/Makefile.analyzers index a5184265..ed895101 100644 --- a/Makefile.analyzers +++ b/Makefile.analyzers @@ -566,6 +566,9 @@ else LWIP_TOP_FOR_TEST := $(SRC_TOP)/../third/lwip endif +FREERTOS_ARM32_MAX_IMAGE_SIZE ?= 240000 +FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE ?= 110000 + .PHONY: freertos-arm32-thumb-build-test freertos-arm32-thumb-build-test: @[ -d $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source/. ] || ( echo '$@: $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source not found.' >&2; exit 1) @@ -574,16 +577,18 @@ freertos-arm32-thumb-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 240000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_IMAGE_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-thumb-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 FUNCTION_SECTIONS=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 110000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-thumb-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-thumb-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mthumb -mcpu=cortex-m7' UNITTEST_LIST=test_lwip clean @echo "passed: freertos-arm32-thumb-build test." +FREERTOS_ARM32_MAX_SECTIONS_SIZE ?= 100000 + .PHONY: freertos-arm32-build-test freertos-arm32-build-test: @[ -d $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source/. ] || ( echo '$@: $(FREERTOS_TOP_FOR_TEST)/FreeRTOS/Source not found.' >&2; exit 1) @@ -592,12 +597,12 @@ freertos-arm32-build-test: @rm -rf "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" # first build without FUNCTION_SECTIONS=1 to detect any bloat or unresolved symbols anywhere in the library. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 240000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_IMAGE_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3' UNITTEST_LIST=test_lwip clean # now rebuild with FUNCTION_SECTIONS=1, to test expected text size on a parsimonious link. @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3 -specs=nano.specs -Wno-inline' NO_STDIO_STREAMS=1 '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip' UNITTEST_LIST=test_lwip OPTIM='-Os' STRIPPED=1 FUNCTION_SECTIONS=1 - @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt 100000 ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi + @TEST_LWIP_SZ=$$(stat --format='%s' '$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip'); if [[ "$$TEST_LWIP_SZ" -gt $(FREERTOS_ARM32_MAX_SECTIONS_SIZE) ]]; then echo "$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds/tests/test_lwip is unexpectedly large ($$TEST_LWIP_SZ bytes)."; exit 1; fi @rm -rf '$(BUILD_PARENT)/'wolfsentry-freertos-arm32-test-builds/tests/{freertos,lwip} @$(MAKE) $(EXTRA_MAKE_FLAGS) $(QUIET_FLAG) -f $(THIS_MAKEFILE) VERY_QUIET=1 BUILD_TOP="$(BUILD_PARENT)/wolfsentry-freertos-arm32-test-builds" HOST=arm-none-eabi RUNTIME=FreeRTOS-lwIP FREERTOS_TOP=$(FREERTOS_TOP_FOR_TEST) LWIP_TOP=$(LWIP_TOP_FOR_TEST) EXTRA_CFLAGS+='-mcpu=cortex-m3' UNITTEST_LIST=test_lwip clean @echo "passed: freertos-arm32-build test." From 438bc8a42c474aaa4d692c2dd57628aede9462a1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 12:31:12 -0800 Subject: [PATCH 252/255] Run make check-all in CI through the pre-push hook --- .../wolfsentry-check-all/run-check-all.sh | 36 ------------------- .github/workflows/check-all.yml | 29 +++++++++------ 2 files changed, 18 insertions(+), 47 deletions(-) delete mode 100755 .github/docker/wolfsentry-check-all/run-check-all.sh diff --git a/.github/docker/wolfsentry-check-all/run-check-all.sh b/.github/docker/wolfsentry-check-all/run-check-all.sh deleted file mode 100755 index f5bc7b17..00000000 --- a/.github/docker/wolfsentry-check-all/run-check-all.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# Usage: run-check-all.sh , from a directory holding -# wolfsentry/, third/ (FreeRTOS, patched lwIP), wolfssl/ and JSON-Schema-Test-Suite/. - -set -euo pipefail - -group="$1" -top="$(pwd)" - -git config --global --add safe.directory '*' -mkdir -p "$top/analyzer-root/scripts" -: > "$top/analyzer-root/scripts/analyzer-config.sh" - -if [ -d "$top/JSON-Schema-Test-Suite/tests/latest/." ]; then - export JSON_TEST_CORPUS_DIR="$top/JSON-Schema-Test-Suite/tests/latest/." -fi - -cd "$top/wolfsentry" - -case "$group" in -check-extra) - # The FreeRTOS image size limits are tuned to a different arm-none-eabi-gcc than Ubuntu ships. - targets=$(sed -n 's/^check-extra: //p' Makefile.analyzers | sed -E 's/\//g') - ;; -*) - targets="$group" - ;; -esac - -# .WAIT is only valid in a prerequisite list, so honor it by running each side separately. -IFS='|' read -r -a phases <<< "${targets//.WAIT/|}" -for phase in "${phases[@]}"; do - # shellcheck disable=SC2086 - make -j"$(nproc)" TMPDIR=/tmp REPO_ROOT="$top/analyzer-root" CLANG_PATH=/usr/bin \ - NOTIFICATION_SANDBOX= UNITTEST_LIST_JSON_DOM_EXTRAS=test_json_corpus $phase -done diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index f9082335..8ab1d52f 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -27,17 +27,14 @@ defaults: jobs: check-all: - name: ${{ matrix.group }} + name: make check-all runs-on: ubuntu-latest - timeout-minutes: 120 - strategy: - fail-fast: false - matrix: - group: [ analyze-all, check, check-extra ] + timeout-minutes: 240 steps: - uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} path: wolfsentry fetch-depth: 0 - uses: actions/checkout@v4 @@ -54,15 +51,17 @@ jobs: with: repository: json-schema-org/JSON-Schema-Test-Suite ref: 245a45abc90190c0bff04d97e6d2b541b4bb9f12 - path: JSON-Schema-Test-Suite + path: wolfsentry/tests/JSON-Schema-Test-Suite - uses: actions/checkout@v4 - if: matrix.group == 'check-extra' with: repository: wolfSSL/wolfssl ref: master path: wolfssl - name: Apply the lwIP packet filter patch run: patch --directory third/lwip --strip 1 < wolfsentry/lwip/LWIP_PACKET_FILTER_API.patch + # stand-in until the maintainer's local scripts/analyzer-config.sh is available; cppcheck needs the file to exist + - name: Provide scripts/analyzer-config.sh + run: touch wolfsentry/scripts/analyzer-config.sh - name: Lower mmap randomization for ThreadSanitizer run: sudo sysctl -w vm.mmap_rnd_bits=28 - name: Build the check-all image @@ -70,10 +69,18 @@ jobs: docker build \ --file wolfsentry/.github/docker/wolfsentry-check-all/Dockerfile \ --tag wolfsentry-check-all:local wolfsentry - - name: Run ${{ matrix.group }} + # make pre-push-check runs scripts/pre-push.sh, which runs make check-all on this commit. + # The image's gcc 14 arm-none-eabi toolchain needs larger FreeRTOS image limits than the defaults. + - name: make pre-push-check run: | docker run --rm \ --volume "$GITHUB_WORKSPACE:/workspace" \ - --workdir /workspace \ + --workdir /workspace/wolfsentry \ + --env TMPDIR=/tmp \ + --env CLANG_PATH=/usr/bin \ + --env NOTIFICATION_SANDBOX= \ + --env FREERTOS_ARM32_MAX_IMAGE_SIZE=270000 \ + --env FREERTOS_ARM32_MAX_SECTIONS_SIZE=145000 \ + --env FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE=145000 \ wolfsentry-check-all:local \ - wolfsentry/.github/docker/wolfsentry-check-all/run-check-all.sh "${{ matrix.group }}" + bash -ec 'git config --global --add safe.directory "*"; make pre-push-check' From a718ec72fb2ce297e8726cd88c825580f14a5c09 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 12:46:52 -0800 Subject: [PATCH 253/255] Keep the JSON corpus on tmpfs for the qemu armeb tests in CI --- .github/workflows/check-all.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index 8ab1d52f..7e784d11 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -51,7 +51,7 @@ jobs: with: repository: json-schema-org/JSON-Schema-Test-Suite ref: 245a45abc90190c0bff04d97e6d2b541b4bb9f12 - path: wolfsentry/tests/JSON-Schema-Test-Suite + path: JSON-Schema-Test-Suite - uses: actions/checkout@v4 with: repository: wolfSSL/wolfssl @@ -70,12 +70,14 @@ jobs: --file wolfsentry/.github/docker/wolfsentry-check-all/Dockerfile \ --tag wolfsentry-check-all:local wolfsentry # make pre-push-check runs scripts/pre-push.sh, which runs make check-all on this commit. - # The image's gcc 14 arm-none-eabi toolchain needs larger FreeRTOS image limits than the defaults. + # gcc 14 arm-none-eabi needs larger FreeRTOS image limits; the JSON corpus sits on tmpfs because + # qemu-user cannot pass ext4's 64-bit directory cookies to the 32-bit armeb tests. - name: make pre-push-check run: | docker run --rm \ --volume "$GITHUB_WORKSPACE:/workspace" \ --workdir /workspace/wolfsentry \ + --mount type=tmpfs,destination=/workspace/wolfsentry/tests/JSON-Schema-Test-Suite \ --env TMPDIR=/tmp \ --env CLANG_PATH=/usr/bin \ --env NOTIFICATION_SANDBOX= \ @@ -83,4 +85,4 @@ jobs: --env FREERTOS_ARM32_MAX_SECTIONS_SIZE=145000 \ --env FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE=145000 \ wolfsentry-check-all:local \ - bash -ec 'git config --global --add safe.directory "*"; make pre-push-check' + bash -ec 'cp -a /workspace/JSON-Schema-Test-Suite/. tests/JSON-Schema-Test-Suite/; git config --global --add safe.directory "*"; make pre-push-check' From fc39967505de7c3ba8627aeed1fe7c121f62cb94 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 13:05:39 -0800 Subject: [PATCH 254/255] Retry every network fetch in CI and the example images --- .github/docker/wolfsentry-check-all/Dockerfile | 4 +++- .github/docker/wolfsentry-ci/Dockerfile | 2 ++ .github/workflows/check-all.yml | 3 ++- .github/workflows/examples.yml | 10 +++++++--- .github/workflows/wolfssl-versions.yml | 10 ++++++---- examples/Linux-LWIP/Dockerfile | 7 +++++-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/docker/wolfsentry-check-all/Dockerfile b/.github/docker/wolfsentry-check-all/Dockerfile index d67ff46f..fdce8d16 100644 --- a/.github/docker/wolfsentry-check-all/Dockerfile +++ b/.github/docker/wolfsentry-check-all/Dockerfile @@ -2,6 +2,8 @@ FROM ubuntu:26.04@sha256:157dc3d6e4a3e4f2c68d12df9df9ed6e808b59af6d4c3757b432b4f ARG DEBIAN_FRONTEND=noninteractive +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + RUN apt-get update \ && apt-get install -y --no-install-recommends \ autoconf \ @@ -44,7 +46,7 @@ ARG ARMEB_TOOLCHAIN_SHA256=4d93a7b26c0909ea7574e98246522cf6b77491d43a7ca7aa08ae1 # Buildroot's compiler wrapper finds its real binary from argv[0], so expose the # armeb-linux-gnueabihf names as exec shims, and put the sanitizer runtimes in the sysroot qemu loads from. -RUN curl -fsSL --retry 3 -o /tmp/armeb.tar.xz \ +RUN curl -fsSL --retry 5 --retry-all-errors -o /tmp/armeb.tar.xz \ "https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/${ARMEB_TOOLCHAIN}.tar.xz" \ && echo "${ARMEB_TOOLCHAIN_SHA256} /tmp/armeb.tar.xz" | sha256sum -c - \ && tar -xJf /tmp/armeb.tar.xz -C /opt \ diff --git a/.github/docker/wolfsentry-ci/Dockerfile b/.github/docker/wolfsentry-ci/Dockerfile index d864936d..91643186 100644 --- a/.github/docker/wolfsentry-ci/Dockerfile +++ b/.github/docker/wolfsentry-ci/Dockerfile @@ -1,5 +1,7 @@ FROM ghcr.io/wolfssl/wolfcose-ci@sha256:c073c7afb4cd3f296b8c1a305db2f718530b7a8aca899482629aee3a5cf8bfbc +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + RUN apt-get update \ && apt-get install -y --no-install-recommends \ bubblewrap \ diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index 7e784d11..fa9dee50 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -66,7 +66,8 @@ jobs: run: sudo sysctl -w vm.mmap_rnd_bits=28 - name: Build the check-all image run: | - docker build \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker build \ --file wolfsentry/.github/docker/wolfsentry-check-all/Dockerfile \ --tag wolfsentry-check-all:local wolfsentry # make pre-push-check runs scripts/pre-push.sh, which runs make check-all on this commit. diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 5d503190..19bc7606 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -36,7 +36,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and start the lwIP network - run: docker compose up --build --detach + run: | + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker compose up --build --detach - name: Exercise allowed and rejected traffic run: | for attempt in $(seq 1 30); do @@ -94,7 +96,8 @@ jobs: path: wolfip - name: Build the wolfSentry CI image run: | - docker build \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker build \ --file wolfsentry/.github/docker/wolfsentry-ci/Dockerfile \ --tag wolfsentry-ci:local wolfsentry - name: Build and run the wolfIP example @@ -146,7 +149,8 @@ jobs: path: wolfssl - name: Build the wolfSentry CI image run: | - docker build \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry docker build \ --file wolfsentry/.github/docker/wolfsentry-ci/Dockerfile \ --tag wolfsentry-ci:local wolfsentry - name: Build and exercise the notification demo diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 3d9f8787..ed46b96f 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -37,9 +37,10 @@ jobs: - name: Build matrix id: versions run: | - LATEST=$(git ls-remote --tags --refs \ - https://github.com/wolfSSL/wolfssl.git 'v*-stable' \ - | awk -F/ '{print $NF}' | sort -V | tail -n 1) + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + TAGS=$(retry git ls-remote --tags --refs \ + https://github.com/wolfSSL/wolfssl.git 'v*-stable') + LATEST=$(awk -F/ '{print $NF}' <<<"$TAGS" | sort -V | tail -n 1) test -n "$LATEST" if [[ ! "$LATEST" =~ ^v[0-9]+(\.[0-9]+)+-stable$ ]]; then echo "Unexpected wolfSSL stable tag: $LATEST" >&2 @@ -83,7 +84,8 @@ jobs: env: WOLFSSL_REF: ${{ matrix.ref }} run: | - git clone --depth 1 --branch "$WOLFSSL_REF" \ + retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } + retry git clone --depth 1 --branch "$WOLFSSL_REF" \ https://github.com/wolfSSL/wolfssl.git "$HOME/wolfssl" cd "$HOME/wolfssl" ./autogen.sh diff --git a/examples/Linux-LWIP/Dockerfile b/examples/Linux-LWIP/Dockerfile index 372da55a..4bc61b62 100644 --- a/examples/Linux-LWIP/Dockerfile +++ b/examples/Linux-LWIP/Dockerfile @@ -1,5 +1,7 @@ FROM debian:12 AS builder +RUN echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries + RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential ca-certificates cmake git libpcap-dev netbase patch \ && rm -rf /var/lib/apt/lists/* @@ -8,9 +10,10 @@ WORKDIR /src COPY . wolfsentry -RUN git clone --depth 1 --branch STABLE-2_1_3_RELEASE \ +RUN retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } \ + && retry git clone --depth 1 --branch STABLE-2_1_3_RELEASE \ https://github.com/lwip-tcpip/lwip.git \ - && git clone --depth 1 --branch STABLE-2_1_0_RELEASE \ + && retry git clone --depth 1 --branch STABLE-2_1_0_RELEASE \ https://git.savannah.nongnu.org/git/lwip/lwip-contrib.git \ lwip/contrib \ && cd lwip \ From 727679fbe9f0f1f81a68bffef702ac3f422065a6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 6 Nov 2026 13:25:02 -0800 Subject: [PATCH 255/255] Pull the published check-all image in CI instead of building it --- .github/workflows/check-all.yml | 11 +++-- .github/workflows/publish-ci-image.yml | 66 ++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-all.yml b/.github/workflows/check-all.yml index fa9dee50..e362d20e 100644 --- a/.github/workflows/check-all.yml +++ b/.github/workflows/check-all.yml @@ -30,6 +30,9 @@ jobs: name: make check-all runs-on: ubuntu-latest timeout-minutes: 240 + env: + # built from .github/docker/wolfsentry-check-all by publish-ci-image.yml + CHECK_ALL_IMAGE: ghcr.io/wolfssl/wolfsentry-check-all@sha256:99be2efa32df4ea09a87706286b914e949e4b636787759dd4ae806f4b1aaae84 steps: - uses: actions/checkout@v4 @@ -64,12 +67,10 @@ jobs: run: touch wolfsentry/scripts/analyzer-config.sh - name: Lower mmap randomization for ThreadSanitizer run: sudo sysctl -w vm.mmap_rnd_bits=28 - - name: Build the check-all image + - name: Pull the check-all image run: | retry() { for attempt in 1 2 3; do "$@" && return 0; [ "$attempt" -lt 3 ] && sleep $((attempt * 15)); done; return 1; } - retry docker build \ - --file wolfsentry/.github/docker/wolfsentry-check-all/Dockerfile \ - --tag wolfsentry-check-all:local wolfsentry + retry docker pull "$CHECK_ALL_IMAGE" # make pre-push-check runs scripts/pre-push.sh, which runs make check-all on this commit. # gcc 14 arm-none-eabi needs larger FreeRTOS image limits; the JSON corpus sits on tmpfs because # qemu-user cannot pass ext4's 64-bit directory cookies to the 32-bit armeb tests. @@ -85,5 +86,5 @@ jobs: --env FREERTOS_ARM32_MAX_IMAGE_SIZE=270000 \ --env FREERTOS_ARM32_MAX_SECTIONS_SIZE=145000 \ --env FREERTOS_ARM32_THUMB_MAX_SECTIONS_SIZE=145000 \ - wolfsentry-check-all:local \ + "$CHECK_ALL_IMAGE" \ bash -ec 'cp -a /workspace/JSON-Schema-Test-Suite/. tests/JSON-Schema-Test-Suite/; git config --global --add safe.directory "*"; make pre-push-check' diff --git a/.github/workflows/publish-ci-image.yml b/.github/workflows/publish-ci-image.yml index 0ea0986c..85f8fb30 100644 --- a/.github/workflows/publish-ci-image.yml +++ b/.github/workflows/publish-ci-image.yml @@ -5,6 +5,7 @@ on: branches: [ '**' ] paths: - '.github/docker/wolfsentry-ci/**' + - '.github/docker/wolfsentry-check-all/**' - '.github/workflows/publish-ci-image.yml' workflow_dispatch: @@ -80,3 +81,68 @@ jobs: scan-build --help > /dev/null /usr/bin/python3 -c 'import dbus, gi' make -j2 test + + publish-check-all: + name: Publish check-all image + if: github.repository == 'wolfSSL/wolfsentry' + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + packages: write + outputs: + digest: ${{ steps.build.outputs.digest }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push image + id: build + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + file: .github/docker/wolfsentry-check-all/Dockerfile + platforms: linux/amd64 + pull: true + push: true + tags: | + ghcr.io/wolfssl/wolfsentry-check-all:sha-${{ github.sha }} + ghcr.io/wolfssl/wolfsentry-check-all:${{ github.ref == 'refs/heads/master' && 'latest' || 'edge' }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + cache-from: type=registry,ref=ghcr.io/wolfssl/wolfsentry-check-all:latest + cache-to: type=inline + + smoke-check-all: + name: Test published check-all image + needs: publish-check-all + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: read + container: + image: ghcr.io/wolfssl/wolfsentry-check-all@${{ needs.publish-check-all.outputs.digest }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + defaults: + run: + shell: bash -e {0} + steps: + - name: Exercise image + run: | + echo "digest: ${{ needs.publish-check-all.outputs.digest }}" + gcc-15 --version + clang --version + clang-tidy --version + cppcheck --version + arm-none-eabi-gcc --version + armeb-linux-gnueabihf-gcc --version + qemu-armeb --version + test -e /usr/armeb-linux-gnueabihf/lib/libasan.so.8