From 59af7ff77adb92844b0c805a4c0c29ff28285cb8 Mon Sep 17 00:00:00 2001 From: AlmaLinux RelEng Bot Date: Tue, 7 Apr 2026 06:32:13 -0400 Subject: [PATCH] import CS libreswan-5.3-8.el10 --- .gitignore | 2 +- libreswan-5.2.tar.gz.asc | 17 - libreswan-5.3-helper-thread.patch | 97 ++ ...an-5.3-outstanding-ike-auth-crossing.patch | 1056 +++++++++++++++++ libreswan-5.3.tar.gz.asc | 17 + libreswan.spec | 69 +- sources | 2 +- 7 files changed, 1232 insertions(+), 28 deletions(-) delete mode 100644 libreswan-5.2.tar.gz.asc create mode 100644 libreswan-5.3-helper-thread.patch create mode 100644 libreswan-5.3-outstanding-ike-auth-crossing.patch create mode 100644 libreswan-5.3.tar.gz.asc diff --git a/.gitignore b/.gitignore index a962855..31366c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ ikev1_dsa.fax.bz2 ikev1_psk.fax.bz2 ikev2.fax.bz2 -libreswan-5.2.tar.gz +libreswan-5.3.tar.gz diff --git a/libreswan-5.2.tar.gz.asc b/libreswan-5.2.tar.gz.asc deleted file mode 100644 index 64928ef..0000000 --- a/libreswan-5.2.tar.gz.asc +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQJHBAABCgAxFiEEkH55DyXB6OVhzXO1hf9LQ7MPxvkFAme/mHcTHHRlYW1AbGli -cmVzd2FuLm9yZwAKCRCF/0tDsw/G+Y95D/981PfZO85XO4a6Db18FJEfe3lt4pWY -aEIuE//BPruXi6hAAC3+6kzrujT9zxavwecRRr7YXqh3nXyXTTYK/KoK9jp0rqkC -0orOQb/JO9Gba2PQQU66F37hfQKmxx6ewzZJBjyOERlCaMRU7Rp5cSQv/6XqJqdj -HAd+wJYIMP8a6NWPiXR9/0gh25slXcLHBSrAtRWXU9rMnh6mEBfGGRfBe1yuw8TQ -+HUf0LrL8YJTPWPRC4jWAP9NE7IILDzFAD+g/JZKmm9cEhqBbQR66SEbQpJEEh+h -gDObF2FdXRF8Ya/otSjiI18FcXrg6owSH+e22NKNTdOjJl0j40rM+EVm/fYUNgKx -FcJ68H42jC1NRJ56k87kC1EGmPXIOfh/N8MwOv2OHyekU5QTR5lUP3nLSS3+9z8m -f4egcBR/1sVIA+8+boWMQVH9Jt7e/UvpTWmDSNoN847OgFx7R1K3PMKeuMM+hXmp -izjWZqEbqJ9u1LhToYHW76Ya1QDUWawNQcBNFzzIZn6l1eiS5FS9XzBk0ygA9SJa -XXvWHmybhem1mMiZYL0fdMtpoVJyxCWG02wt0fYi0CCFjWZYnXUfz37jCgMgU5ML -7awN/qujryXvFI6siBYz7+HwykvRPrz9Z2lm/okkPjHuoBiUZ5DKWSRTN+WqZFRU -SR5d2j3Nup2DXg== -=nmjl ------END PGP SIGNATURE----- diff --git a/libreswan-5.3-helper-thread.patch b/libreswan-5.3-helper-thread.patch new file mode 100644 index 0000000..0693317 --- /dev/null +++ b/libreswan-5.3-helper-thread.patch @@ -0,0 +1,97 @@ +From eb31dda36d082e33749bc294d56ec493a094336d Mon Sep 17 00:00:00 2001 +From: Andrew Cagney +Date: Sun, 16 Nov 2025 10:33:53 -0500 +Subject: [PATCH] helpers: work around NSS by joining helper threads + +Much of the analysis and testing by Ondrej Moris + +NSS attaches stuff to the threads onexit queue that must +be run before the main thread exits (if it doesn't things +explode during shutdown). + +See: Race condition in helper_thread_stopped_callback() #2461 +See: PR_Cleanup() doesn't wait for pthread_create() threads +https://bugzilla.mozilla.org/show_bug.cgi?id=1992272 +--- + programs/pluto/server_pool.c | 42 +++++++++++++++++++++++++++++++----- + 1 file changed, 37 insertions(+), 5 deletions(-) + +diff --git a/programs/pluto/server_pool.c b/programs/pluto/server_pool.c +index 056a007301..5c7dc5e18d 100644 +--- a/programs/pluto/server_pool.c ++++ b/programs/pluto/server_pool.c +@@ -275,6 +275,22 @@ static void *helper_thread(void *arg) + dbg("helper %u: telling main thread that it is exiting", w->helper_id); + schedule_callback("helper stopped", deltatime(0), SOS_NOBODY, + helper_thread_stopped_callback, NULL); ++ /* ++ * Danger. This isn't the end. ++ * ++ * NSS still has stuff in thread-exit handlers to execute and ++ * there's no clean way of forcing its execution (and if it ++ * isn't allowed to run NSS crashes!). Hence, the main thread ++ * will need to wait for this thread to exit. ++ * ++ * But wait, there's more. The main thread also needs to keep ++ * the event loop running while these threads are exiting so ++ * ptread_join() needs to be called with care. ++ * ++ * See: Race condition in helper_thread_stopped_callback() #2461 ++ * See: PR_Cleanup() doesn't wait for pthread_create() threads ++ * https://bugzilla.mozilla.org/show_bug.cgi?id=1992272 ++ */ + return NULL; + } + +@@ -589,10 +605,6 @@ void start_server_helpers(uintmax_t nhelpers, struct logger *logger) + + /* + * Repeatedly nudge the helper threads until they all exit. +- * +- * Note that pthread_join() doesn't work here: an any-thread join may +- * end up joining an unrelated thread (for instance the CRL helper); +- * and a specific thread join may block waiting for the wrong thread. + */ + + static void (*server_helpers_stopped_callback)(void); +@@ -605,6 +617,17 @@ static void helper_thread_stopped_callback(const char *story UNUSED, + dbg("one helper thread exited, %u remaining", + helper_threads_started-helper_threads_stopped); + ++ /* ++ * Danger: ++ * ++ * Delay joining W.pid until all helper threads have exited. ++ * This way the event-loop is kept running. ++ * ++ * Even though W is on the exit path it still needs to execute ++ * NSS's thread exit code - who knows what that is doing and ++ * how long it will take - ++ */ ++ + /* wait for more? */ + if (helper_threads_started > helper_threads_stopped) { + /* poke threads waiting for work */ +@@ -612,9 +635,18 @@ static void helper_thread_stopped_callback(const char *story UNUSED, + return; + } + +- /* all done; cleanup */ ++ /* ++ * All done; cleanup ++ * ++ * All helper threads are on the exit war-path so, hopefully, ++ * this join will not block (but no telling what NSS did). ++ */ + for (unsigned h = 0; h < helper_threads_started; h++) { + struct helper_thread *w = &helper_threads[h]; ++ int e = pthread_join(w->pid, NULL); ++ if (e != 0) { ++ llog_errno(RC_LOG, w->logger, e, "WARNING: pthread_join() failed, "); ++ } + free_logger(&w->logger, HERE); + } + +-- +2.52.0 + diff --git a/libreswan-5.3-outstanding-ike-auth-crossing.patch b/libreswan-5.3-outstanding-ike-auth-crossing.patch new file mode 100644 index 0000000..cccf3a7 --- /dev/null +++ b/libreswan-5.3-outstanding-ike-auth-crossing.patch @@ -0,0 +1,1056 @@ +From f58dec967328c9b11b834c42b1506e2d261d8bb2 Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Fri, 21 Nov 2025 22:42:33 +0100 +Subject: [PATCH 1/7] Add option reject-simultaneous-ike-auth + +Newly added option (default values is yes) decides what to +do when IKE_AUTH request is received from the peer while having +an outstanding IKE_AUTH request for the same connection. + +Signed-off-by: Ondrej Moris +Signed-off-by: Andrew Cagney +--- + .../reject-simultaneous-ike-auth.xml | 21 +++++++++++++++++++ + configs/ipsec.conf.5.xml | 2 ++ + include/ipsecconf/keywords.h | 1 + + include/whack.h | 1 + + lib/libswan/ipsecconf/keywords.c | 1 + + lib/libswan/ipsecconf/starterwhack.c | 1 + + programs/pluto/connections.c | 6 ++++++ + programs/pluto/connections.h | 2 ++ + programs/whack/whack.c | 7 +++++++ + 9 files changed, 42 insertions(+) + create mode 100644 configs/d.ipsec.conf/reject-simultaneous-ike-auth.xml + +diff --git a/configs/d.ipsec.conf/reject-simultaneous-ike-auth.xml b/configs/d.ipsec.conf/reject-simultaneous-ike-auth.xml +new file mode 100644 +index 0000000000..7c3f68c42d +--- /dev/null ++++ b/configs/d.ipsec.conf/reject-simultaneous-ike-auth.xml +@@ -0,0 +1,21 @@ ++ ++ ++ ++ ++ ++ ++ ++ When libreswan receives IKE_AUTH request from the peer while having ++ an outstanding IKE_AUTH request for the same connection, reject with ++ AUTHENTICATION_FAILED to avoid potential SA mismatch issues. This only ++ applies to permanent IKEv2 connections so that the revival mechanism ++ ensures connection retry. ++ ++ ++ ++ The accepted values are (the default) or ++ . ++ ++ ++ ++ +diff --git a/configs/ipsec.conf.5.xml b/configs/ipsec.conf.5.xml +index 03f04106ae..eb0f69007d 100644 +--- a/configs/ipsec.conf.5.xml ++++ b/configs/ipsec.conf.5.xml +@@ -25,6 +25,7 @@ + + + ++ + + + +@@ -377,6 +378,7 @@ + &failureshunt; + &negotiationshunt; + &debug; ++ &reject-simultaneous-ike-auth; + + + +diff --git a/include/ipsecconf/keywords.h b/include/ipsecconf/keywords.h +index bdbc00ee98..21b5954c52 100644 +--- a/include/ipsecconf/keywords.h ++++ b/include/ipsecconf/keywords.h +@@ -201,6 +201,7 @@ enum config_conn_keyword { + KWS_CISCO_SPLIT, /* send cisco unity VID */ + + KWYN_SEND_ESP_TFC_PADDING_NOT_SUPPORTED, ++ KWYN_REJECT_SIMULTANEOUS_IKE_AUTH, + KWYN_FAKE_STRONGSWAN, /* send strongswan VID (required for twofish/serpent) */ + KWYN_SEND_VENDORID, /* per conn sending of our own libreswan vendorid */ + KNCF_IKEPAD, /* pad IKE packets to 4 bytes */ +diff --git a/include/whack.h b/include/whack.h +index 85f38da0aa..76d6eb2908 100644 +--- a/include/whack.h ++++ b/include/whack.h +@@ -409,6 +409,7 @@ struct whack_message { + const char *priority; + const char *tfc; + enum yn_options send_esp_tfc_padding_not_supported; ++ enum yn_options reject_simultaneous_ike_auth; + + enum yn_options iptfs; + enum yn_options iptfs_fragmentation; +diff --git a/lib/libswan/ipsecconf/keywords.c b/lib/libswan/ipsecconf/keywords.c +index 1e42bf2a4a..6c662a9688 100644 +--- a/lib/libswan/ipsecconf/keywords.c ++++ b/lib/libswan/ipsecconf/keywords.c +@@ -162,6 +162,7 @@ static const struct keyword_def config_conn_keyword[] = { + + K("initial-contact", LEMPTY, kt_sparse_name, KWYN_INITIAL_CONTACT, .sparse_names = &yn_option_names), + K("send-esp-tfc-padding-not-supported", LEMPTY, kt_sparse_name, KWYN_SEND_ESP_TFC_PADDING_NOT_SUPPORTED, .sparse_names = &yn_option_names), ++ K("reject-simultaneous-ike-auth", LEMPTY, kt_sparse_name, KWYN_REJECT_SIMULTANEOUS_IKE_AUTH, .sparse_names = &yn_option_names), + + K("iptfs", LEMPTY, kt_sparse_name, KWYN_IPTFS, .sparse_names = &yn_option_names), + K("iptfs-fragmentation", LEMPTY, kt_sparse_name, KWYN_IPTFS_FRAGMENTATION, .sparse_names = &yn_option_names), +diff --git a/lib/libswan/ipsecconf/starterwhack.c b/lib/libswan/ipsecconf/starterwhack.c +index 0e09a8e8d5..9a73769c15 100644 +--- a/lib/libswan/ipsecconf/starterwhack.c ++++ b/lib/libswan/ipsecconf/starterwhack.c +@@ -232,6 +232,7 @@ int starter_whack_add_conn(const char *ctlsocket, + conn->values[KWYN_SEND_ESP_TFC_PADDING_NOT_SUPPORTED].option; + msg.nflog_group = conn->values[KWS_NFLOG_GROUP].string; + msg.reqid = conn->values[KWS_REQID].string; ++ msg.reject_simultaneous_ike_auth = conn->values[KWYN_REJECT_SIMULTANEOUS_IKE_AUTH].option; + + if (conn->values[KNCF_TCP_REMOTEPORT].set) { + msg.tcp_remoteport = conn->values[KNCF_TCP_REMOTEPORT].option; +diff --git a/programs/pluto/connections.c b/programs/pluto/connections.c +index e949f56ebd..ca98dfb1e9 100644 +--- a/programs/pluto/connections.c ++++ b/programs/pluto/connections.c +@@ -4894,6 +4894,12 @@ static diag_t extract_connection(const struct whack_message *wm, + wm->send_esp_tfc_padding_not_supported, + YN_NO, wm, c->logger); + ++ if (wm->reject_simultaneous_ike_auth && ike_version < IKEv2) { ++ return diag("cannot specify reject-simultaneous-ike-auth for IKEv1"); ++ } ++ config->reject_simultaneous_ike_auth = extract_yn("", "reject-simultaneous-ike-auth", ++ wm->reject_simultaneous_ike_auth, /*value_when_unset*/YN_YES, wm, c->logger); ++ + /* + * Since security labels use the same REQID for everything, + * pre-assign it. +diff --git a/programs/pluto/connections.h b/programs/pluto/connections.h +index 0910fc9930..cd9cb331b6 100644 +--- a/programs/pluto/connections.h ++++ b/programs/pluto/connections.h +@@ -431,6 +431,8 @@ struct config { + uint32_t id; + } ipsec_interface; + ++ bool reject_simultaneous_ike_auth; ++ + struct end_config end[END_ROOF]; + }; + +diff --git a/programs/whack/whack.c b/programs/whack/whack.c +index a5a95045bf..5e47e009b8 100644 +--- a/programs/whack/whack.c ++++ b/programs/whack/whack.c +@@ -111,6 +111,7 @@ static void help(void) + " [--mtu ] \\\n" + " [--priority ] [--reqid ] \\\n" + " [--tfc ] [--send-esp-tfc-padding-not-supported] \\\n" ++ " [--reject-simultaneous-ike-auth] \\\n" + " [--iptfs[={yes,no}] \\\n" + " [--iptfs-fragmentation[={yes,no}]] \\\n" + " [--iptfs-packet-size ] \\\n" +@@ -516,6 +517,7 @@ enum opt { + CD_PRIORITY, + CD_TFC, + CD_SEND_ESP_TFC_PADDING_NOT_SUPPORTED, ++ CD_REJECT_SIMULTANEOUS_IKE_AUTH, + CD_PFS, + CD_REQID, + CD_NFLOG_GROUP, +@@ -898,6 +900,7 @@ const struct option optarg_options[] = { + { "tfc\0", required_argument, NULL, CD_TFC }, + { "send-esp-tfc-padding-not-supported\0yes|no", optional_argument, NULL, CD_SEND_ESP_TFC_PADDING_NOT_SUPPORTED }, + { "send-no-esp-tfc\0", no_argument, NULL, CD_SEND_ESP_TFC_PADDING_NOT_SUPPORTED }, ++ { "reject-simultaneous-ike-auth\0yes|no", optional_argument, NULL, CD_REJECT_SIMULTANEOUS_IKE_AUTH }, + { "pfs\0", optional_argument, NULL, CD_PFS }, + { "reqid\01-65535", required_argument, NULL, CD_REQID }, + #ifdef USE_NFLOG +@@ -2085,6 +2088,10 @@ int main(int argc, char **argv) + optarg_yn(logger, YN_YES); + continue; + ++ case CD_REJECT_SIMULTANEOUS_IKE_AUTH: /* --reject-simultaneous-ike-auth */ ++ msg.reject_simultaneous_ike_auth = optarg_yn(logger, YN_YES); ++ continue; ++ + case CD_PFS: /* --pfs */ + msg.pfs = optarg_yn(logger, YN_YES); + continue; +-- +2.52.0 + + +From 31ce0692217e18c3e5ed6cc2cded7c937b7ae1c9 Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Fri, 13 Mar 2026 15:00:05 +0100 +Subject: [PATCH 2/7] ikev2: reject simultaneous IKE_AUTH requests + +If initiator has another IKE SA with IKE_AUTH request +outstanding for the same permanent connection then send +AUTHENTICATION_FAILED instead of IKE_AUTH response for this +IKE_AUTH request and terminate current IKE SA. This is to +prevent potential crossing streams scenario. + +This is now the default behavior and can be disabled by +a connection option reject-simultaneous-ike-auth. + +Signed-off-by: Ondrej Moris +--- + programs/pluto/ikev2_auth.c | 53 +++++++++++++++++++++++++++++++++ + programs/pluto/ikev2_auth.h | 3 ++ + programs/pluto/ikev2_child.c | 13 ++++++++ + programs/pluto/ikev2_ike_auth.c | 13 ++++++++ + 4 files changed, 82 insertions(+) + +diff --git a/programs/pluto/ikev2_auth.c b/programs/pluto/ikev2_auth.c +index 6e89bce58b..2e1b6df655 100644 +--- a/programs/pluto/ikev2_auth.c ++++ b/programs/pluto/ikev2_auth.c +@@ -1044,3 +1044,56 @@ lset_t proposed_v2AUTH(struct ike_sa *ike, + } + } + } ++ ++/* ++ * Check if a given permanent connection has another IKE SA with ++ * IKE_AUTH request outstanding. This is useful to detect potential ++ * IKE_AUTH crossing streams scenarios. ++ */ ++bool has_outstanding_ike_auth_request(const struct connection *c, ++ const struct ike_sa *ike, ++ const struct msg_digest *md) ++{ ++ /* Check can be disabled in a connection config */ ++ if (!c->config->reject_simultaneous_ike_auth) { ++ return false; ++ } ++ ++ /* Connection must be permanent and request must be incoming */ ++ if (v2_msg_role(md) != MESSAGE_REQUEST || !is_permanent(c)) { ++ return false; ++ } ++ ++ struct state_filter sf = { ++ .connection_serialno = c->serialno, ++ .search = { ++ .order = NEW2OLD, ++ .verbose.logger = ike->sa.logger, ++ .where = HERE, ++ }, ++ }; ++ ++ while (next_state(&sf)) { ++ if (!IS_IKE_SA(sf.st)) { ++ continue; ++ } ++ ++ struct ike_sa *simultaneous_ike = pexpect_ike_sa(sf.st); ++ if (simultaneous_ike == NULL || simultaneous_ike == ike) { ++ continue; ++ } else if (simultaneous_ike->sa.st_sa_role != SA_INITIATOR) { ++ continue; ++ } else if (!v2_msgid_request_outstanding(simultaneous_ike)) { ++ continue; ++ } ++ ++ const struct v2_exchange *outstanding_request = ++ simultaneous_ike->sa.st_v2_msgid_windows.initiator.exchange; ++ if (outstanding_request != NULL && outstanding_request->type == ISAKMP_v2_IKE_AUTH) { ++ llog(RC_LOG, ike->sa.logger, "IKE SA "PRI_SO" has outstanding IKE_AUTH request", ++ pri_so(simultaneous_ike->sa.st_serialno)); ++ return true; ++ } ++ } ++ return false; ++} +diff --git a/programs/pluto/ikev2_auth.h b/programs/pluto/ikev2_auth.h +index 13ee71c36d..d616534507 100644 +--- a/programs/pluto/ikev2_auth.h ++++ b/programs/pluto/ikev2_auth.h +@@ -85,4 +85,7 @@ struct crypt_mac v2_remote_id_hash(const struct ike_sa *ike, const char *why, + + lset_t proposed_v2AUTH(struct ike_sa *ike, struct msg_digest *md); + ++bool has_outstanding_ike_auth_request(const struct connection *c, ++ const struct ike_sa *ike, ++ const struct msg_digest *md); + #endif +diff --git a/programs/pluto/ikev2_child.c b/programs/pluto/ikev2_child.c +index 49aaff98a2..e53f53f4b3 100644 +--- a/programs/pluto/ikev2_child.c ++++ b/programs/pluto/ikev2_child.c +@@ -68,6 +68,7 @@ + #include "ikev2_notification.h" + #include "iface.h" + #include "nat_traversal.h" ++#include "ikev2_auth.h" + + static bool emit_v2_child_response_payloads(struct ike_sa *ike, + const struct child_sa *child, +@@ -1029,6 +1030,18 @@ static v2_notification_t process_v2_IKE_AUTH_request_child_sa_payloads(struct ik + ldbg_sa(child, "skipping TS processing, mainly to stop tests failing but rumored to cause connection flips?!?"); + } + ++ /* It is possible that Child SA switched to permanent connection ++ * where initiator has IKE SA with IKE_AUTH request outstanding, ++ * in that case send AUTHENTICATION_FAILED and terminate this IKE SA. ++ * This is to prevent potential crossing streams scenario for ++ * IKE AUTH exchange. ++ */ ++ if (has_outstanding_ike_auth_request(child->sa.st_connection, ike, md)) { ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, ++ empty_shunk, ENCRYPTED_PAYLOAD); ++ return v2N_AUTHENTICATION_FAILED; ++ } ++ + n = process_childs_v2SA_payload("IKE_AUTH responder matching remote ESP/AH proposals", + ike, child, md, + child->sa.st_connection->config->child_sa.v2_ike_auth_proposals, +diff --git a/programs/pluto/ikev2_ike_auth.c b/programs/pluto/ikev2_ike_auth.c +index ba5aeace88..f70c06e9ef 100644 +--- a/programs/pluto/ikev2_ike_auth.c ++++ b/programs/pluto/ikev2_ike_auth.c +@@ -683,6 +683,19 @@ stf_status process_v2_IKE_AUTH_request_standard_payloads(struct ike_sa *ike, str + */ + + const struct connection *c = ike->sa.st_connection; ++ ++ /* If initiator has another IKE SA with IKE_AUTH request ++ * outstanding for the same permanent connection then send ++ * AUTHENTICATION_FAILED instead of IKE_AUTH response for this ++ * IKE_AUTH request and terminate current IKE SA. This is to ++ * prevent potential crossing streams scenario. ++ */ ++ if (has_outstanding_ike_auth_request(c, ike, md)) { ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, ++ empty_shunk, ENCRYPTED_PAYLOAD); ++ return STF_FATAL; ++ } ++ + bool found_ppk = false; + + /* +-- +2.52.0 + + +From 877c8c9c2fd963bb23842cdddf798b34f7e6d42f Mon Sep 17 00:00:00 2001 +From: Daiki Ueno +Date: Sat, 17 Jan 2026 13:28:39 +0900 +Subject: [PATCH 3/7] pluto: reject crossing IKE_AUTH request only for the one + side + +When an IKE_AUTH request is crossed, both sides previously rejected it +by sending AUTHENTICATION_FAILED, resulting in no IKE_SA being +established. This patch relaxes the condition and make the one outlive +the other. + +Suggested-by: Ondrej Moris +Signed-off-by: Daiki Ueno +--- + include/ike_spi.h | 1 + + programs/pluto/ike_spi.c | 11 ++++++++++ + programs/pluto/ikev2_auth.c | 10 ++++----- + programs/pluto/ikev2_auth.h | 2 +- + programs/pluto/ikev2_child.c | 35 ++++++++++++++++++++++++++++---- + programs/pluto/ikev2_ike_auth.c | 36 +++++++++++++++++++++++++++++---- + 6 files changed, 81 insertions(+), 14 deletions(-) + +diff --git a/include/ike_spi.h b/include/ike_spi.h +index 0c05542bae..376c120c0e 100644 +--- a/include/ike_spi.h ++++ b/include/ike_spi.h +@@ -39,6 +39,7 @@ typedef struct { + } ike_spis_t; + + bool ike_spis_eq(const ike_spis_t *lhs, const ike_spis_t *rhs); ++bool ike_spis_gt(const ike_spis_t *lhs, const ike_spis_t *rhs); + + /* + * Need to handle two cases: +diff --git a/programs/pluto/ike_spi.c b/programs/pluto/ike_spi.c +index 312c835bed..d7bb73a6e8 100644 +--- a/programs/pluto/ike_spi.c ++++ b/programs/pluto/ike_spi.c +@@ -42,6 +42,17 @@ bool ike_spis_eq(const ike_spis_t *lhs, const ike_spis_t *rhs) + ike_spi_eq(&lhs->responder, &rhs->responder)); + } + ++bool ike_spis_gt(const ike_spis_t *lhs, const ike_spis_t *rhs) ++{ ++ int d = memcmp(lhs->initiator.bytes, rhs->initiator.bytes, ++ sizeof(lhs->initiator.bytes)); ++ if (d == 0) { ++ d = memcmp(lhs->responder.bytes, rhs->responder.bytes, ++ sizeof(lhs->responder.bytes)); ++ } ++ return d > 0; ++} ++ + static struct { + uint8_t bytes[SHA2_256_DIGEST_SIZE]; + } ike_spi_secret; +diff --git a/programs/pluto/ikev2_auth.c b/programs/pluto/ikev2_auth.c +index 2e1b6df655..ee36ef8b03 100644 +--- a/programs/pluto/ikev2_auth.c ++++ b/programs/pluto/ikev2_auth.c +@@ -1050,18 +1050,18 @@ lset_t proposed_v2AUTH(struct ike_sa *ike, + * IKE_AUTH request outstanding. This is useful to detect potential + * IKE_AUTH crossing streams scenarios. + */ +-bool has_outstanding_ike_auth_request(const struct connection *c, ++struct ike_sa *get_sa_with_outstanding_ike_auth_request(const struct connection *c, + const struct ike_sa *ike, + const struct msg_digest *md) + { + /* Check can be disabled in a connection config */ + if (!c->config->reject_simultaneous_ike_auth) { +- return false; ++ return NULL; + } + + /* Connection must be permanent and request must be incoming */ + if (v2_msg_role(md) != MESSAGE_REQUEST || !is_permanent(c)) { +- return false; ++ return NULL; + } + + struct state_filter sf = { +@@ -1092,8 +1092,8 @@ bool has_outstanding_ike_auth_request(const struct connection *c, + if (outstanding_request != NULL && outstanding_request->type == ISAKMP_v2_IKE_AUTH) { + llog(RC_LOG, ike->sa.logger, "IKE SA "PRI_SO" has outstanding IKE_AUTH request", + pri_so(simultaneous_ike->sa.st_serialno)); +- return true; ++ return simultaneous_ike; + } + } +- return false; ++ return NULL; + } +diff --git a/programs/pluto/ikev2_auth.h b/programs/pluto/ikev2_auth.h +index d616534507..bb659ce88f 100644 +--- a/programs/pluto/ikev2_auth.h ++++ b/programs/pluto/ikev2_auth.h +@@ -85,7 +85,7 @@ struct crypt_mac v2_remote_id_hash(const struct ike_sa *ike, const char *why, + + lset_t proposed_v2AUTH(struct ike_sa *ike, struct msg_digest *md); + +-bool has_outstanding_ike_auth_request(const struct connection *c, ++struct ike_sa *get_sa_with_outstanding_ike_auth_request(const struct connection *c, + const struct ike_sa *ike, + const struct msg_digest *md); + #endif +diff --git a/programs/pluto/ikev2_child.c b/programs/pluto/ikev2_child.c +index e53f53f4b3..09b5862cd0 100644 +--- a/programs/pluto/ikev2_child.c ++++ b/programs/pluto/ikev2_child.c +@@ -69,6 +69,7 @@ + #include "iface.h" + #include "nat_traversal.h" + #include "ikev2_auth.h" ++#include "terminate.h" + + static bool emit_v2_child_response_payloads(struct ike_sa *ike, + const struct child_sa *child, +@@ -1036,10 +1037,36 @@ static v2_notification_t process_v2_IKE_AUTH_request_child_sa_payloads(struct ik + * This is to prevent potential crossing streams scenario for + * IKE AUTH exchange. + */ +- if (has_outstanding_ike_auth_request(child->sa.st_connection, ike, md)) { +- record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, +- empty_shunk, ENCRYPTED_PAYLOAD); +- return v2N_AUTHENTICATION_FAILED; ++ struct ike_sa *simultaneous_ike = ++ get_sa_with_outstanding_ike_auth_request(child->sa.st_connection, ike, md); ++ if (simultaneous_ike != NULL) { ++ /* Compare our initiated SPI vs their initiated SPI ++ * from the message. Note that the ordering doesn't ++ * matter, but it ensures that both sides have the ++ * same consensus on which IKE SA should be dropped. ++ */ ++ if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, ++ &ike->sa.st_ike_spis)) { ++ /* Our IKE SA with oustanding IKE AUTH request ++ * has SPI higher, delete this IKE SA, keep ++ * the simultaneous_ike. ++ */ ++ ldbg(ike->sa.logger, "preferring the outstanding "PRI_SO" over the current "PRI_SO, ++ pri_so(simultaneous_ike->sa.st_serialno), ++ pri_so(ike->sa.st_serialno)); ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, ++ empty_shunk, ENCRYPTED_PAYLOAD); ++ return v2N_AUTHENTICATION_FAILED; ++ } else { ++ /* IKE SA for the current IKE AUTH request has ++ * SPI higher, continue with IKE AUTH reply ++ * and drop the other one. ++ */ ++ ldbg(ike->sa.logger, "preferring the current "PRI_SO" over the outstanding "PRI_SO"", ++ pri_so(ike->sa.st_serialno), ++ pri_so(simultaneous_ike->sa.st_serialno)); ++ terminate_ike_family(&simultaneous_ike, REASON_SUPERSEDED_BY_NEW_SA, HERE); ++ } + } + + n = process_childs_v2SA_payload("IKE_AUTH responder matching remote ESP/AH proposals", +diff --git a/programs/pluto/ikev2_ike_auth.c b/programs/pluto/ikev2_ike_auth.c +index f70c06e9ef..406469dc75 100644 +--- a/programs/pluto/ikev2_ike_auth.c ++++ b/programs/pluto/ikev2_ike_auth.c +@@ -76,6 +76,8 @@ + #include "ikev2_notification.h" + #include "peer_id.h" + #include "ddos.h" ++#include "ikev2_nat.h" ++#include "terminate.h" + + static ikev2_state_transition_fn process_v2_IKE_AUTH_request; + +@@ -690,10 +692,36 @@ stf_status process_v2_IKE_AUTH_request_standard_payloads(struct ike_sa *ike, str + * IKE_AUTH request and terminate current IKE SA. This is to + * prevent potential crossing streams scenario. + */ +- if (has_outstanding_ike_auth_request(c, ike, md)) { +- record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, +- empty_shunk, ENCRYPTED_PAYLOAD); +- return STF_FATAL; ++ struct ike_sa *simultaneous_ike = ++ get_sa_with_outstanding_ike_auth_request(c, ike, md); ++ if (simultaneous_ike != NULL) { ++ /* Compare our initiated SPIs vs their initiated SPIs ++ * from the message. Note that the ordering doesn't ++ * matter, but it ensures that both sides have the ++ * same consensus on which IKE SA should be dropped. ++ */ ++ if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, ++ &ike->sa.st_ike_spis)) { ++ /* Our IKE SA with oustanding IKE AUTH request ++ * has SPI higher, delete this IKE SA, keep ++ * the simultaneous_ike. ++ */ ++ ldbg(ike->sa.logger, "preferring the outstanding "PRI_SO" over the current "PRI_SO, ++ pri_so(simultaneous_ike->sa.st_serialno), ++ pri_so(ike->sa.st_serialno)); ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, ++ empty_shunk, ENCRYPTED_PAYLOAD); ++ return STF_FATAL; ++ } else { ++ /* IKE SA for the current IKE AUTH request has ++ * SPI higher, continue with IKE AUTH reply ++ * and drop the other one. ++ */ ++ ldbg(ike->sa.logger, "preferring the current "PRI_SO" over the outstanding "PRI_SO"", ++ pri_so(ike->sa.st_serialno), ++ pri_so(simultaneous_ike->sa.st_serialno)); ++ terminate_ike_family(&simultaneous_ike, REASON_SUPERSEDED_BY_NEW_SA, HERE); ++ } + } + + bool found_ppk = false; +-- +2.52.0 + + +From f35e32abc6b1e8d027ce811f0954f4b5de618f68 Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Sun, 25 Jan 2026 19:41:25 +0100 +Subject: [PATCH 4/7] ikev2: refactor simultaneous IKE_AUTH logic + +1. Moved decision which IKE to reject into the function. +2. Renamed function to better capture its new structure. + +Signed-off-by: Ondrej Moris +--- + programs/pluto/ikev2_auth.c | 80 +++++++++++++++++++++++---------- + programs/pluto/ikev2_auth.h | 6 +-- + programs/pluto/ikev2_child.c | 44 +++++------------- + programs/pluto/ikev2_ike_auth.c | 43 +++++------------- + 4 files changed, 80 insertions(+), 93 deletions(-) + +diff --git a/programs/pluto/ikev2_auth.c b/programs/pluto/ikev2_auth.c +index ee36ef8b03..3c61e45cdd 100644 +--- a/programs/pluto/ikev2_auth.c ++++ b/programs/pluto/ikev2_auth.c +@@ -1046,13 +1046,23 @@ lset_t proposed_v2AUTH(struct ike_sa *ike, + } + + /* +- * Check if a given permanent connection has another IKE SA with +- * IKE_AUTH request outstanding. This is useful to detect potential +- * IKE_AUTH crossing streams scenarios. ++ * Check relevant simultaneous IKE_AUTH requests and decide ++ * which IKE SA is going to be rejected (if any). This is goint ++ * to be called when processing IKE_AUTH request from the responder ++ * ++ * Relevant: ++ * - same connection ++ * - initiating IKE ++ * - non-established with IKE_AUTH request waiting for reply ++ * ++ * Returns: ++ * - ike: reject current IKE_AUTH request ++ * - other IKE SA than ike: terminate it, keep ike ++ * - NULL: no simultaneous IKE SA + */ +-struct ike_sa *get_sa_with_outstanding_ike_auth_request(const struct connection *c, +- const struct ike_sa *ike, +- const struct msg_digest *md) ++struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, ++ const struct ike_sa *ike, ++ const struct msg_digest *md) + { + /* Check can be disabled in a connection config */ + if (!c->config->reject_simultaneous_ike_auth) { +@@ -1065,35 +1075,57 @@ struct ike_sa *get_sa_with_outstanding_ike_auth_request(const struct connection + } + + struct state_filter sf = { +- .connection_serialno = c->serialno, +- .search = { +- .order = NEW2OLD, +- .verbose.logger = ike->sa.logger, +- .where = HERE, +- }, ++ .connection_serialno = c->serialno, ++ .search = { ++ .order = NEW2OLD, ++ .verbose.logger = ike->sa.logger, ++ .where = HERE, ++ }, + }; + ++ struct ike_sa *simultaneous_ike = NULL; ++ + while (next_state(&sf)) { + if (!IS_IKE_SA(sf.st)) { + continue; + } + +- struct ike_sa *simultaneous_ike = pexpect_ike_sa(sf.st); +- if (simultaneous_ike == NULL || simultaneous_ike == ike) { +- continue; +- } else if (simultaneous_ike->sa.st_sa_role != SA_INITIATOR) { ++ struct ike_sa *candidate = pexpect_ike_sa(sf.st); ++ ++ if (candidate == NULL || candidate == ike) { + continue; +- } else if (!v2_msgid_request_outstanding(simultaneous_ike)) { ++ } else if (candidate->sa.st_sa_role != SA_INITIATOR) { + continue; + } + +- const struct v2_exchange *outstanding_request = +- simultaneous_ike->sa.st_v2_msgid_windows.initiator.exchange; +- if (outstanding_request != NULL && outstanding_request->type == ISAKMP_v2_IKE_AUTH) { +- llog(RC_LOG, ike->sa.logger, "IKE SA "PRI_SO" has outstanding IKE_AUTH request", +- pri_so(simultaneous_ike->sa.st_serialno)); +- return simultaneous_ike; ++ /* Does candidate has IKE_AUTH request outstanding? */ ++ if (v2_msgid_request_outstanding(candidate)) { ++ const struct v2_exchange *outstanding_request = candidate->sa.st_v2_msgid_windows.initiator.exchange; ++ if (outstanding_request != NULL && outstanding_request->type == ISAKMP_v2_IKE_AUTH) { ++ llog(RC_LOG, ike->sa.logger, "IKE SA "PRI_SO" has outstanding IKE_AUTH request", ++ pri_so(candidate->sa.st_serialno)); ++ simultaneous_ike = candidate; ++ break; ++ } + } + } +- return NULL; ++ ++ /* No simultaneous IKE found */ ++ if (simultaneous_ike == NULL) { ++ return NULL; ++ } ++ ++ /* Simultaneous IKE found, we need to decide if we keep that one or current IKE. ++ * ++ * We keep one with higher SPI. ++ */ ++ if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, &ike->sa.st_ike_spis)) { ++ ldbg(ike->sa.logger, "preferring the simultaneous IKE SA "PRI_SO" over the current IKE SA "PRI_SO, ++ pri_so(simultaneous_ike->sa.st_serialno), pri_so(ike->sa.st_serialno)); ++ return (struct ike_sa *) ike; ++ } else { ++ ldbg(ike->sa.logger, "preferring the current IKE SA "PRI_SO" over the simultanoues IKE SA "PRI_SO"", ++ pri_so(ike->sa.st_serialno), pri_so(simultaneous_ike->sa.st_serialno)); ++ return simultaneous_ike; ++ } + } +diff --git a/programs/pluto/ikev2_auth.h b/programs/pluto/ikev2_auth.h +index bb659ce88f..1c25479ed7 100644 +--- a/programs/pluto/ikev2_auth.h ++++ b/programs/pluto/ikev2_auth.h +@@ -85,7 +85,7 @@ struct crypt_mac v2_remote_id_hash(const struct ike_sa *ike, const char *why, + + lset_t proposed_v2AUTH(struct ike_sa *ike, struct msg_digest *md); + +-struct ike_sa *get_sa_with_outstanding_ike_auth_request(const struct connection *c, +- const struct ike_sa *ike, +- const struct msg_digest *md); ++struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, ++ const struct ike_sa *ike, ++ const struct msg_digest *md); + #endif +diff --git a/programs/pluto/ikev2_child.c b/programs/pluto/ikev2_child.c +index 09b5862cd0..9763112ddf 100644 +--- a/programs/pluto/ikev2_child.c ++++ b/programs/pluto/ikev2_child.c +@@ -1032,41 +1032,19 @@ static v2_notification_t process_v2_IKE_AUTH_request_child_sa_payloads(struct ik + } + + /* It is possible that Child SA switched to permanent connection +- * where initiator has IKE SA with IKE_AUTH request outstanding, +- * in that case send AUTHENTICATION_FAILED and terminate this IKE SA. +- * This is to prevent potential crossing streams scenario for ++ * where initiator has IKE SA with IKE_AUTH request outstanding ++ * (or recently established IKE SA) in that case keep only one of ++ * them. This is to prevent potential crossing streams scenario for + * IKE AUTH exchange. + */ +- struct ike_sa *simultaneous_ike = +- get_sa_with_outstanding_ike_auth_request(child->sa.st_connection, ike, md); +- if (simultaneous_ike != NULL) { +- /* Compare our initiated SPI vs their initiated SPI +- * from the message. Note that the ordering doesn't +- * matter, but it ensures that both sides have the +- * same consensus on which IKE SA should be dropped. +- */ +- if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, +- &ike->sa.st_ike_spis)) { +- /* Our IKE SA with oustanding IKE AUTH request +- * has SPI higher, delete this IKE SA, keep +- * the simultaneous_ike. +- */ +- ldbg(ike->sa.logger, "preferring the outstanding "PRI_SO" over the current "PRI_SO, +- pri_so(simultaneous_ike->sa.st_serialno), +- pri_so(ike->sa.st_serialno)); +- record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, +- empty_shunk, ENCRYPTED_PAYLOAD); +- return v2N_AUTHENTICATION_FAILED; +- } else { +- /* IKE SA for the current IKE AUTH request has +- * SPI higher, continue with IKE AUTH reply +- * and drop the other one. +- */ +- ldbg(ike->sa.logger, "preferring the current "PRI_SO" over the outstanding "PRI_SO"", +- pri_so(ike->sa.st_serialno), +- pri_so(simultaneous_ike->sa.st_serialno)); +- terminate_ike_family(&simultaneous_ike, REASON_SUPERSEDED_BY_NEW_SA, HERE); +- } ++ struct ike_sa *ike_to_reject = check_simultaneous_ike_auth(child->sa.st_connection, ike, md); ++ if (ike_to_reject == ike) { ++ /* Reject current IKE_AUTH request */ ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, empty_shunk, ENCRYPTED_PAYLOAD); ++ return v2N_AUTHENTICATION_FAILED; ++ } else if (ike_to_reject != NULL) { ++ /* Terminate the other IKE SA, continue with current */ ++ terminate_ike_family(&ike_to_reject, REASON_SUPERSEDED_BY_NEW_SA, HERE); + } + + n = process_childs_v2SA_payload("IKE_AUTH responder matching remote ESP/AH proposals", +diff --git a/programs/pluto/ikev2_ike_auth.c b/programs/pluto/ikev2_ike_auth.c +index 406469dc75..c9eb40e420 100644 +--- a/programs/pluto/ikev2_ike_auth.c ++++ b/programs/pluto/ikev2_ike_auth.c +@@ -687,41 +687,18 @@ stf_status process_v2_IKE_AUTH_request_standard_payloads(struct ike_sa *ike, str + const struct connection *c = ike->sa.st_connection; + + /* If initiator has another IKE SA with IKE_AUTH request +- * outstanding for the same permanent connection then send +- * AUTHENTICATION_FAILED instead of IKE_AUTH response for this +- * IKE_AUTH request and terminate current IKE SA. This is to ++ * outstanding for the same permanent connection (or recently ++ * established one) then we keep only one of them. This is to + * prevent potential crossing streams scenario. + */ +- struct ike_sa *simultaneous_ike = +- get_sa_with_outstanding_ike_auth_request(c, ike, md); +- if (simultaneous_ike != NULL) { +- /* Compare our initiated SPIs vs their initiated SPIs +- * from the message. Note that the ordering doesn't +- * matter, but it ensures that both sides have the +- * same consensus on which IKE SA should be dropped. +- */ +- if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, +- &ike->sa.st_ike_spis)) { +- /* Our IKE SA with oustanding IKE AUTH request +- * has SPI higher, delete this IKE SA, keep +- * the simultaneous_ike. +- */ +- ldbg(ike->sa.logger, "preferring the outstanding "PRI_SO" over the current "PRI_SO, +- pri_so(simultaneous_ike->sa.st_serialno), +- pri_so(ike->sa.st_serialno)); +- record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, +- empty_shunk, ENCRYPTED_PAYLOAD); +- return STF_FATAL; +- } else { +- /* IKE SA for the current IKE AUTH request has +- * SPI higher, continue with IKE AUTH reply +- * and drop the other one. +- */ +- ldbg(ike->sa.logger, "preferring the current "PRI_SO" over the outstanding "PRI_SO"", +- pri_so(ike->sa.st_serialno), +- pri_so(simultaneous_ike->sa.st_serialno)); +- terminate_ike_family(&simultaneous_ike, REASON_SUPERSEDED_BY_NEW_SA, HERE); +- } ++ struct ike_sa *ike_to_reject = check_simultaneous_ike_auth(c, ike, md); ++ if (ike_to_reject == ike) { ++ /* Reject current IKE_AUTH request */ ++ record_v2N_response(ike->sa.logger, ike, md, v2N_AUTHENTICATION_FAILED, empty_shunk, ENCRYPTED_PAYLOAD); ++ return STF_FATAL; ++ } else if (ike_to_reject != NULL) { ++ /* Terminate the other IKE SA, continue with current */ ++ terminate_ike_family(&ike_to_reject, REASON_SUPERSEDED_BY_NEW_SA, HERE); + } + + bool found_ppk = false; +-- +2.52.0 + + +From c956e30d304ac460559116dbf53487ebd2696f8a Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Sun, 25 Jan 2026 19:46:45 +0100 +Subject: [PATCH 5/7] ikev2: extend check for simultaneous IKE_AUTH + +On initiator, if simultaneous IKE receives IKE_AUTH response +shortly before the current IKE_AUTH request is received, +then this simultaneous IKE gets established and hence it +incorrectly missed by the current check. This commit extends +the check for simultaneous IKE to capture such cases and if +there is such simultaneous IKE it will be keps and the current +one will be dropped. + +Signed-off-by: Ondrej Moris +--- + programs/pluto/ikev2_auth.c | 21 ++++++++++++++++++++- + 1 file changed, 20 insertions(+), 1 deletion(-) + +diff --git a/programs/pluto/ikev2_auth.c b/programs/pluto/ikev2_auth.c +index 3c61e45cdd..10d583b4c1 100644 +--- a/programs/pluto/ikev2_auth.c ++++ b/programs/pluto/ikev2_auth.c +@@ -1054,6 +1054,7 @@ lset_t proposed_v2AUTH(struct ike_sa *ike, + * - same connection + * - initiating IKE + * - non-established with IKE_AUTH request waiting for reply ++ * - established within 1 second of processing this IKE_AUTH request + * + * Returns: + * - ike: reject current IKE_AUTH request +@@ -1108,6 +1109,17 @@ struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, + break; + } + } ++ ++ /* Was candidate established within the last second of the current IKE_AUTH request arrival? */ ++ if (candidate->sa.st_state->kind == STATE_V2_ESTABLISHED_IKE_SA) { ++ deltatime_t age = monotime_diff(mononow(), candidate->sa.st_v2_msgid_windows.initiator.last_recv); ++ if (deltatime_cmp(age, <, one_second)) { ++ llog(RC_LOG, ike->sa.logger, "IKE SA "PRI_SO" recently established", ++ pri_so(candidate->sa.st_serialno)); ++ simultaneous_ike = candidate; ++ break; ++ } ++ } + } + + /* No simultaneous IKE found */ +@@ -1117,8 +1129,15 @@ struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, + + /* Simultaneous IKE found, we need to decide if we keep that one or current IKE. + * +- * We keep one with higher SPI. ++ * If simultaneous IKE is already established, we keep it. Otherwise we keep one ++ * with higher SPI. + */ ++ if (simultaneous_ike->sa.st_state->kind == STATE_V2_ESTABLISHED_IKE_SA) { ++ llog(RC_LOG, ike->sa.logger, "rejecting IKE_AUTH request; IKE SA "PRI_SO" already established", ++ pri_so(simultaneous_ike->sa.st_serialno)); ++ return (struct ike_sa *) ike; ++ } ++ + if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, &ike->sa.st_ike_spis)) { + ldbg(ike->sa.logger, "preferring the simultaneous IKE SA "PRI_SO" over the current IKE SA "PRI_SO, + pri_so(simultaneous_ike->sa.st_serialno), pri_so(ike->sa.st_serialno)); +-- +2.52.0 + + +From 182a09854935d505aa8cb5cfd7843bd133508452 Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Tue, 24 Feb 2026 12:59:29 +0100 +Subject: [PATCH 6/7] ikev2: use nonces to tie-break simultaneous IKE + +Previously, SPI was used to tie-break simultaneous IKE_AUTH +request. Using nonces follows RFC 7296 better. + +Signed-off-by: Ondrej Moris +--- + include/ike_spi.h | 1 - + programs/pluto/ike_spi.c | 11 ----------- + programs/pluto/ikev2_auth.c | 4 ++-- + 3 files changed, 2 insertions(+), 14 deletions(-) + +diff --git a/include/ike_spi.h b/include/ike_spi.h +index 376c120c0e..0c05542bae 100644 +--- a/include/ike_spi.h ++++ b/include/ike_spi.h +@@ -39,7 +39,6 @@ typedef struct { + } ike_spis_t; + + bool ike_spis_eq(const ike_spis_t *lhs, const ike_spis_t *rhs); +-bool ike_spis_gt(const ike_spis_t *lhs, const ike_spis_t *rhs); + + /* + * Need to handle two cases: +diff --git a/programs/pluto/ike_spi.c b/programs/pluto/ike_spi.c +index d7bb73a6e8..312c835bed 100644 +--- a/programs/pluto/ike_spi.c ++++ b/programs/pluto/ike_spi.c +@@ -42,17 +42,6 @@ bool ike_spis_eq(const ike_spis_t *lhs, const ike_spis_t *rhs) + ike_spi_eq(&lhs->responder, &rhs->responder)); + } + +-bool ike_spis_gt(const ike_spis_t *lhs, const ike_spis_t *rhs) +-{ +- int d = memcmp(lhs->initiator.bytes, rhs->initiator.bytes, +- sizeof(lhs->initiator.bytes)); +- if (d == 0) { +- d = memcmp(lhs->responder.bytes, rhs->responder.bytes, +- sizeof(lhs->responder.bytes)); +- } +- return d > 0; +-} +- + static struct { + uint8_t bytes[SHA2_256_DIGEST_SIZE]; + } ike_spi_secret; +diff --git a/programs/pluto/ikev2_auth.c b/programs/pluto/ikev2_auth.c +index 10d583b4c1..7785860995 100644 +--- a/programs/pluto/ikev2_auth.c ++++ b/programs/pluto/ikev2_auth.c +@@ -1130,7 +1130,7 @@ struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, + /* Simultaneous IKE found, we need to decide if we keep that one or current IKE. + * + * If simultaneous IKE is already established, we keep it. Otherwise we keep one +- * with higher SPI. ++ * with higher nonce. + */ + if (simultaneous_ike->sa.st_state->kind == STATE_V2_ESTABLISHED_IKE_SA) { + llog(RC_LOG, ike->sa.logger, "rejecting IKE_AUTH request; IKE SA "PRI_SO" already established", +@@ -1138,7 +1138,7 @@ struct ike_sa *check_simultaneous_ike_auth(const struct connection *c, + return (struct ike_sa *) ike; + } + +- if (ike_spis_gt(&simultaneous_ike->sa.st_ike_spis, &ike->sa.st_ike_spis)) { ++ if (hunk_cmp(simultaneous_ike->sa.st_ni, ike->sa.st_ni) > 0) { + ldbg(ike->sa.logger, "preferring the simultaneous IKE SA "PRI_SO" over the current IKE SA "PRI_SO, + pri_so(simultaneous_ike->sa.st_serialno), pri_so(ike->sa.st_serialno)); + return (struct ike_sa *) ike; +-- +2.52.0 + + +From be59b08e8f88937ae513c65902ce1af97f7a1eb3 Mon Sep 17 00:00:00 2001 +From: Ondrej Moris +Date: Tue, 24 Feb 2026 14:54:35 +0100 +Subject: [PATCH 7/7] impair: add impair option for setting nonces + +New impair options 'impair_initiator_nonce' and 'impair_responder_nonce' +allow setting all 32 bytes of nonce to specified value, e.g. to set high +nonce value one can use 'impair ike_initiator_nonce:0xff'. This might be +useful for testing. + +Signed-off-by: Ondrej Moris +--- + include/impair.h | 2 ++ + lib/libswan/impair.c | 2 ++ + programs/pluto/crypt_ke.c | 14 ++++++++++++++ + 3 files changed, 18 insertions(+) + +diff --git a/include/impair.h b/include/impair.h +index d3b2b68b3d..e6897e18d6 100644 +--- a/include/impair.h ++++ b/include/impair.h +@@ -111,6 +111,8 @@ struct impair { + + struct impair_unsigned ike_initiator_spi; + struct impair_unsigned ike_responder_spi; ++ struct impair_unsigned ike_initiator_nonce; ++ struct impair_unsigned ike_responder_nonce; + + bool bust_mi2; + bool bust_mr2; +diff --git a/lib/libswan/impair.c b/lib/libswan/impair.c +index 9e7cd829de..ba28d6420b 100644 +--- a/lib/libswan/impair.c ++++ b/lib/libswan/impair.c +@@ -210,6 +210,8 @@ struct impairment impairments[] = { + + U(ike_initiator_spi, "corrupt the IKE initiator SPI setting it to the value"), + U(ike_responder_spi, "corrupt the IKE responder SPI setting it to the value"), ++ U(ike_initiator_nonce, "corrupt the IKE initiator nonce setting it to the "), ++ U(ike_responder_nonce, "corrupt the IKE responder nonce setting it to the "), + + B(ikev1_del_with_notify, "causes pluto to send IKE Delete with additional bogus Notify payload"), + +diff --git a/programs/pluto/crypt_ke.c b/programs/pluto/crypt_ke.c +index 1d7dfd5ad5..8535a350fc 100644 +--- a/programs/pluto/crypt_ke.c ++++ b/programs/pluto/crypt_ke.c +@@ -52,12 +52,14 @@ + #include "ike_alg.h" + #include "crypt_dh.h" + #include "crypt_ke.h" ++#include "impair.h" + + struct task { + const struct dh_desc *dh; + chunk_t nonce; + struct dh_local_secret *local_secret; + ke_and_nonce_cb *cb; ++ enum sa_role role; + }; + + static void compute_ke_and_nonce(struct logger *logger, +@@ -72,6 +74,17 @@ static void compute_ke_and_nonce(struct logger *logger, + } + } + task->nonce = alloc_rnd_chunk(DEFAULT_NONCE_SIZE, "nonce"); ++ ++ if (impair.ike_initiator_nonce.enabled && task->role == SA_INITIATOR) { ++ uint8_t pattern = (uint8_t)impair.ike_initiator_nonce.value; ++ memset(task->nonce.ptr, pattern, task->nonce.len); ++ llog(RC_LOG, logger, "IMPAIR: forcing IKE initiator nonce to all 0x%02x bytes", pattern); ++ } else if (impair.ike_responder_nonce.enabled && task->role == SA_RESPONDER) { ++ uint8_t pattern = (uint8_t)impair.ike_responder_nonce.value; ++ memset(task->nonce.ptr, pattern, task->nonce.len); ++ llog(RC_LOG, logger, "IMPAIR: forcing IKE responder nonce to all 0x%02x bytes", pattern); ++ } ++ + if (LDBGP(DBG_CRYPT, logger)) { + LDBG_log_hunk(logger, "generated nonce:", task->nonce); + } +@@ -112,6 +125,7 @@ void submit_ke_and_nonce(struct state *callback_sa, + struct task *task = alloc_thing(struct task, "dh"); + task->dh = dh; + task->cb = cb; ++ task->role = task_sa->st_sa_role; + submit_task(/*callback*/callback_sa, /*task*/task_sa, md, detach_whack, + task, &ke_and_nonce_handler, where); + } +-- +2.52.0 + diff --git a/libreswan-5.3.tar.gz.asc b/libreswan-5.3.tar.gz.asc new file mode 100644 index 0000000..e147caf --- /dev/null +++ b/libreswan-5.3.tar.gz.asc @@ -0,0 +1,17 @@ +-----BEGIN PGP SIGNATURE----- + +iQJHBAABCgAxFiEEkH55DyXB6OVhzXO1hf9LQ7MPxvkFAmhmv0ETHHRlYW1AbGli +cmVzd2FuLm9yZwAKCRCF/0tDsw/G+Z24D/98iYhm+GlrtA3RIRWK/SptlXzR+Wq9 +8oL7qU1PiI/ncY9srFvQM4IG1IocPwIWWNPnTU4WVw0/6NRkkH0n9hXdFX6UTOdV +yrXr8fLKXAthtOJp0oKxs8GZEPqdY06b5AobhhclxzAqGArLwqyzoAHirAB44ia7 +s/1L8krVj1tMdXSbFH3/DaKiduYK460iPNC3zPw+WtsfnGN6ByaNmrAugxfB8ler +CuilM8mmi5OKq7DZMCH52A7ZNC8I1EkSnXUcPU8JNC097JsIfQyH3VCudwlFifxJ +sAi4AsEdOCwP2LOm+zvyEjNa0XFIBdRC+YBSMugPWcBWxP+IyspDNc7f8y27Rqva +kTa/Xd2M6ZeYPw+sdfd+2gMXr8mS7U0OzL7Y0Bmz2ESpmEVTQ8xYf6hEY3hdv1Mz +aBeY8UpvxEb6mSMJZjOCFLusYJNrbSCtiZlJZbTvPekTxT3xHPa6GZ5CX2hGR+Tn +RsBQjuqgoqMCakt0rfx+ikVp3ufoUREZKpuGoVW1GPXQTJ9uNzVTjDMHH9M9+o90 +DE75ZHuwvEMnAkfWf3FTr2OQdk3OXFLsQn7bjleoy+gjXdUH0cTIg670EZmnyela +xv9NjxG047NPFXfLZ3xTIrV6zqETVtG3YiWQlMdGVKjbm8F8UdZN0YXOmJaXks02 +2x+S0v04SBwxEg== +=iJlv +-----END PGP SIGNATURE----- diff --git a/libreswan.spec b/libreswan.spec index 6aac468..ccaa7be 100644 --- a/libreswan.spec +++ b/libreswan.spec @@ -2,7 +2,7 @@ ## (rpmautospec version 0.6.5) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 1; + release_number = 8; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -21,6 +21,7 @@ MANDIR=%{_mandir} \\\ PREFIX=%{_prefix} \\\ INITSYSTEM=systemd \\\ + SBINDIR=%{_sbindir} \\\ SHELL_BINARY=%{_bindir}/sh \\\ USE_DNSSEC=true \\\ USE_LABELED_IPSEC=true \\\ @@ -39,7 +40,7 @@ Name: libreswan Summary: Internet Key Exchange (IKEv1 and IKEv2) implementation for IPsec # version is generated in the release script -Version: 5.2 +Version: 5.3 Release: %autorelease # The code in lib/libswan/nss_copies.c is under MPL-2.0, while the # rest is under GPL-2.0-or-later @@ -55,6 +56,8 @@ Source5: https://download.libreswan.org/cavs/ikev2.fax.bz2 %endif Patch1: libreswan-4.15-ipsec_import.patch +Patch2: libreswan-5.3-outstanding-ike-auth-crossing.patch +Patch3: libreswan-5.3-helper-thread.patch BuildRequires: audit-libs-devel BuildRequires: bison @@ -86,15 +89,12 @@ BuildRequires: ElectricFence Requires: iproute >= 2.6.8 Requires: nss >= %{nss_version} Requires: nss-softokn -Requires: nss-tools -Requires: unbound-libs >= %{unbound_version} Requires: logrotate # for pidof Requires: procps-ng - -Requires(post): bash -Requires(post): coreutils +Requires: %{name}-minimal%{?_isa} = %{version}-%{release} +Obsoletes: %{name} < 5.3-5 Requires(post): systemd Requires(preun): systemd Requires(postun): systemd @@ -115,6 +115,26 @@ Libreswan also supports IKEv2 (RFC7296) and Secure Labeling Libreswan is based on Openswan-2.6.38 which in turn is based on FreeS/WAN-2.04 +%package minimal +Summary: Internet Key Exchange (IKEv1 and IKEv2) implementation for IPsec (minimal version) +Requires(post): bash +Requires(post): coreutils +Requires: nss-tools +Requires: unbound-libs >= %{unbound_version} +Obsoletes: %{name} < 5.3-5 + +%description minimal +Libreswan is a free implementation of IPsec & IKE for Linux. IPsec is +the Internet Protocol Security and uses strong cryptography to provide +both authentication and encryption services. These services allow you +to build secure tunnels through untrusted networks. Everything passing +through the untrusted net is encrypted by the ipsec gateway machine and +decrypted by the gateway at the other end of the tunnel. The resulting +tunnel is a virtual private network or VPN. + +This package contains the minimal set of daemons and userland tools +for setting up Libreswan. + %prep %{gpgverify} --keyring='%{SOURCE2}' --signature='%{SOURCE1}' --data='%{SOURCE0}' %setup -q -n libreswan-%{version}%{?prever} @@ -197,6 +217,8 @@ certutil -N -d sql:$tmpdir --empty-password %post %systemd_post ipsec.service + +%post minimal %sysctl_apply 50-libreswan.conf %preun @@ -208,6 +230,10 @@ certutil -N -d sql:$tmpdir --empty-password %files %doc CHANGES COPYING CREDITS README* LICENSE %doc docs/*.* docs/examples +%attr(0644,root,root) %{_unitdir}/ipsec.service +%doc %{_mandir}/*/* + +%files minimal %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/ipsec.conf %attr(0600,root,root) %config(noreplace) %{_sysconfdir}/ipsec.secrets %attr(0700,root,root) %dir %{_sysconfdir}/ipsec.d @@ -218,15 +244,40 @@ certutil -N -d sql:$tmpdir --empty-password %attr(0700,root,root) %dir %{_sharedstatedir}/ipsec %attr(0700,root,root) %dir %{_sharedstatedir}/ipsec/nss %attr(0644,root,root) %{_tmpfilesdir}/libreswan.conf -%attr(0644,root,root) %{_unitdir}/ipsec.service %attr(0644,root,root) %config(noreplace) %{_sysconfdir}/pam.d/pluto %config(noreplace) %{_sysconfdir}/logrotate.d/libreswan %{_sbindir}/ipsec %{_libexecdir}/ipsec -%doc %{_mandir}/*/* %changelog ## START: Generated by rpmautospec +* Wed Mar 18 2026 Daiki Ueno - 5.3-8 +- Bump release number + +* Tue Mar 17 2026 Ondrej Moris - 5.3-7 +- ikev2: use nonces to tie-break simultaneous IKE + +* Tue Mar 17 2026 Ondrej Moris - 5.3-6 +- CI: Update CI plan url + +* Mon Feb 23 2026 Daiki Ueno - 5.3-5 +- Subpackage minimal set of daemons into -minimal + +* Tue Feb 03 2026 Daiki Ueno - 5.3-4 +- pluto: reject crossing IKE_AUTH request only for the one side + +* Wed Jan 14 2026 Andrew Cagney - 5.3-3 +- helpers: work around NSS by joining helper threads + +* Fri Jan 09 2026 Ondrej Moris - 5.3-2 +- ikev2: reject simultaneous IKE_AUTH requests + +* Fri Jul 11 2025 Daiki Ueno - 5.3-1 +- Update to libreswan-5.3 + +* Wed Jun 18 2025 Daiki Ueno - 5.2-2 +- ipsec delete: expect no IKE only for orphan child + * Thu Mar 06 2025 Daiki Ueno - 5.2-1 - Update to libreswan 5.2 diff --git a/sources b/sources index 1e908cc..64464d5 100644 --- a/sources +++ b/sources @@ -1,4 +1,4 @@ SHA512 (ikev1_dsa.fax.bz2) = 627cbac14248bd68e8d22fbca247668a7749ef0c2e41df8d776d62df9a21403d3a246c0bd82c3faedce62de90b9f91a87f753e17b056319000bba7d2038461ac SHA512 (ikev1_psk.fax.bz2) = 1b2daec32edc56b410c036db2688c92548a9bd9914994bc7e555b301dd6db4497a6b3e89dc12ddf36826ae90b40fcde501a5a45c0d59098e07839073d219d467 SHA512 (ikev2.fax.bz2) = 0d3748d1bd574f6f1f3e4db847eca126ce649566ea710ef227426f433122752b80d1d6b8acf9d0df07b5597c1e45447e3a2fcb3391756e834e8e75f99df8e51e -SHA512 (libreswan-5.2.tar.gz) = 5c87edc879914158ba9c4c2a0edcd6fac0787b16d3c6a50c268cbd675c51cdec94e509031bc226680c0d40bd3375d73007cae5ee0588c136292e3f34cb759694 +SHA512 (libreswan-5.3.tar.gz) = 338fb82a9969da8fa78f64ec9eda0e3dcd216d6b8333a6f966ba839e31d3eb5fdd94613f0fff934be16ff8d84f6f4265c8b35f37c642569e042f65a58038ba0d