Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kronosnet for openSUSE:Factory checked in at 2026-07-23 23:09:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kronosnet (Old) and /work/SRC/openSUSE:Factory/.kronosnet.new.2004 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kronosnet" Thu Jul 23 23:09:13 2026 rev:11 rq:1367089 version:1.33 Changes: -------- --- /work/SRC/openSUSE:Factory/kronosnet/kronosnet.changes 2026-02-02 14:55:42.983842265 +0100 +++ /work/SRC/openSUSE:Factory/.kronosnet.new.2004/kronosnet.changes 2026-07-23 23:10:03.067673107 +0200 @@ -1,0 +2,14 @@ +Tue Jul 21 08:25:57 UTC 2026 - Nicholas Yang <[email protected]> + +- add a patch to fix CVE-2026-15811: + * 0001-CVE-2026-15811-encryption-key-exposure-in-memory-aft.patch +- add a patch to fix CVE-2026-15812: + * 0002-CVE-2026-15812-access-control-list-bypass-via-link-I.patch +- add a patch to fix CVE-2026-15813: + * 0003-CVE-2026-15813-memory-corruption-and-out-of-bounds-a.patch +- add patches to make the tests of previous fixes run on 1.33: + * 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch + * 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch + * adapt-backported-tests-to-1.33.patch + +------------------------------------------------------------------- New: ---- 0001-CVE-2026-15811-encryption-key-exposure-in-memory-aft.patch 0002-CVE-2026-15812-access-control-list-bypass-via-link-I.patch 0003-CVE-2026-15813-memory-corruption-and-out-of-bounds-a.patch 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch adapt-backported-tests-to-1.33.patch ----------(New B)---------- New:- add a patch to fix CVE-2026-15811: * 0001-CVE-2026-15811-encryption-key-exposure-in-memory-aft.patch - add a patch to fix CVE-2026-15812: New:- add a patch to fix CVE-2026-15812: * 0002-CVE-2026-15812-access-control-list-bypass-via-link-I.patch - add a patch to fix CVE-2026-15813: New:- add a patch to fix CVE-2026-15813: * 0003-CVE-2026-15813-memory-corruption-and-out-of-bounds-a.patch - add patches to make the tests of previous fixes run on 1.33: New:- add patches to make the tests of previous fixes run on 1.33: * 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch * 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch New: * 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch * 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch * adapt-backported-tests-to-1.33.patch New: * 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch * adapt-backported-tests-to-1.33.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kronosnet.spec ++++++ --- /var/tmp/diff_new_pack.OhgQKm/_old 2026-07-23 23:10:03.839700215 +0200 +++ /var/tmp/diff_new_pack.OhgQKm/_new 2026-07-23 23:10:03.847700496 +0200 @@ -111,6 +111,12 @@ Group: Productivity/Clustering/HA URL: https://kronosnet.org Source0: https://kronosnet.org/releases/kronosnet-%{version}.tar.xz +Patch0: 0001-CVE-2026-15811-encryption-key-exposure-in-memory-aft.patch +Patch1: 0002-CVE-2026-15812-access-control-list-bypass-via-link-I.patch +Patch2: 0003-CVE-2026-15813-memory-corruption-and-out-of-bounds-a.patch +Patch3: 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch +Patch4: 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch +Patch5: adapt-backported-tests-to-1.33.patch ## Setup/build bits # Build dependencies @@ -166,7 +172,7 @@ %endif %prep -%setup -q -n %{name}-%{version}%{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}} +%autosetup -p1 -n %{name}-%{version}%{?numcomm:.%{numcomm}}%{?alphatag:-%{alphatag}}%{?dirty:-%{dirty}} %build %if %{with runautogen} ++++++ 0001-CVE-2026-15811-encryption-key-exposure-in-memory-aft.patch ++++++ >From f51260301717d7345b3ab941ddd6022005d39dcd Mon Sep 17 00:00:00 2001 From: "Fabio M. Di Nitto" <[email protected]> Date: Mon, 20 Apr 2026 09:17:08 +0200 Subject: [PATCH 1/5] CVE-2026-15811: encryption key exposure in memory after cryptographic configuration changes Severity: LOW Wipe cryptographic keys before freeing to prevent exposure through memory disclosure vulnerabilities using explicit_bzero(). Wipe private_key buffers in openssl/gcrypt backends and instance structures in all backends. The explicit_bzero() function is now required by kronosnet. All modern Linux distributions (glibc >= 2.25, musl >= 1.1.20) and BSDs provide this function. Signed-off-by: Fabio M. Di Nitto <[email protected]> Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- configure.ac | 6 ++++++ libknet/crypto_nss.c | 7 +++++++ libknet/crypto_openssl.c | 9 +++++++++ 3 files changed, 22 insertions(+) diff --git a/configure.ac b/configure.ac index e7105fef..86762702 100644 --- a/configure.ac +++ b/configure.ac @@ -277,6 +277,12 @@ if test "x$enable_libknet_sctp" = xyes; then ) fi +AC_CHECK_FUNCS([explicit_bzero]) +# explicit_bzero is required for secure memory wiping +if test "x$ac_cv_func_explicit_bzero" = xno; then + AC_MSG_ERROR([explicit_bzero is required but not available on this system]) +fi + # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_PID_T diff --git a/libknet/crypto_nss.c b/libknet/crypto_nss.c index 6abe35e4..ca107234 100644 --- a/libknet/crypto_nss.c +++ b/libknet/crypto_nss.c @@ -11,6 +11,7 @@ #include <errno.h> #include <stdlib.h> +#include <string.h> #include <nss.h> #include <nspr.h> #include <pk11pub.h> @@ -776,6 +777,12 @@ static void nsscrypto_fini( PK11_FreeSymKey(nsscrypto_instance->nss_sym_key_sign); nsscrypto_instance->nss_sym_key_sign = NULL; } + /* + * Wipe the instance structure itself before freeing. + * PK11_FreeSymKey() already handles secure wiping of key material, + * but we still wipe metadata like key lengths and cipher types. + */ + explicit_bzero(nsscrypto_instance, sizeof(struct nsscrypto_instance)); free(nsscrypto_instance); crypto_instance->model_instance = NULL; } diff --git a/libknet/crypto_openssl.c b/libknet/crypto_openssl.c index 05cdd0a2..94709179 100644 --- a/libknet/crypto_openssl.c +++ b/libknet/crypto_openssl.c @@ -597,6 +597,11 @@ static void opensslcrypto_fini( if (opensslcrypto_instance) { if (opensslcrypto_instance->private_key) { + /* + * Securely wipe private key from memory before freeing + */ + explicit_bzero(opensslcrypto_instance->private_key, + opensslcrypto_instance->private_key_len); free(opensslcrypto_instance->private_key); opensslcrypto_instance->private_key = NULL; } @@ -605,6 +610,10 @@ static void opensslcrypto_fini( EVP_MAC_free(opensslcrypto_instance->crypto_hash_mac); } #endif + /* + * Wipe the instance structure itself before freeing + */ + explicit_bzero(opensslcrypto_instance, sizeof(struct opensslcrypto_instance)); free(opensslcrypto_instance); crypto_instance->model_instance = NULL; } -- 2.55.0 ++++++ 0002-CVE-2026-15812-access-control-list-bypass-via-link-I.patch ++++++ >From 2a09f2d044685d96e092842e1fe54a58bc773773 Mon Sep 17 00:00:00 2001 From: "Fabio M. Di Nitto" <[email protected]> Date: Thu, 7 May 2026 07:13:22 +0200 Subject: [PATCH 2/5] CVE-2026-15812: access control list bypass via link ID spoofing on unencrypted dynamic links Severity: LOW Exploitable only with unencrypted traffic (not supported for production). Validate that packets originate from the configured source address for the claimed link_id. Previously, link_id was trusted without verification, allowing attackers to probe link states or confuse failover logic. Implement secure-by-default model with proper static/dynamic link handling: - Enable use_access_lists=1 by default in knet_handle_new - Static links: always validate against auto-configured ACL - Dynamic links with use_access_lists=1 (default): require ACL or reject - Dynamic links with use_access_lists=0: skip validation (explicit opt-out) Add bounds check, source address verification, and centralized _check_rx_acl(). Update tests to reflect secure-by-default model. Signed-off-by: Fabio M. Di Nitto <[email protected]> Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- libknet/handle.c | 6 + libknet/tests/Makefile.am | 14 +- libknet/tests/fun_acl_check.c | 22 +- libknet/tests/sec_acl_link_id_spoofing.c | 269 +++++++++++++++++++++++ libknet/threads_rx.c | 87 +++++++- 5 files changed, 384 insertions(+), 14 deletions(-) create mode 100644 libknet/tests/sec_acl_link_id_spoofing.c diff --git a/libknet/handle.c b/libknet/handle.c index 72cf2063..2eef9af3 100644 --- a/libknet/handle.c +++ b/libknet/handle.c @@ -602,6 +602,12 @@ knet_handle_t knet_handle_new_ex(knet_node_id_t host_id, knet_h->stats.tx_crypt_time_min = UINT64_MAX; knet_h->stats.rx_crypt_time_min = UINT64_MAX; + /* + * Enable ACL validation by default for security (CVE-2026-15812) + * Users can explicitly disable with knet_handle_enable_access_lists(0) + */ + knet_h->use_access_lists = 1; + /* * init global shared bits */ diff --git a/libknet/tests/Makefile.am b/libknet/tests/Makefile.am index b10ba8b1..4f42d340 100644 --- a/libknet/tests/Makefile.am +++ b/libknet/tests/Makefile.am @@ -29,7 +29,8 @@ noinst_HEADERS = \ check_PROGRAMS = \ $(api_checks) \ - $(int_checks) + $(int_checks) \ + $(sec_checks) if RUN_FUN_TESTS check_PROGRAMS += $(fun_checks) @@ -39,6 +40,9 @@ int_checks = \ int_links_acl_ip_test \ int_timediff_test +sec_checks = \ + sec_acl_link_id_spoofing_test + fun_checks = \ fun_config_crypto_test \ fun_acl_check_test @@ -59,7 +63,8 @@ noinst_PROGRAMS = \ $(long_run_checks) \ $(api_checks) \ $(int_checks) \ - $(fun_checks) + $(fun_checks) \ + $(sec_checks) noinst_SCRIPTS = \ api-test-coverage @@ -120,3 +125,8 @@ fun_config_crypto_test_SOURCES = fun_config_crypto.c \ fun_acl_check_test_SOURCES = fun_acl_check.c \ test-common.c + +sec_acl_link_id_spoofing_test_SOURCES = sec_acl_link_id_spoofing.c \ + test-common.c + +CLEANFILES = knet_test_unix_stream.* diff --git a/libknet/tests/fun_acl_check.c b/libknet/tests/fun_acl_check.c index 929b5319..b566578c 100644 --- a/libknet/tests/fun_acl_check.c +++ b/libknet/tests/fun_acl_check.c @@ -263,6 +263,13 @@ static void test(int transport) start_logthread(logfds[1], stdout); knet_handle_start_nodes(knet_h, TESTNODES, logfds, KNET_LOG_DEBUG); + /* + * Disable use_access_lists initially (CVE-2026-15812 changed default to enabled) + * This test explicitly enables ACLs later to test ACL functionality + */ + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 0)); + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[2], 0)); + FAIL_ON_ERR_THR(knet_host_add(knet_h[2], 1)); FAIL_ON_ERR_THR(knet_host_add(knet_h[1], 2)); @@ -324,6 +331,8 @@ static void test(int transport) // Unblock and check again FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 0, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 0, seconds, logfds[0], stdout)); + // Disable ACL validation temporarily while nodes reconnect after ACL change + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 0)); FAIL_ON_ERR_THR(knet_link_rm_acl(knet_h[1], 2, 0, &ss1, NULL, CHECK_TYPE_ADDRESS, CHECK_REJECT)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 1, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 1, seconds, logfds[0], stdout)); @@ -332,6 +341,9 @@ static void test(int transport) FAIL_ON_ERR_THR(knet_send_str(knet_h[2], "1Address unblocked - this should get through")); FAIL_ON_ERR_THR(wait_for_reply(seconds)); + // Re-enable ACL validation for next test + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 1)); + // Block traffic using a netmask knet_strtoaddr("127.0.0.1","0", &ss1, sizeof(ss1)); knet_strtoaddr("255.0.0.1","0", &ss2, sizeof(ss2)); @@ -343,6 +355,8 @@ static void test(int transport) // Unblock and check again FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 0, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 0, seconds, logfds[0], stdout)); + // Disable ACL validation temporarily while nodes reconnect after ACL change + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 0)); FAIL_ON_ERR_THR(knet_link_rm_acl(knet_h[1], 2, 0, &ss1, &ss2, CHECK_TYPE_MASK, CHECK_REJECT)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 1, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 1, seconds, logfds[0], stdout)); @@ -351,6 +365,9 @@ static void test(int transport) FAIL_ON_ERR_THR(knet_send_str(knet_h[2], "1Netmask unblocked - this should get through")); FAIL_ON_ERR_THR(wait_for_reply(seconds)); + // Re-enable ACL validation for next test + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 1)); + // Block traffic from a range knet_strtoaddr("127.0.0.0", "0", &ss1, sizeof(ss1)); knet_strtoaddr("127.0.0.9", "0", &ss2, sizeof(ss2)); @@ -362,6 +379,8 @@ static void test(int transport) // Unblock and check again FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 0, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 0, seconds, logfds[0], stdout)); + // Disable ACL validation temporarily while nodes reconnect after ACL change + FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 0)); FAIL_ON_ERR_THR(knet_link_rm_acl(knet_h[1], 2, 0, &ss1, &ss2, CHECK_TYPE_RANGE, CHECK_REJECT)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 1, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 1, seconds, logfds[0], stdout)); @@ -370,7 +389,8 @@ static void test(int transport) FAIL_ON_ERR_THR(knet_send_str(knet_h[2], "1Range unblocked - this should get through")); FAIL_ON_ERR_THR(wait_for_reply(seconds)); - // Finish up - disable ACLS to make sure the QUIT message gets through + // Finish up - disable ACLs to make sure messages get through + // With secure-by-default (use_access_lists=1), need to disable for dynamic link without ACL FAIL_ON_ERR_THR(knet_handle_enable_access_lists(knet_h[1], 0)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[1], TESTNODES, 1, seconds, logfds[0], stdout)); FAIL_ON_ERR_THR(wait_for_nodes_state(knet_h[2], TESTNODES, 1, seconds, logfds[0], stdout)); diff --git a/libknet/tests/sec_acl_link_id_spoofing.c b/libknet/tests/sec_acl_link_id_spoofing.c new file mode 100644 index 00000000..57c050c4 --- /dev/null +++ b/libknet/tests/sec_acl_link_id_spoofing.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2026 Red Hat, Inc. All rights reserved. + * + * Authors: Fabio M. Di Nitto <[email protected]> + * + * This software licensed under GPL-2.0+ + */ + +#include "config.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <time.h> + +#include "libknet.h" +#include "internals.h" +#include "onwire.h" +#include "test-common.h" + +#define TEST_NAME "sec_acl_link_id_spoofing" + +/* + * Regression test for CVE-2026-15812: ACL bypass via link ID spoofing + * + * Validates that knet properly rejects ping packets where the link_id + * in the packet doesn't match the source address, preventing ACL bypass. + * + * Security model (as of CVE-2026-15812 fix): + * - use_access_lists is enabled by default (secure by default) + * - Static links: always validate against auto-configured ACL + * - Dynamic links: + * - If use_access_lists = 1 (default): require ACL configuration + * - If use_access_lists = 0: skip validation (user explicit opt-out) + * + * Attack scenarios tested (using static links): + * 1. ACL bypass: Packet from link 0 claiming link_id=1 (different configured link) + * - Both links configured with different addresses + * - Packet passes bounds/configured checks + * - ACL validation rejects because source doesn't match link 1's expected address + * 2. Unconfigured link_id: Packet claims link_id=2 (not configured) + * - Rejected by configured state check + * 3. Out-of-bounds link_id: Packet claims link_id >= KNET_MAX_LINK + * - Rejected by bounds check + * + * Test approach: + * - Configure two static links with different addresses to same host + * - Inject packets with various spoofed link_id values + * - Verify each attack is rejected with appropriate log message + * - Verify use_access_lists defaults to enabled + */ + +static int private_data; + +static void sock_notify(void *pvt_data, + int datafd, + int8_t channel, + uint8_t tx_rx, + int error, + int errorno) +{ + return; +} + +/* + * Log filter callback to check for packet rejection due to ACL failure + * This catches link ID spoofing where source address doesn't match claimed link + */ +static int filter_packet_rejected(int logfd, const char *log_line, void *private_data) +{ + (void)logfd; + (void)private_data; + + if (strstr(log_line, "Packet rejected")) { + return 1; + } + return 0; +} + +/* + * Log filter callback to check for invalid link_id message + */ +static int filter_invalid_link_id(int logfd, const char *log_line, void *private_data) +{ + (void)logfd; + (void)private_data; + + if (strstr(log_line, "Invalid link_id")) { + return 1; + } + return 0; +} + +static void test_link_id_spoofing(void) +{ + knet_handle_t knet_h1, knet_h[2] = {0}; + int logfd; + int datafd = 0; + int8_t channel = 0; + seq_num_t seq_num = 2000; + + logfd = start_logging(stdout); + + log_test(logfd, "Test: CVE-2026-15812 Link ID spoofing prevention (secure by default)"); + + /* + * Set up knet handle + */ + knet_h1 = _ts_knet_handle_start(logfd, KNET_LOG_DEBUG, knet_h); + + FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h1, &private_data, sock_notify)); + FAIL_ON_ERR(knet_handle_add_datafd(knet_h1, &datafd, &channel)); + FAIL_ON_ERR(knet_host_add(knet_h1, 1)); + + /* + * Configure two STATIC links to the same remote host with DIFFERENT IP addresses + * Link 0: 127.0.0.1 + * Link 1: 127.0.0.2 + * This allows ACL to distinguish between links (ACL checks IP, not port) + * + * Note: Static links automatically get their dst_addr added to ACL during + * knet_link_set_config(), and static links ALWAYS validate ACL regardless + * of use_access_lists setting. + * + * With the CVE-2026-15812 fix, use_access_lists defaults to 1, + * which means dynamic links without ACL would also be rejected. + */ + log_test(logfd, "Step 1: Configure two static links to same host (different IPs)"); + + struct sockaddr_storage lo0, lo1; + + /* Allocate dynamic port for link 0, use 127.0.0.1 */ + FAIL_ON_ERR(make_local_sockaddr(&lo0, 0, logfd)); + FAIL_ON_ERR(knet_link_set_config(knet_h1, 1, 0, KNET_TRANSPORT_UDP, &lo0, &lo0, 0)); + log_test(logfd, " Link 0 configured (127.0.0.1)"); + + /* Allocate dynamic port for link 1, change IP to 127.0.0.2 */ + FAIL_ON_ERR(make_local_sockaddr(&lo1, 1, logfd)); + ((struct sockaddr_in *)&lo1)->sin_addr.s_addr = htonl(0x7f000002); /* 127.0.0.2 */ + if (knet_link_set_config(knet_h1, 1, 1, KNET_TRANSPORT_UDP, &lo1, &lo1, 0) < 0) { + log_test(logfd, "Cannot bind 127.0.0.2 (platform lacks full 127/8 support), skipping test"); + _ts_knet_handle_stop_everything(knet_h, 1, logfd); + TEST_EXIT(PASS); + } + log_test(logfd, " Link 1 configured (127.0.0.2)"); + + log_test(logfd, "Two static links configured to same host with different IPs"); + + /* + * Test Case 1: ACL bypass via link ID spoofing + * Send ping from link 0's address claiming to be link 1 + * Link 1 IS configured but expects packets from a different address + * ACL check should reject because source doesn't match link 1's expected address + */ + log_test(logfd, "Step 2: Attack - ACL bypass via link ID spoofing"); + log_test(logfd, " Send from link 0 address claiming to be link 1"); + log_test(logfd, " Link 1 expects different address - should fail ACL check"); + + /* Install filter for packet rejection due to ACL */ + install_log_filter(logfd, filter_packet_rejected, NULL); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 1, 0, 0, seq_num, 0, NULL, 0) < 0) { + log_test(logfd, "*** FAIL: Failed to inject ACL bypass packet"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 2); /* Give RX thread time to process */ + + log_test(logfd, "Spoofed link_id packet sent"); + + log_test(logfd, "Step 3: Verify ACL rejected the packet"); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: ACL did not reject spoofed packet"); + log_test(logfd, " Expected: 'Packet rejected from'"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + log_test(logfd, "ACL correctly rejected spoofed link_id packet"); + + /* + * Test Case 2: Send ping with unconfigured link_id (link 2 not configured) + * Source address is known (link 0) but link_id in packet is not configured + */ + log_test(logfd, "Step 4: Attack - Unconfigured link_id in packet"); + log_test(logfd, " link_id in packet: 2 (not configured)"); + + /* Switch to invalid link_id filter */ + install_log_filter(logfd, filter_invalid_link_id, NULL); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 2, 0, 0, seq_num + 1, 0, NULL, 0) < 0) { + log_test(logfd, "*** FAIL: Failed to inject unconfigured link_id packet"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 2); + + log_test(logfd, "Unconfigured link_id packet sent"); + + log_test(logfd, "Step 5: Verify log contains invalid link_id warning"); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: Log does not contain expected invalid link_id warning"); + log_test(logfd, " Expected: 'Invalid link_id'"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + log_test(logfd, "Log contains invalid link_id rejection message"); + + /* + * Test Case 3: Send ping with out-of-bounds link_id + */ + log_test(logfd, "Step 6: Attack - Out-of-bounds link_id"); + log_test(logfd, " link_id in packet: %u (>= KNET_MAX_LINK)", KNET_MAX_LINK); + + /* Keep invalid link_id filter active */ + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, KNET_MAX_LINK, 0, 0, seq_num + 2, 0, NULL, 0) < 0) { + log_test(logfd, "*** FAIL: Failed to inject out-of-bounds ping packet"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 2); + + log_test(logfd, "Out-of-bounds link_id packet sent"); + + log_test(logfd, "Step 7: Verify log contains invalid link_id warning"); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: Log does not contain expected invalid link_id warning"); + log_test(logfd, " Expected: 'Invalid link_id'"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + log_test(logfd, "Log contains out-of-bounds rejection message"); + + /* Remove filter */ + install_log_filter(logfd, NULL, NULL); + + log_test(logfd, "=== CVE-2026-15812 Link ID spoofing prevention test PASSED ==="); + log_test(logfd, "ACL bypass via link_id spoofing prevented"); + log_test(logfd, "Unconfigured link_id rejected"); + log_test(logfd, "Out-of-bounds link_id rejected"); + log_test(logfd, "All attack vectors blocked"); + log_test(logfd, "Note: use_access_lists is now enabled by default (secure by default)"); + + _ts_knet_handle_stop_everything(knet_h, 1, logfd); + + TEST_EXIT(PASS); +} + +int main(int argc, char *argv[]) +{ + printf("[TEST] %s: Test ACL bypass via link ID spoofing (CVE-2026-15812)\n", TEST_NAME); + + test_link_id_spoofing(); + + return PASS; +} diff --git a/libknet/threads_rx.c b/libknet/threads_rx.c index 6aa1a1f1..e2f254cc 100644 --- a/libknet/threads_rx.c +++ b/libknet/threads_rx.c @@ -231,23 +231,71 @@ static int pckt_defrag(knet_handle_t knet_h, struct knet_header *inbuf, ssize_t */ static int _check_rx_acl(knet_handle_t knet_h, struct knet_link *src_link, const struct knet_mmsghdr *msg) { - if (knet_h->use_access_lists) { - if (!check_validate(knet_h, src_link, msg->msg_hdr.msg_name)) { - char src_ipaddr[KNET_MAX_HOST_LEN]; - char src_port[KNET_MAX_PORT_LEN]; - - memset(src_ipaddr, 0, KNET_MAX_HOST_LEN); - memset(src_port, 0, KNET_MAX_PORT_LEN); - if (knet_addrtostr(msg->msg_hdr.msg_name, sockaddr_len(msg->msg_hdr.msg_name), + const struct sockaddr_storage *src_addr = msg->msg_hdr.msg_name; + char src_ipaddr[KNET_MAX_HOST_LEN]; + char src_port[KNET_MAX_PORT_LEN]; + int has_acl; + + /* + * ACL validation to prevent link ID spoofing (CVE-2026-15812) + * + * Static links (KNET_LINK_STATIC): + * Always validate - dst_addr is auto-configured in ACL during knet_link_set_config() + * + * Dynamic links (KNET_LINK_DYNIP): + * - If use_access_lists = 0: skip validation (user explicitly disabled) + * - If use_access_lists = 1 (default): + * - If no ACL configured: reject with specific message + * - If ACL configured: validate source address + * + * This ensures all links require proper ACL configuration unless explicitly opted out. + */ + + if (src_link->dynamic == KNET_LINK_STATIC) { + /* Static links: always validate */ + if (!check_validate(knet_h, src_link, (struct sockaddr_storage *)src_addr)) { + if (knet_addrtostr(src_addr, sockaddr_len(src_addr), src_ipaddr, KNET_MAX_HOST_LEN, src_port, KNET_MAX_PORT_LEN) < 0) { - log_warn(knet_h, KNET_SUB_RX, "Packet rejected: unable to resolve host/port"); } else { log_warn(knet_h, KNET_SUB_RX, "Packet rejected from %s:%s", src_ipaddr, src_port); } return 0; } + } else { + /* Dynamic links */ + if (knet_h->use_access_lists) { + /* Check if link has ACL configured */ + has_acl = (src_link->access_list_match_entry_head != NULL) && + (*(void **)src_link->access_list_match_entry_head != NULL); + + if (!has_acl) { + /* No ACL configured - reject */ + if (knet_addrtostr(src_addr, sockaddr_len(src_addr), + src_ipaddr, KNET_MAX_HOST_LEN, + src_port, KNET_MAX_PORT_LEN) < 0) { + log_warn(knet_h, KNET_SUB_RX, "Packet rejected: dynamic link has no ACL configured"); + } else { + log_warn(knet_h, KNET_SUB_RX, "Packet rejected from %s:%s: dynamic link has no ACL configured", + src_ipaddr, src_port); + } + return 0; + } + + /* ACL configured - validate */ + if (!check_validate(knet_h, src_link, (struct sockaddr_storage *)src_addr)) { + if (knet_addrtostr(src_addr, sockaddr_len(src_addr), + src_ipaddr, KNET_MAX_HOST_LEN, + src_port, KNET_MAX_PORT_LEN) < 0) { + log_warn(knet_h, KNET_SUB_RX, "Packet rejected: unable to resolve host/port"); + } else { + log_warn(knet_h, KNET_SUB_RX, "Packet rejected from %s:%s", src_ipaddr, src_port); + } + return 0; + } + } + /* else: use_access_lists = 0, skip validation */ } return 1; } @@ -352,8 +400,25 @@ static void _parse_recv_from_links(knet_handle_t knet_h, int sockfd, const struc if ((inbuf->kh_type & KNET_HEADER_TYPE_PMSK) != 0) { /* be aware this works only for PING / PONG and PMTUd packets! */ - src_link = src_host->link + - (inbuf->khp_ping_link % KNET_MAX_LINK); + uint8_t link_id = inbuf->khp_ping_link; + + /* + * Bounds check and configuration validation (CVE-2026-15812) + * Additional source address validation in _check_rx_acl() + */ + if (link_id >= KNET_MAX_LINK) { + log_warn(knet_h, KNET_SUB_RX, "Invalid link_id in packet: %u (max: %u)", + link_id, KNET_MAX_LINK - 1); + return; + } + + src_link = &src_host->link[link_id]; + + if (!src_link->configured) { + log_warn(knet_h, KNET_SUB_RX, "Invalid link_id in packet (unconfigured): %u", link_id); + return; + } + if (!_check_rx_acl(knet_h, src_link, msg)) { return; } -- 2.55.0 ++++++ 0003-CVE-2026-15813-memory-corruption-and-out-of-bounds-a.patch ++++++ >From 8dae6ae48562210c3286d1dd0f5a0b24a621220a Mon Sep 17 00:00:00 2001 From: "Fabio M. Di Nitto" <[email protected]> Date: Wed, 20 May 2026 11:19:22 +0200 Subject: [PATCH 3/5] CVE-2026-15813: memory corruption and out-of-bounds access via malformed network packet defragmentation Severity: MEDIUM Validate fragment sequence numbers before accessing defragmentation buffers. Previous code used unchecked sequence numbers as array indices, allowing heap buffer overflow. Exploitable only with unencrypted traffic (not supported for production). CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer Signed-off-by: Fabio M. Di Nitto <[email protected]> Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- libknet/tests/Makefile.am | 6 +- libknet/tests/sec_frag_sequence.c | 220 ++++++++++++++++++++++++++++++ libknet/threads_rx.c | 27 ++++ 3 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 libknet/tests/sec_frag_sequence.c diff --git a/libknet/tests/Makefile.am b/libknet/tests/Makefile.am index 4f42d340..9fd584d9 100644 --- a/libknet/tests/Makefile.am +++ b/libknet/tests/Makefile.am @@ -41,7 +41,8 @@ int_checks = \ int_timediff_test sec_checks = \ - sec_acl_link_id_spoofing_test + sec_acl_link_id_spoofing_test \ + sec_frag_sequence_test fun_checks = \ fun_config_crypto_test \ @@ -129,4 +130,7 @@ fun_acl_check_test_SOURCES = fun_acl_check.c \ sec_acl_link_id_spoofing_test_SOURCES = sec_acl_link_id_spoofing.c \ test-common.c +sec_frag_sequence_test_SOURCES = sec_frag_sequence.c \ + test-common.c + CLEANFILES = knet_test_unix_stream.* diff --git a/libknet/tests/sec_frag_sequence.c b/libknet/tests/sec_frag_sequence.c new file mode 100644 index 00000000..c96f0535 --- /dev/null +++ b/libknet/tests/sec_frag_sequence.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2026 Red Hat, Inc. All rights reserved. + * + * Authors: Fabio M. Di Nitto <[email protected]> + * + * This software licensed under GPL-2.0+ + */ + +#include "config.h" + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include "libknet.h" +#include "internals.h" +#include "onwire.h" +#include "test-common.h" + +#define TEST_NAME "sec_frag_sequence" + +/* + * Test for CVE-2026-15813: Fragment sequence bounds checking + * + * This test validates that the RX thread properly rejects packets with: + * 1. frag_seq == 0 (fragments are 1-indexed, this is invalid) + * 2. frag_seq > frag_num (sequence number exceeds fragment count) + * + * We use a packet injector to directly test the validation logic without + * requiring network-level packet manipulation. + */ + +static int private_data; +static seq_num_t next_seq_num = 1; + +static void sock_notify(void *pvt_data, + int datafd, + int8_t channel, + uint8_t tx_rx, + int error, + int errorno) +{ + return; +} + +/* + * Log filter callback to check for invalid fragment sequence message + */ +static int filter_invalid_frag_seq(int logfd, const char *log_line, void *private_data) +{ + (void)logfd; + (void)private_data; + + if (strstr(log_line, "Invalid fragment sequence")) { + return 1; + } + return 0; +} + +/* + * Log filter callback to check for invalid fragment count message + */ +static int filter_invalid_frag_count(int logfd, const char *log_line, void *private_data) +{ + (void)logfd; + (void)private_data; + + if (strstr(log_line, "Invalid fragment count")) { + return 1; + } + return 0; +} + +/* + * Test invalid fragment sequences + */ +static void test_invalid_frag_seq(void) +{ + knet_handle_t knet_h1, knet_h[2] = {0}; + int logfd; + int datafd = 0; + int8_t channel = 0; + struct sockaddr_storage lo; + char payload[100]; + + logfd = start_logging(stdout); + + memset(payload, 'A', sizeof(payload)); + + log_test(logfd, "Test fragment sequence validation with malformed packets"); + + knet_h1 = _ts_knet_handle_start(logfd, KNET_LOG_DEBUG, knet_h); + + FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h1, &private_data, sock_notify)); + FAIL_ON_ERR(knet_handle_add_datafd(knet_h1, &datafd, &channel)); + FAIL_ON_ERR(knet_host_add(knet_h1, 1)); + FAIL_ON_ERR(_ts_knet_link_set_config(knet_h1, 1, 0, KNET_TRANSPORT_UDP, 0, AF_INET, 0, &lo, logfd)); + FAIL_ON_ERR(knet_link_set_enable(knet_h1, 1, 0, 1)); + FAIL_ON_ERR(knet_handle_setfwd(knet_h1, 1)); + FAIL_ON_ERR(wait_for_host(knet_h1, 1, 10, logfd)); + + log_test(logfd, "Test 1: Inject packet with frag_seq=0 (should be rejected)"); + log_test(logfd, " Fragments are 1-indexed, so frag_seq=0 is invalid"); + + /* Install log filter to catch rejection message */ + install_log_filter(logfd, filter_invalid_frag_seq, NULL); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 0, next_seq_num++, 0, payload, 50) < 0) { + log_test(logfd, "Failed to inject packet with frag_seq=0"); + TEST_EXIT_CLEAN(FAIL); + } + + /* Give RX thread time to process and log thread to catch it */ + test_sleep(logfd, 2); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: Expected log 'Invalid fragment sequence' not found"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + log_test(logfd, "Test 1 PASSED: Invalid frag_seq=0 was rejected"); + + log_test(logfd, "Test 2: Inject packet with frag_seq > frag_num (should be rejected)"); + log_test(logfd, " frag_seq=5 but frag_num=2 (sequence exceeds count)"); + + /* Filter still installed, reset flag */ + install_log_filter(logfd, filter_invalid_frag_seq, NULL); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 5, next_seq_num++, 0, payload, 50) < 0) { + log_test(logfd, "Failed to inject packet with frag_seq > frag_num"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 2); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: Expected log 'Invalid fragment sequence' not found"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + log_test(logfd, "Test 2 PASSED: Invalid frag_seq > frag_num was rejected"); + + log_test(logfd, "Test 3: Inject valid fragment packet (should be accepted)"); + log_test(logfd, " frag_seq=1, frag_num=1 (valid unfragmented packet)"); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 1, 1, next_seq_num++, 0, payload, 50) < 0) { + log_test(logfd, "Failed to inject valid packet"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 1); + log_test(logfd, "Test 3 PASSED: Valid packet was accepted (no rejection log)"); + + log_test(logfd, "Test 4: Inject packet with frag_num=255 (maximum value, should be rejected)"); + log_test(logfd, " frag_map array has 255 elements (indices 0-254)"); + log_test(logfd, " frag_seq=255 would access frag_map[255] which is out of bounds"); + + /* Install filter for fragment count validation */ + install_log_filter(logfd, filter_invalid_frag_count, NULL); + + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 255, 255, next_seq_num++, 0, payload, 50) < 0) { + log_test(logfd, "Failed to inject packet with frag_num=255"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 2); + + if (!check_log_pattern_found()) { + log_test(logfd, "*** FAIL: Expected log 'Invalid fragment count' not found"); + install_log_filter(logfd, NULL, NULL); + TEST_EXIT_CLEAN(FAIL); + } + log_test(logfd, "Test 4 PASSED: frag_num=255 was rejected (prevents array overflow)"); + + log_test(logfd, "Test 5: Inject packet with frag_num=254 (maximum valid, should be accepted)"); + log_test(logfd, " Valid range is 1-254, this tests the boundary"); + + /* Remove filter so we can verify no rejection */ + install_log_filter(logfd, NULL, NULL); + + /* Send complete packet with frag_num=254, frag_seq=1 (first of 254 fragments) */ + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 254, 1, next_seq_num++, 0, payload, 50) < 0) { + log_test(logfd, "Failed to inject packet with frag_num=254"); + TEST_EXIT_CLEAN(FAIL); + } + + test_sleep(logfd, 1); + log_test(logfd, "Test 5 PASSED: frag_num=254 was accepted (maximum valid value)"); + + /* Remove filter */ + install_log_filter(logfd, NULL, NULL); + + log_test(logfd, "=== CVE-2026-15813 Fragment sequence validation test PASSED ==="); + log_test(logfd, "Invalid frag_seq values were properly rejected"); + log_test(logfd, "Invalid frag_num=255 was rejected (prevents array overflow)"); + log_test(logfd, "Valid frag_num=254 was accepted (maximum valid value)"); + log_test(logfd, "Valid fragment packets were accepted"); + log_test(logfd, "Heap buffer overflow prevented"); + + _ts_knet_handle_stop_everything(knet_h, 1, logfd); + + TEST_EXIT(PASS); +} + +int main(int argc, char *argv[]) +{ + printf("[TEST] %s: Test fragment sequence bounds checking (CVE-2026-15813)\n", TEST_NAME); + + test_invalid_frag_seq(); + + return PASS; +} diff --git a/libknet/threads_rx.c b/libknet/threads_rx.c index e2f254cc..3fd986e9 100644 --- a/libknet/threads_rx.c +++ b/libknet/threads_rx.c @@ -126,6 +126,33 @@ static int pckt_defrag(knet_handle_t knet_h, struct knet_header *inbuf, ssize_t struct knet_host_defrag_buf *defrag_buf; int defrag_buf_idx; + /* + * Validate total fragment count (CVE-2026-15813) + * frag_map array has PCKT_FRAG_MAX (255) elements indexed 0-254. + * Since frag_seq is 1-based and used as array index, maximum valid + * value is PCKT_FRAG_MAX-1 (254). + */ + if (inbuf->khp_data_frag_num == 0 || inbuf->khp_data_frag_num >= PCKT_FRAG_MAX) { + log_warn(knet_h, KNET_SUB_RX, + "Invalid fragment count: %u (valid range: 1-%u)", + inbuf->khp_data_frag_num, PCKT_FRAG_MAX - 1); + errno = EINVAL; + return -1; + } + + /* + * Validate fragment sequence number to prevent buffer overflow. + * frag_seq is used as array index into frag_map[PCKT_FRAG_MAX]. + * Also ensure frag_seq is within the declared fragment count. + */ + if (inbuf->khp_data_frag_seq == 0 || inbuf->khp_data_frag_seq > inbuf->khp_data_frag_num) { + log_warn(knet_h, KNET_SUB_RX, + "Invalid fragment sequence %u (total frags: %u)", + inbuf->khp_data_frag_seq, inbuf->khp_data_frag_num); + errno = EINVAL; + return -1; + } + defrag_buf_idx = find_pckt_defrag_buf(knet_h, inbuf); if (defrag_buf_idx < 0) { return 1; -- 2.55.0 ++++++ 0004-tests-Add-runtime-log-filter-callback-infrastructure.patch ++++++ >From 2159c4c0b7f260692f8c629b091a83d2b6b7ab91 Mon Sep 17 00:00:00 2001 From: "Fabio M. Di Nitto" <[email protected]> Date: Tue, 19 May 2026 10:18:13 +0200 Subject: [PATCH 4/5] [tests] Add runtime log filter callback infrastructure Add thread-safe log filter callback mechanism for runtime pattern matching in test logs. The log thread invokes registered callbacks for each log line, allowing tests to detect specific messages without parsing log files. Features: - install_log_filter() to register/unregister callbacks - check_log_pattern_found() to query if pattern was matched - Thread-safe via mutex protection - Callback receives logfd for use with log_test() facility Signed-off-by: Fabio M. Di Nitto <[email protected]> Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- libknet/tests/test-common.c | 58 ++++++++++++++++++++++++++++++++++--- libknet/tests/test-common.h | 23 +++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/libknet/tests/test-common.c b/libknet/tests/test-common.c index 812e24a2..a5247591 100644 --- a/libknet/tests/test-common.c +++ b/libknet/tests/test-common.c @@ -150,6 +150,13 @@ out_clean0: return err; } +/* Log filter state for runtime pattern matching */ +static pthread_mutex_t log_filter_mutex = PTHREAD_MUTEX_INITIALIZER; +static log_filter_fn log_filter_callback = NULL; +static int log_filter_logfd = -1; +static void *log_filter_private_data = NULL; +static int log_pattern_found = 0; + int is_memcheck(void) { char *val; @@ -222,6 +229,7 @@ void flush_logs(int logfd, FILE *std) { struct knet_log_msg msg; int len; + char log_line[1024]; while (1) { len = read(logfd, &msg, sizeof(msg)); @@ -235,10 +243,22 @@ void flush_logs(int logfd, FILE *std) msg.msg[sizeof(msg.msg) - 1] = 0; - fprintf(std, "[knet]: [%s] %s: %.*s\n", - knet_log_get_loglevel_name(msg.msglevel), - knet_log_get_subsystem_name(msg.subsystem), - KNET_MAX_LOG_MSG_SIZE, msg.msg); + snprintf(log_line, sizeof(log_line), + "[knet]: [%s] %s: %.*s", + knet_log_get_loglevel_name(msg.msglevel), + knet_log_get_subsystem_name(msg.subsystem), + KNET_MAX_LOG_MSG_SIZE, msg.msg); + + fprintf(std, "%s\n", log_line); + + /* Check log filter if installed */ + pthread_mutex_lock(&log_filter_mutex); + if (log_filter_callback != NULL) { + if (log_filter_callback(log_filter_logfd, log_line, log_filter_private_data)) { + log_pattern_found = 1; + } + } + pthread_mutex_unlock(&log_filter_mutex); } } @@ -992,3 +1012,33 @@ void knet_handle_stop_everything(knet_handle_t knet_h[], uint8_t numnodes) } } } + +/* + * Install a runtime log filter callback + * Thread-safe via mutex protection + */ +void install_log_filter(int logfd, log_filter_fn filter_fn, void *private_data) +{ + pthread_mutex_lock(&log_filter_mutex); + log_filter_callback = filter_fn; + log_filter_logfd = logfd; + log_filter_private_data = private_data; + log_pattern_found = 0; /* Reset flag when installing new filter */ + pthread_mutex_unlock(&log_filter_mutex); +} + +/* + * Check if log filter found a pattern match + * Returns current value and resets the flag + */ +int check_log_pattern_found(void) +{ + int found; + + pthread_mutex_lock(&log_filter_mutex); + found = log_pattern_found; + log_pattern_found = 0; /* Reset after reading */ + pthread_mutex_unlock(&log_filter_mutex); + + return found; +} diff --git a/libknet/tests/test-common.h b/libknet/tests/test-common.h index c726f6ff..143a6695 100644 --- a/libknet/tests/test-common.h +++ b/libknet/tests/test-common.h @@ -149,4 +149,27 @@ int wait_for_nodes_state(knet_handle_t knet_h, size_t numnodes, uint8_t state, uint32_t timeout, int logfd, FILE *std); +/* + * Log filter callback type + * Called by log thread for each log line. Return 1 to set pattern_found flag. + */ +typedef int (*log_filter_fn)(int logfd, const char *log_line, void *private_data); + +/* + * Install a runtime log filter + * The filter callback is invoked for each log line by the log thread. + * Thread-safe via mutex protection. + * + * filter_fn: callback function to check log lines (NULL to disable filtering) + * private_data: opaque pointer passed to filter callback + */ +void install_log_filter(int logfd, log_filter_fn filter_fn, void *private_data); + +/* + * Check if log filter found a match + * Returns 1 if filter callback returned 1 for any log line, 0 otherwise + * Resets the found flag after reading it. + */ +int check_log_pattern_found(void); + #endif -- 2.55.0 ++++++ 0005-tests-Add-packet-injection-helper-for-RX-validation-.patch ++++++ >From 4feafe410e5c2317752ed92adf4cd449c928c7a4 Mon Sep 17 00:00:00 2001 From: "Fabio M. Di Nitto" <[email protected]> Date: Tue, 19 May 2026 10:18:57 +0200 Subject: [PATCH 5/5] [tests] Add packet injection helper for RX validation testing Add inject_packet() helper to construct and inject packets directly into link sockets for testing RX thread validation logic without requiring network-level packet manipulation. Supports: - DATA packets with configurable fragmentation fields - PING packets with configurable link_id - Controlled sequence numbers to avoid deduplication - Tests can forge invalid packets to verify rejection logic Signed-off-by: Fabio M. Di Nitto <[email protected]> Co-Authored-By: Claude Sonnet 4.5 <[email protected]> --- libknet/tests/test-common.c | 119 ++++++++++++++++++++++++++++++++++++ libknet/tests/test-common.h | 16 +++++ 2 files changed, 135 insertions(+) diff --git a/libknet/tests/test-common.c b/libknet/tests/test-common.c index a5247591..00bc864b 100644 --- a/libknet/tests/test-common.c +++ b/libknet/tests/test-common.c @@ -1013,6 +1013,125 @@ void knet_handle_stop_everything(knet_handle_t knet_h[], uint8_t numnodes) } } +/* + * Packet injector: Create a packet and inject it into a link's socket + * + * This allows testing RX validation without network-level packet manipulation. + * The caller provides a seq_num to avoid packet deduplication in the RX thread. + * + * Returns 0 on success, -1 on error + */ +int inject_packet(knet_handle_t knet_h, + uint8_t packet_type, + knet_node_id_t src_host_id, + uint8_t actual_link_id, + uint8_t claimed_link_id, + uint8_t frag_num, + uint8_t frag_seq, + seq_num_t seq_num, + const char *payload, + size_t payload_len) +{ + struct knet_header *packet; + size_t packet_len; + struct knet_host *src_host; + struct knet_link *src_link; + ssize_t sent; + socklen_t addrlen; + struct timespec timestamp; + + /* Determine packet size based on type */ + switch (packet_type) { + case KNET_HEADER_TYPE_DATA: + packet_len = KNET_HEADER_DATA_V1_SIZE + payload_len; + break; + case KNET_HEADER_TYPE_PING: + packet_len = KNET_HEADER_PING_V1_SIZE; + break; + default: + errno = EINVAL; + return -1; + } + + packet = malloc(packet_len); + if (!packet) { + return -1; + } + + memset(packet, 0, packet_len); + + /* Fill in common packet header */ + packet->kh_version = KNET_HEADER_ONWIRE_MAX_VER; + packet->kh_type = packet_type; + packet->kh_node = htons(src_host_id); + packet->kh_max_ver = KNET_HEADER_ONWIRE_MAX_VER; + + /* Fill in type-specific payload */ + switch (packet_type) { + case KNET_HEADER_TYPE_DATA: + packet->khp_data_v1_seq_num = htons(seq_num); + packet->khp_data_v1_compress = 0; + packet->khp_data_v1_bcast = 0; + packet->khp_data_v1_channel = 0; + packet->khp_data_v1_frag_num = frag_num; + packet->khp_data_v1_frag_seq = frag_seq; + + /* Copy payload */ + if (payload && payload_len > 0) { + memcpy(packet->khp_data_v1_userdata, payload, payload_len); + } + break; + case KNET_HEADER_TYPE_PING: + packet->khp_ping_v1_link = claimed_link_id; + clock_gettime(CLOCK_MONOTONIC, ×tamp); + memmove(&packet->khp_ping_v1_time[0], ×tamp, sizeof(struct timespec)); + packet->khp_ping_v1_seq_num = htons(seq_num); + packet->khp_ping_v1_timed = 1; + break; + } + + /* Get the source host and link to determine where to inject */ + src_host = knet_h->host_index[src_host_id]; + if (!src_host) { + free(packet); + return -1; + } + + src_link = &src_host->link[actual_link_id]; + + /* Check if link is properly configured */ + if (src_link->outsock < 0) { + free(packet); + return -1; + } + + /* Determine address length based on address family */ + switch (src_link->dst_addr.ss_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + free(packet); + return -1; + } + + /* Inject the packet by sending to ourselves (loopback) */ + sent = sendto(src_link->outsock, packet, packet_len, MSG_DONTWAIT | MSG_NOSIGNAL, + (struct sockaddr *)&src_link->dst_addr, + addrlen); + + free(packet); + + if (sent != (ssize_t)packet_len) { + return -1; + } + + return 0; +} + /* * Install a runtime log filter callback * Thread-safe via mutex protection diff --git a/libknet/tests/test-common.h b/libknet/tests/test-common.h index 143a6695..b13d8ebd 100644 --- a/libknet/tests/test-common.h +++ b/libknet/tests/test-common.h @@ -149,6 +149,22 @@ int wait_for_nodes_state(knet_handle_t knet_h, size_t numnodes, uint8_t state, uint32_t timeout, int logfd, FILE *std); +/* + * Packet injection helper for testing RX validation + * Creates and injects a packet with specified fragment parameters + * Returns 0 on success, -1 on error + */ +int inject_packet(knet_handle_t knet_h, + uint8_t packet_type, + knet_node_id_t src_host_id, + uint8_t actual_link_id, + uint8_t claimed_link_id, + uint8_t frag_num, + uint8_t frag_seq, + seq_num_t seq_num, + const char *payload, + size_t payload_len); + /* * Log filter callback type * Called by log thread for each log line. Return 1 to set pattern_found flag. -- 2.55.0 ++++++ adapt-backported-tests-to-1.33.patch ++++++ diff --git i/libknet/tests/sec_acl_link_id_spoofing.c w/libknet/tests/sec_acl_link_id_spoofing.c index 57c050c4..6179094f 100644 --- i/libknet/tests/sec_acl_link_id_spoofing.c +++ w/libknet/tests/sec_acl_link_id_spoofing.c @@ -99,19 +99,23 @@ static int filter_invalid_link_id(int logfd, const char *log_line, void *private static void test_link_id_spoofing(void) { knet_handle_t knet_h1, knet_h[2] = {0}; + int logfds[2]; int logfd; int datafd = 0; int8_t channel = 0; seq_num_t seq_num = 2000; + int res; logfd = start_logging(stdout); + logfds[0] = 0; + logfds[1] = logfd; log_test(logfd, "Test: CVE-2026-15812 Link ID spoofing prevention (secure by default)"); /* * Set up knet handle */ - knet_h1 = _ts_knet_handle_start(logfd, KNET_LOG_DEBUG, knet_h); + knet_h1 = knet_handle_start(logfds, KNET_LOG_DEBUG, knet_h); FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h1, &private_data, sock_notify)); FAIL_ON_ERR(knet_handle_add_datafd(knet_h1, &datafd, &channel)); @@ -135,17 +139,16 @@ static void test_link_id_spoofing(void) struct sockaddr_storage lo0, lo1; /* Allocate dynamic port for link 0, use 127.0.0.1 */ - FAIL_ON_ERR(make_local_sockaddr(&lo0, 0, logfd)); + FAIL_ON_ERR(make_local_sockaddr(&lo0, 0)); FAIL_ON_ERR(knet_link_set_config(knet_h1, 1, 0, KNET_TRANSPORT_UDP, &lo0, &lo0, 0)); log_test(logfd, " Link 0 configured (127.0.0.1)"); /* Allocate dynamic port for link 1, change IP to 127.0.0.2 */ - FAIL_ON_ERR(make_local_sockaddr(&lo1, 1, logfd)); + FAIL_ON_ERR(make_local_sockaddr(&lo1, 1)); ((struct sockaddr_in *)&lo1)->sin_addr.s_addr = htonl(0x7f000002); /* 127.0.0.2 */ if (knet_link_set_config(knet_h1, 1, 1, KNET_TRANSPORT_UDP, &lo1, &lo1, 0) < 0) { log_test(logfd, "Cannot bind 127.0.0.2 (platform lacks full 127/8 support), skipping test"); - _ts_knet_handle_stop_everything(knet_h, 1, logfd); - TEST_EXIT(PASS); + clean_exit(knet_h, 1, logfds, PASS); } log_test(logfd, " Link 1 configured (127.0.0.2)"); @@ -164,13 +167,13 @@ static void test_link_id_spoofing(void) /* Install filter for packet rejection due to ACL */ install_log_filter(logfd, filter_packet_rejected, NULL); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 1, 0, 0, seq_num, 0, NULL, 0) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 1, 0, 0, seq_num, NULL, 0) < 0) { log_test(logfd, "*** FAIL: Failed to inject ACL bypass packet"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 2); /* Give RX thread time to process */ + test_sleep(knet_h1, 2); /* Give RX thread time to process */ log_test(logfd, "Spoofed link_id packet sent"); @@ -180,7 +183,7 @@ static void test_link_id_spoofing(void) log_test(logfd, "*** FAIL: ACL did not reject spoofed packet"); log_test(logfd, " Expected: 'Packet rejected from'"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "ACL correctly rejected spoofed link_id packet"); @@ -195,13 +198,13 @@ static void test_link_id_spoofing(void) /* Switch to invalid link_id filter */ install_log_filter(logfd, filter_invalid_link_id, NULL); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 2, 0, 0, seq_num + 1, 0, NULL, 0) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, 2, 0, 0, seq_num + 1, NULL, 0) < 0) { log_test(logfd, "*** FAIL: Failed to inject unconfigured link_id packet"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 2); + test_sleep(knet_h1, 2); log_test(logfd, "Unconfigured link_id packet sent"); @@ -211,7 +214,7 @@ static void test_link_id_spoofing(void) log_test(logfd, "*** FAIL: Log does not contain expected invalid link_id warning"); log_test(logfd, " Expected: 'Invalid link_id'"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "Log contains invalid link_id rejection message"); @@ -223,13 +226,13 @@ static void test_link_id_spoofing(void) log_test(logfd, " link_id in packet: %u (>= KNET_MAX_LINK)", KNET_MAX_LINK); /* Keep invalid link_id filter active */ - if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, KNET_MAX_LINK, 0, 0, seq_num + 2, 0, NULL, 0) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_PING, 1, 0, KNET_MAX_LINK, 0, 0, seq_num + 2, NULL, 0) < 0) { log_test(logfd, "*** FAIL: Failed to inject out-of-bounds ping packet"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 2); + test_sleep(knet_h1, 2); log_test(logfd, "Out-of-bounds link_id packet sent"); @@ -239,7 +242,7 @@ static void test_link_id_spoofing(void) log_test(logfd, "*** FAIL: Log does not contain expected invalid link_id warning"); log_test(logfd, " Expected: 'Invalid link_id'"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "Log contains out-of-bounds rejection message"); @@ -254,9 +257,7 @@ static void test_link_id_spoofing(void) log_test(logfd, "All attack vectors blocked"); log_test(logfd, "Note: use_access_lists is now enabled by default (secure by default)"); - _ts_knet_handle_stop_everything(knet_h, 1, logfd); - - TEST_EXIT(PASS); + clean_exit(knet_h, 1, logfds, PASS); } int main(int argc, char *argv[]) diff --git i/libknet/tests/sec_frag_sequence.c w/libknet/tests/sec_frag_sequence.c index c96f0535..dcbebe6d 100644 --- i/libknet/tests/sec_frag_sequence.c +++ w/libknet/tests/sec_frag_sequence.c @@ -82,27 +82,31 @@ static int filter_invalid_frag_count(int logfd, const char *log_line, void *priv static void test_invalid_frag_seq(void) { knet_handle_t knet_h1, knet_h[2] = {0}; + int logfds[2]; int logfd; int datafd = 0; int8_t channel = 0; struct sockaddr_storage lo; char payload[100]; + int res; logfd = start_logging(stdout); + logfds[0] = 0; + logfds[1] = logfd; memset(payload, 'A', sizeof(payload)); log_test(logfd, "Test fragment sequence validation with malformed packets"); - knet_h1 = _ts_knet_handle_start(logfd, KNET_LOG_DEBUG, knet_h); + knet_h1 = knet_handle_start(logfds, KNET_LOG_DEBUG, knet_h); FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h1, &private_data, sock_notify)); FAIL_ON_ERR(knet_handle_add_datafd(knet_h1, &datafd, &channel)); FAIL_ON_ERR(knet_host_add(knet_h1, 1)); - FAIL_ON_ERR(_ts_knet_link_set_config(knet_h1, 1, 0, KNET_TRANSPORT_UDP, 0, AF_INET, 0, &lo, logfd)); + FAIL_ON_ERR(_knet_link_set_config(knet_h1, 1, 0, KNET_TRANSPORT_UDP, 0, AF_INET, 0, &lo)); FAIL_ON_ERR(knet_link_set_enable(knet_h1, 1, 0, 1)); FAIL_ON_ERR(knet_handle_setfwd(knet_h1, 1)); - FAIL_ON_ERR(wait_for_host(knet_h1, 1, 10, logfd)); + FAIL_ON_ERR(wait_for_host(knet_h1, 1, 10, logfds[0], stdout)); log_test(logfd, "Test 1: Inject packet with frag_seq=0 (should be rejected)"); log_test(logfd, " Fragments are 1-indexed, so frag_seq=0 is invalid"); @@ -110,18 +114,18 @@ static void test_invalid_frag_seq(void) /* Install log filter to catch rejection message */ install_log_filter(logfd, filter_invalid_frag_seq, NULL); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 0, next_seq_num++, 0, payload, 50) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 0, next_seq_num++, payload, 50) < 0) { log_test(logfd, "Failed to inject packet with frag_seq=0"); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } /* Give RX thread time to process and log thread to catch it */ - test_sleep(logfd, 2); + test_sleep(knet_h1, 2); if (!check_log_pattern_found()) { log_test(logfd, "*** FAIL: Expected log 'Invalid fragment sequence' not found"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "Test 1 PASSED: Invalid frag_seq=0 was rejected"); @@ -131,31 +135,31 @@ static void test_invalid_frag_seq(void) /* Filter still installed, reset flag */ install_log_filter(logfd, filter_invalid_frag_seq, NULL); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 5, next_seq_num++, 0, payload, 50) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 2, 5, next_seq_num++, payload, 50) < 0) { log_test(logfd, "Failed to inject packet with frag_seq > frag_num"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 2); + test_sleep(knet_h1, 2); if (!check_log_pattern_found()) { log_test(logfd, "*** FAIL: Expected log 'Invalid fragment sequence' not found"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "Test 2 PASSED: Invalid frag_seq > frag_num was rejected"); log_test(logfd, "Test 3: Inject valid fragment packet (should be accepted)"); log_test(logfd, " frag_seq=1, frag_num=1 (valid unfragmented packet)"); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 1, 1, next_seq_num++, 0, payload, 50) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 1, 1, next_seq_num++, payload, 50) < 0) { log_test(logfd, "Failed to inject valid packet"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 1); + test_sleep(knet_h1, 1); log_test(logfd, "Test 3 PASSED: Valid packet was accepted (no rejection log)"); log_test(logfd, "Test 4: Inject packet with frag_num=255 (maximum value, should be rejected)"); @@ -165,18 +169,18 @@ static void test_invalid_frag_seq(void) /* Install filter for fragment count validation */ install_log_filter(logfd, filter_invalid_frag_count, NULL); - if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 255, 255, next_seq_num++, 0, payload, 50) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 255, 255, next_seq_num++, payload, 50) < 0) { log_test(logfd, "Failed to inject packet with frag_num=255"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 2); + test_sleep(knet_h1, 2); if (!check_log_pattern_found()) { log_test(logfd, "*** FAIL: Expected log 'Invalid fragment count' not found"); install_log_filter(logfd, NULL, NULL); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } log_test(logfd, "Test 4 PASSED: frag_num=255 was rejected (prevents array overflow)"); @@ -187,12 +191,12 @@ static void test_invalid_frag_seq(void) install_log_filter(logfd, NULL, NULL); /* Send complete packet with frag_num=254, frag_seq=1 (first of 254 fragments) */ - if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 254, 1, next_seq_num++, 0, payload, 50) < 0) { + if (inject_packet(knet_h1, KNET_HEADER_TYPE_DATA, 1, 0, 0, 254, 1, next_seq_num++, payload, 50) < 0) { log_test(logfd, "Failed to inject packet with frag_num=254"); - TEST_EXIT_CLEAN(FAIL); + clean_exit(knet_h, 1, logfds, FAIL); } - test_sleep(logfd, 1); + test_sleep(knet_h1, 1); log_test(logfd, "Test 5 PASSED: frag_num=254 was accepted (maximum valid value)"); /* Remove filter */ @@ -205,9 +209,7 @@ static void test_invalid_frag_seq(void) log_test(logfd, "Valid fragment packets were accepted"); log_test(logfd, "Heap buffer overflow prevented"); - _ts_knet_handle_stop_everything(knet_h, 1, logfd); - - TEST_EXIT(PASS); + clean_exit(knet_h, 1, logfds, PASS); } int main(int argc, char *argv[]) diff --git i/libknet/tests/test-common.c w/libknet/tests/test-common.c index 00bc864b..739134bb 100644 --- i/libknet/tests/test-common.c +++ w/libknet/tests/test-common.c @@ -1043,10 +1043,10 @@ int inject_packet(knet_handle_t knet_h, /* Determine packet size based on type */ switch (packet_type) { case KNET_HEADER_TYPE_DATA: - packet_len = KNET_HEADER_DATA_V1_SIZE + payload_len; + packet_len = KNET_HEADER_DATA_SIZE + payload_len; break; case KNET_HEADER_TYPE_PING: - packet_len = KNET_HEADER_PING_V1_SIZE; + packet_len = KNET_HEADER_PING_SIZE; break; default: errno = EINVAL; @@ -1061,32 +1061,31 @@ int inject_packet(knet_handle_t knet_h, memset(packet, 0, packet_len); /* Fill in common packet header */ - packet->kh_version = KNET_HEADER_ONWIRE_MAX_VER; + packet->kh_version = KNET_HEADER_VERSION; packet->kh_type = packet_type; packet->kh_node = htons(src_host_id); - packet->kh_max_ver = KNET_HEADER_ONWIRE_MAX_VER; /* Fill in type-specific payload */ switch (packet_type) { case KNET_HEADER_TYPE_DATA: - packet->khp_data_v1_seq_num = htons(seq_num); - packet->khp_data_v1_compress = 0; - packet->khp_data_v1_bcast = 0; - packet->khp_data_v1_channel = 0; - packet->khp_data_v1_frag_num = frag_num; - packet->khp_data_v1_frag_seq = frag_seq; + packet->khp_data_seq_num = htons(seq_num); + packet->khp_data_compress = 0; + packet->khp_data_bcast = 0; + packet->khp_data_channel = 0; + packet->khp_data_frag_num = frag_num; + packet->khp_data_frag_seq = frag_seq; /* Copy payload */ if (payload && payload_len > 0) { - memcpy(packet->khp_data_v1_userdata, payload, payload_len); + memcpy(packet->khp_data_userdata, payload, payload_len); } break; case KNET_HEADER_TYPE_PING: - packet->khp_ping_v1_link = claimed_link_id; + packet->khp_ping_link = claimed_link_id; clock_gettime(CLOCK_MONOTONIC, ×tamp); - memmove(&packet->khp_ping_v1_time[0], ×tamp, sizeof(struct timespec)); - packet->khp_ping_v1_seq_num = htons(seq_num); - packet->khp_ping_v1_timed = 1; + memmove(packet->khp_ping_time, ×tamp, sizeof(struct timespec)); + packet->khp_ping_seq_num = htons(seq_num); + packet->khp_ping_timed = 1; break; } diff --git i/libknet/tests/test-common.h w/libknet/tests/test-common.h index b13d8ebd..380d8a19 100644 --- i/libknet/tests/test-common.h +++ w/libknet/tests/test-common.h @@ -37,6 +37,22 @@ */ #define TESTNODES 1 +/* + * Test logging macro - writes to knet logging infrastructure + * with NULL handle (displayed as [testsuite]: message) + * Parameters: logfd - the write end of the log pipe, fmt - printf-style format string + */ +#define log_test(logfd, fmt, ...) \ + do { \ + struct knet_log_msg _log_msg; \ + memset(&_log_msg, 0, sizeof(_log_msg)); \ + _log_msg.subsystem = KNET_SUB_UNKNOWN - 1; \ + snprintf(_log_msg.msg, KNET_MAX_LOG_MSG_SIZE, fmt, ##__VA_ARGS__); \ + if (write(logfd, &_log_msg, sizeof(_log_msg)) != sizeof(_log_msg)) { \ + fprintf(stderr, "Failed to write to log pipe\n"); \ + } \ + } while(0) + #define FAIL_ON_ERR(fn) \ printf("FOE: %s\n", #fn); \ if ((res = fn) != 0) { \
