Version in base suite: 2.6.3-1+deb12u3 Base version: openvpn_2.6.3-1+deb12u3 Target version: openvpn_2.6.3-1+deb12u4 Base file: /srv/ftp-master.debian.org/ftp/pool/main/o/openvpn/openvpn_2.6.3-1+deb12u3.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/o/openvpn/openvpn_2.6.3-1+deb12u4.dsc changelog | 26 ++ patches/CVE-2024-5594-regression-fix.patch | 203 ++++++++++++++++++ patches/CVE-2025-13086.patch | 155 ++++++++++++++ patches/check-message-id.patch | 312 +++++++++++++++++++++++++++++ patches/series | 4 salsa-ci.yml | 9 tests/control | 4 tests/unit-tests | 13 + 8 files changed, 725 insertions(+), 1 deletion(-) diff -Nru openvpn-2.6.3/debian/changelog openvpn-2.6.3/debian/changelog --- openvpn-2.6.3/debian/changelog 2025-04-02 15:45:15.000000000 +0000 +++ openvpn-2.6.3/debian/changelog 2025-11-26 21:54:51.000000000 +0000 @@ -1,3 +1,29 @@ +openvpn (2.6.3-1+deb12u4) bookworm-security; urgency=medium + + [ Bernhard Schmidt ] + * Cherry-pick patches for CVE-2025-13086 + - check-message-id.patch: Check message id/acked ids too when doing + sessionid cookie checks - bugfix for floating client problem, code + prequesite for the CVE patch to apply + - CVE-2025-13086.patch: Fix memcmp check for the hmac verification in the + 3way handshake being inverted (Closes: #1121086) + + [ Aquila Macedo ] + * Add new autopkgtest for unit tests. + + [ Carlos Henrique Lima Melara ] + * debian/patches/CVE-2024-5594-regression-fix.patch: cherry-pick from + upstream to fix a regression introduced with CVE-2024-5594's fix. Namely, + "Allow trailing \r and \n in control channel message". (Closes: #1112516) + * debian/salsa-ci: + - Allow lintian job to fail. Sid's version dislikes things from bookworm. + - Disable gbp setup-gitattributes. + - Disable reprotest on bookworm. It can't run on bookworm, so the build + fails because of build dependencies problems. + * debian/tests/unit-tests: enable unit-tests in configure and be verbose. + + -- Bernhard Schmidt Wed, 26 Nov 2025 22:54:51 +0100 + openvpn (2.6.3-1+deb12u3) bookworm; urgency=medium [ Bernhard Schmidt ] diff -Nru openvpn-2.6.3/debian/patches/CVE-2024-5594-regression-fix.patch openvpn-2.6.3/debian/patches/CVE-2024-5594-regression-fix.patch --- openvpn-2.6.3/debian/patches/CVE-2024-5594-regression-fix.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.3/debian/patches/CVE-2024-5594-regression-fix.patch 2025-11-26 21:54:51.000000000 +0000 @@ -0,0 +1,203 @@ +From: Arne Schwabe +Date: Wed, 10 Jul 2024 16:06:23 +0200 +Subject: Allow trailing \r and \n in control channel message + +Writing a reason from a script will easily end up adding extra \r\n characters +at the end of the reason. Our current code pushes this to the peer. So be more +liberal in accepting these message. + +Github: closes OpenVPN/openvpn#568 + +Change-Id: I47c992b6b73b1475cbff8a28f720cf50dc1fbe3e +Signed-off-by: Arne Schwabe +Acked-by: Frank Lichtenheld +Message-Id: <20240710140623.172829-1-frank@lichtenheld.com> +URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg28910.html +Signed-off-by: Gert Doering +(cherry picked from commit be31325e1dfdffbb152374985c2ae7b6644e3519) + +Origin: upstream, https://github.com/OpenVPN/openvpn/commit/343573990135023d855d151fcd9248e5c26d9f8b +Bug: https://github.com/OpenVPN/openvpn/issues/568 +Last-Update: 2025-08-24 +--- + src/openvpn/forward.c | 33 +++--------------------------- + src/openvpn/ssl_pkt.c | 40 +++++++++++++++++++++++++++++++++++++ + src/openvpn/ssl_pkt.h | 14 +++++++++++++ + tests/unit_tests/openvpn/test_pkt.c | 35 ++++++++++++++++++++++++++++++++ + 4 files changed, 92 insertions(+), 30 deletions(-) + +diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c +index b565bfa..48c4276 100644 +--- a/src/openvpn/forward.c ++++ b/src/openvpn/forward.c +@@ -292,41 +292,14 @@ check_incoming_control_channel(struct context *c) + struct buffer buf = alloc_buf_gc(len, &gc); + if (tls_rec_payload(c->c2.tls_multi, &buf)) + { +- + while (BLEN(&buf) > 1) + { +- /* commands on the control channel are seperated by 0x00 bytes. +- * cmdlen does not include the 0 byte of the string */ +- int cmdlen = (int)strnlen(BSTR(&buf), BLEN(&buf)); +- +- if (cmdlen < BLEN(&buf)) +- { +- /* include the NUL byte and ensure NUL termination */ +- int cmdlen = (int)strlen(BSTR(&buf)) + 1; ++ struct buffer cmdbuf = extract_command_buffer(&buf, &gc); + +- /* Construct a buffer that only holds the current command and +- * its closing NUL byte */ +- struct buffer cmdbuf = alloc_buf_gc(cmdlen, &gc); +- buf_write(&cmdbuf, BPTR(&buf), cmdlen); +- +- /* check we have only printable characters or null byte in the +- * command string and no newlines */ +- if (!string_check_buf(&buf, CC_PRINT | CC_NULL, CC_CRLF)) +- { +- msg(D_PUSH_ERRORS, "WARNING: Received control with invalid characters: %s", +- format_hex(BPTR(&buf), BLEN(&buf), 256, &gc)); +- } +- else +- { +- parse_incoming_control_channel_command(c, &cmdbuf); +- } +- } +- else ++ if (cmdbuf.len > 0) + { +- msg(D_PUSH_ERRORS, "WARNING: Ignoring control channel " +- "message command without NUL termination"); ++ parse_incoming_control_channel_command(c, &cmdbuf); + } +- buf_advance(&buf, cmdlen); + } + } + else +diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c +index 7229f55..42cb130 100644 +--- a/src/openvpn/ssl_pkt.c ++++ b/src/openvpn/ssl_pkt.c +@@ -560,3 +560,43 @@ check_session_id_hmac(struct tls_pre_decrypt_state *state, + } + return false; + } ++ ++struct buffer ++extract_command_buffer(struct buffer *buf, struct gc_arena *gc) ++{ ++ /* commands on the control channel are seperated by 0x00 bytes. ++ * cmdlen does not include the 0 byte of the string */ ++ int cmdlen = (int)strnlen(BSTR(buf), BLEN(buf)); ++ ++ if (cmdlen >= BLEN(buf)) ++ { ++ buf_advance(buf, cmdlen); ++ /* Return empty buffer */ ++ struct buffer empty = { 0 }; ++ return empty; ++ } ++ ++ /* include the NUL byte and ensure NUL termination */ ++ cmdlen += 1; ++ ++ /* Construct a buffer that only holds the current command and ++ * its closing NUL byte */ ++ struct buffer cmdbuf = alloc_buf_gc(cmdlen, gc); ++ buf_write(&cmdbuf, BPTR(buf), cmdlen); ++ ++ /* Remove \r and \n at the end of the buffer to avoid ++ * problems with scripts and other that add extra \r and \n */ ++ buf_chomp(&cmdbuf); ++ ++ /* check we have only printable characters or null byte in the ++ * command string and no newlines */ ++ if (!string_check_buf(&cmdbuf, CC_PRINT | CC_NULL, CC_CRLF)) ++ { ++ msg(D_PUSH_ERRORS, "WARNING: Received control with invalid characters: %s", ++ format_hex(BPTR(&cmdbuf), BLEN(&cmdbuf), 256, gc)); ++ cmdbuf.len = 0; ++ } ++ ++ buf_advance(buf, cmdlen); ++ return cmdbuf; ++} +diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h +index 43c303f..f92eacc 100644 +--- a/src/openvpn/ssl_pkt.h ++++ b/src/openvpn/ssl_pkt.h +@@ -238,6 +238,20 @@ tls_reset_standalone(struct tls_wrap_ctx *ctx, + uint8_t header, + bool request_resend_wkc); + ++ ++/** ++ * Extracts a control channel message from buf and adjusts the size of ++ * buf after the message has been extracted ++ * @param buf The buffer the message should be extracted from ++ * @param gc gc_arena to be used for the returned buffer and displaying ++ * diagnostic messages ++ * @return A buffer with a control channel message or a buffer with ++ * with length 0 if there is no message or the message has ++ * invalid characters. ++ */ ++struct buffer ++extract_command_buffer(struct buffer *buf, struct gc_arena *gc); ++ + static inline const char * + packet_opcode_name(int op) + { +diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c +index 736f131..f3dc855 100644 +--- a/tests/unit_tests/openvpn/test_pkt.c ++++ b/tests/unit_tests/openvpn/test_pkt.c +@@ -636,6 +636,40 @@ test_generate_reset_packet_tls_auth(void **ut_state) + free_tas(&tas_server); + } + ++static void ++test_extract_control_message(void **ut_state) ++{ ++ struct gc_arena gc = gc_new(); ++ struct buffer input_buf = alloc_buf_gc(1024, &gc); ++ ++ /* This message will have a \0x00 at the end since it is a C string */ ++ const char input[] = "valid control message\r\n\0\0Invalid\r\none\0valid one again"; ++ ++ buf_write(&input_buf, input, sizeof(input)); ++ struct buffer cmd1 = extract_command_buffer(&input_buf, &gc); ++ struct buffer cmd2 = extract_command_buffer(&input_buf, &gc); ++ struct buffer cmd3 = extract_command_buffer(&input_buf, &gc); ++ struct buffer cmd4 = extract_command_buffer(&input_buf, &gc); ++ struct buffer cmd5 = extract_command_buffer(&input_buf, &gc); ++ ++ assert_string_equal(BSTR(&cmd1), "valid control message"); ++ /* empty message with just a \0x00 */ ++ assert_int_equal(cmd2.len, 1); ++ assert_string_equal(BSTR(&cmd2), ""); ++ assert_int_equal(cmd3.len, 0); ++ assert_string_equal(BSTR(&cmd4), "valid one again"); ++ assert_int_equal(cmd5.len, 0); ++ ++ const uint8_t nonull[6] = { 'n', 'o', ' ', 'N', 'U', 'L'}; ++ struct buffer nonull_buf = alloc_buf_gc(1024, &gc); ++ ++ buf_write(&nonull_buf, nonull, sizeof(nonull)); ++ struct buffer nonullcmd = extract_command_buffer(&nonull_buf, &gc); ++ assert_int_equal(nonullcmd.len, 0); ++ ++ gc_free(&gc); ++} ++ + int + main(void) + { +@@ -649,6 +683,7 @@ main(void) + cmocka_unit_test(test_verify_hmac_tls_auth), + cmocka_unit_test(test_generate_reset_packet_plain), + cmocka_unit_test(test_generate_reset_packet_tls_auth), ++ cmocka_unit_test(test_extract_control_message) + }; + + #if defined(ENABLE_CRYPTO_OPENSSL) diff -Nru openvpn-2.6.3/debian/patches/CVE-2025-13086.patch openvpn-2.6.3/debian/patches/CVE-2025-13086.patch --- openvpn-2.6.3/debian/patches/CVE-2025-13086.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.3/debian/patches/CVE-2025-13086.patch 2025-11-26 21:54:51.000000000 +0000 @@ -0,0 +1,155 @@ +From fa6a1824b0f37bff137204156a74ca28cf5b6f83 Mon Sep 17 00:00:00 2001 +From: Arne Schwabe +Date: Mon, 27 Oct 2025 10:05:55 +0100 +Subject: [PATCH] Fix memcmp check for the hmac verification in the 3way + handshake being inverted + +This is a stupid mistake but causes all hmac cookies to be accepted, +thus breaking source IP address validation. As a consequence, TLS +sessions can be openend and state can be consumed in the server from +IP addresses that did not initiate an initial connection. + +While at it, fix check to only allow [t-2;t] timeslots, disallowing +HMACs coming in from a future timeslot. + +Github: OpenVPN/openvpn-private-issues#56 + +CVE: 2025-13086 + +Reported-By: Joshua Rogers +Found-by: ZeroPath (https://zeropath.com/) +Reported-By: stefan@srlabs.de + +Change-Id: I9cbe2bf535575b47ddd7f34e985c5c1c6953a6fc +Signed-off-by: Arne Schwabe +Acked-by: Max Fillinger +(cherry picked from commit 68ec931e7fb4af11d5ba0d4283df0350083fd373) +--- + src/openvpn/ssl_pkt.c | 7 ++-- + tests/unit_tests/openvpn/test_pkt.c | 58 ++++++++++++++++++++++++++++- + 2 files changed, 61 insertions(+), 4 deletions(-) + +diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c +index 432bb8b8181..cf1ce172732 100644 +--- a/src/openvpn/ssl_pkt.c ++++ b/src/openvpn/ssl_pkt.c +@@ -576,13 +576,14 @@ check_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state, + } + + +- /* check adjacent timestamps too */ +- for (int offset = -2; offset <= 1; offset++) ++ /* check adjacent timestamps too, the handwindow is split in 2 for the ++ * offset, so we check the current timeslot and the two before that */ ++ for (int offset = -2; offset <= 0; offset++) + { + struct session_id expected_id = + calculate_session_id_hmac(state->peer_session_id, from, hmac, handwindow, offset); + +- if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE)) ++ if (memcmp_constant_time(&expected_id, &state->server_session_id, SID_SIZE) == 0) + { + return true; + } +diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c +index 27f52cf500d..62abafaac99 100644 +--- a/tests/unit_tests/openvpn/test_pkt.c ++++ b/tests/unit_tests/openvpn/test_pkt.c +@@ -444,6 +444,8 @@ test_verify_hmac_tls_auth(void **ut_state) + hmac_ctx_t *hmac = session_id_hmac_init(); + + struct link_socket_actual from = { 0 }; ++ from.dest.addr.sa.sa_family = AF_INET; ++ from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); + struct tls_auth_standalone tas = { 0 }; + struct tls_pre_decrypt_state state = { 0 }; + +@@ -471,10 +473,12 @@ test_verify_hmac_tls_auth(void **ut_state) + static void + test_verify_hmac_none(void **ut_state) + { ++ now = 1000; + hmac_ctx_t *hmac = session_id_hmac_init(); + + struct link_socket_actual from = { 0 }; + from.dest.addr.sa.sa_family = AF_INET; ++ from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); + + struct tls_auth_standalone tas = { 0 }; + struct tls_pre_decrypt_state state = { 0 }; +@@ -489,9 +493,61 @@ test_verify_hmac_none(void **ut_state) + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_ACK_V1); + ++ /* This packet has a random hmac, so it should fail to validate */ + bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); ++ assert_false(valid); ++ ++ struct session_id client_id = { { 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8 } }; ++ assert_memory_equal(&client_id, &state.peer_session_id, sizeof(struct session_id)); ++ ++ struct session_id expected_id = calculate_session_id_hmac(client_id, &from.dest, hmac, 30, 0); ++ ++ free_tls_pre_decrypt_state(&state); ++ buf_reset_len(&buf); ++ ++ /* Write the packet again into the buffer but this time, replacing the peer packet ++ * id with the expected one */ ++ buf_write(&buf, client_ack_none_random_id, sizeof(client_ack_none_random_id) - 8); ++ buf_write(&buf, expected_id.id, 8); ++ ++ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); ++ assert_int_equal(verdict, VERDICT_VALID_ACK_V1); ++ valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); ++ + assert_true(valid); + ++ /* Our handwindow is 30 so the slices are half of that, so they are ++ * (975,990), (990, 1005), (1005, 1020), (1020, 1035), (1035, 1050) ++ * So setting time to the two future ones should work ++ */ ++ now = 980; ++ assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ now = 1040; ++ assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ now = 1002; ++ assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ now = 1022; ++ assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ now = 1010; ++ assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ ++ /* Changing the IP address should make this invalid */ ++ from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020305); ++ assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ ++ /* Change to the correct one again */ ++ from.dest.addr.in4.sin_addr.s_addr = ntohl(0x01020304); ++ assert_true(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ ++ /* Modify the peer id, should now fail hmac verification */ ++ buf_inc_len(&buf, -4); ++ buf_write_u32(&buf, 0x12345678); ++ ++ free_tls_pre_decrypt_state(&state); ++ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); ++ assert_int_equal(verdict, VERDICT_VALID_ACK_V1); ++ assert_false(check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true)); ++ + free_tls_pre_decrypt_state(&state); + free_buf(&buf); + hmac_ctx_cleanup(hmac); +@@ -723,12 +779,12 @@ int + main(void) + { + const struct CMUnitTest tests[] = { ++ cmocka_unit_test(test_verify_hmac_none), + cmocka_unit_test(test_tls_decrypt_lite_none), + cmocka_unit_test(test_tls_decrypt_lite_auth), + cmocka_unit_test(test_tls_decrypt_lite_crypt), + cmocka_unit_test(test_parse_ack), + cmocka_unit_test(test_calc_session_id_hmac_static), +- cmocka_unit_test(test_verify_hmac_none), + cmocka_unit_test(test_verify_hmac_tls_auth), + cmocka_unit_test(test_verify_hmac_none_out_of_range_ack), + cmocka_unit_test(test_generate_reset_packet_plain), diff -Nru openvpn-2.6.3/debian/patches/check-message-id.patch openvpn-2.6.3/debian/patches/check-message-id.patch --- openvpn-2.6.3/debian/patches/check-message-id.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.3/debian/patches/check-message-id.patch 2025-11-26 21:54:51.000000000 +0000 @@ -0,0 +1,312 @@ +From 68c01720eecc1772b3f648b9e043e396d943f632 Mon Sep 17 00:00:00 2001 +From: Arne Schwabe +Date: Tue, 16 Sep 2025 17:52:50 +0200 +Subject: [PATCH] Check message id/acked ids too when doing sessionid cookie + checks + +This fixes that control packets on a floating client can trigger +creating a new session in special circumstances: + +To trigger this circumstance a connection needs to + +- starts on IP A +- successfully floats to IP B by data packet +- then has a control packet from IP A before any + data packet can trigger the float back to IP A + +and all of this needs to happen in the 60s time +that hmac cookie is valid in the default +configuration. + +In this scenario we would trigger a new connection as the HMAC +session id would be valid. + +This patch adds checking also of the message-id and acked ids to +discern packet from the initial three-way handshake where these +ids are 0 or 1 from any later packet. + +This will now trigger (at verb 4 or higher) a messaged like: + + Packet (P_ACK_V1) with invalid or missing SID + +instead. + +Also remove a few duplicated free_tls_pre_decrypt_state in test_ssl. + +Reported-By: Walter Doekes +Tested-By: Walter Doekes + +Change-Id: I6752dcd5aff3e5cea2b439366479e86751a1c403 +Signed-off-by: Arne Schwabe +Acked-by: MaxF +Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1184 +Message-Id: <20250916155258.6864-1-gert@greenie.muc.de> +URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32990.html +Signed-off-by: Gert Doering +(backported from commit 518e122b42739b0dbb54e7169a8a3aadb4773125) +--- + src/openvpn/mudp.c | 6 ++- + src/openvpn/ssl_pkt.c | 39 +++++++++++++-- + src/openvpn/ssl_pkt.h | 15 +++--- + tests/unit_tests/openvpn/test_pkt.c | 77 ++++++++++++++++++++++++++--- + 4 files changed, 117 insertions(+), 20 deletions(-) + +diff --git a/src/openvpn/mudp.c b/src/openvpn/mudp.c +index cdde0b5c972..0492311669c 100644 +--- a/src/openvpn/mudp.c ++++ b/src/openvpn/mudp.c +@@ -153,7 +153,8 @@ do_pre_decrypt_check(struct multi_context *m, + * need to contain the peer id */ + struct gc_arena gc = gc_new(); + +- bool ret = check_session_id_hmac(state, from, hmac, handwindow); ++ bool pkt_is_ack = (verdict == VERDICT_VALID_ACK_V1); ++ bool ret = check_session_hmac_and_pkt_id(state, from, hmac, handwindow, pkt_is_ack); + + const char *peer = print_link_socket_actual(&m->top.c2.from, &gc); + uint8_t pkt_firstbyte = *BPTR( &m->top.c2.buf); +@@ -161,7 +162,8 @@ do_pre_decrypt_check(struct multi_context *m, + + if (!ret) + { +- msg(D_MULTI_MEDIUM, "Packet (%s) with invalid or missing SID from %s", ++ msg(D_MULTI_MEDIUM, "Packet (%s) with invalid or missing SID from" ++ " %s or wrong packet id", + packet_opcode_name(op), peer); + } + else +diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c +index 41299f462db..432bb8b8181 100644 +--- a/src/openvpn/ssl_pkt.c ++++ b/src/openvpn/ssl_pkt.c +@@ -527,10 +527,11 @@ calculate_session_id_hmac(struct session_id client_sid, + } + + bool +-check_session_id_hmac(struct tls_pre_decrypt_state *state, +- const struct openvpn_sockaddr *from, +- hmac_ctx_t *hmac, +- int handwindow) ++check_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state, ++ const struct openvpn_sockaddr *from, ++ hmac_ctx_t *hmac, ++ int handwindow, ++ bool pkt_is_ack) + { + if (!from) + { +@@ -545,6 +546,36 @@ check_session_id_hmac(struct tls_pre_decrypt_state *state, + return false; + } + ++ /* Check if the packet ID of the packet or ACKED packet is <= 1 */ ++ for (int i = 0; i < ack.len; i++) ++ { ++ /* This packet ACKs a packet that has a higher packet id than the ++ * ones expected in the three-way handshake, consider it as invalid ++ * for the session */ ++ if (ack.packet_id[i] > 1) ++ { ++ return false; ++ } ++ } ++ ++ if (!pkt_is_ack) ++ { ++ packet_id_type message_id; ++ /* Extract the packet ID from the packet */ ++ if (!reliable_ack_read_packet_id(&buf, &message_id)) ++ { ++ return false; ++ } ++ ++ /* similar check. Anything larger than 1 is not considered part of the ++ * three-way handshake */ ++ if (message_id > 1) ++ { ++ return false; ++ } ++ } ++ ++ + /* check adjacent timestamps too */ + for (int offset = -2; offset <= 1; offset++) + { +diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h +index 2033da61ff7..1df691c369a 100644 +--- a/src/openvpn/ssl_pkt.h ++++ b/src/openvpn/ssl_pkt.h +@@ -182,17 +182,20 @@ calculate_session_id_hmac(struct session_id client_sid, + /** + * Checks if a control packet has a correct HMAC server session id + * +- * @param client_sid session id of the client ++ * This will also consider packets that have a packet id higher ++ * than 1 or ack packets higher than 1 to be invalid as they are ++ * not part of the initial three way handshake of OpenVPN and should ++ * not create a new connection. ++ * ++ * @param state session information + * @param from link_socket from the client + * @param hmac the hmac context to use for the calculation + * @param handwindow the quantisation of the current time ++ * @param pkt_is_ack the packet being checked is a P_ACK_V1 + * @return the expected server session id + */ +-bool +-check_session_id_hmac(struct tls_pre_decrypt_state *state, +- const struct openvpn_sockaddr *from, +- hmac_ctx_t *hmac, +- int handwindow); ++bool check_session_hmac_and_pkt_id(struct tls_pre_decrypt_state *state, const struct openvpn_sockaddr *from, ++ hmac_ctx_t *hmac, int handwindow, bool pkt_is_ack); + + /* + * Write a control channel authentication record. +diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c +index 74d7311f74d..27f52cf500d 100644 +--- a/tests/unit_tests/openvpn/test_pkt.c ++++ b/tests/unit_tests/openvpn/test_pkt.c +@@ -174,6 +174,27 @@ const uint8_t client_ack_none_random_id[] = { + 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e + }; + ++/* no tls-auth, P_ACK_V1, acks 0,1, and 2 */ ++const uint8_t client_ack_123_none_random_id[] = { ++ 0x28, ++ 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8, ++ 0x03, ++ 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x01, ++ 0x00, 0x00, 0x00, 0x02, ++ 0xdd, 0x85, 0xdb, 0x53, 0x56, 0x23, 0xb0, 0x2e ++}; ++ ++/* no tls-auth, P_CONTROL_V1, acks 0, msg-id 2 */ ++const uint8_t client_control_none_random_id[] = { ++ 0x20, ++ 0xae, 0xb9, 0xaf, 0xe1, 0xf0, 0x1d, 0x79, 0xc8, ++ 0x01, ++ 0x00, 0x00, 0x00, 0x00, ++ 0x02 ++}; ++ ++ + struct tls_auth_standalone + init_tas_auth(int key_direction) + { +@@ -294,12 +315,10 @@ test_tls_decrypt_lite_auth(void **ut_state) + assert_int_equal(verdict, VERDICT_VALID_RESET_V2); + free_tls_pre_decrypt_state(&state); + +- free_tls_pre_decrypt_state(&state); + /* The pre decrypt function should not modify the buffer, so calling it + * again should have the same result */ + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_RESET_V2); +- free_tls_pre_decrypt_state(&state); + + /* and buf memory should be equal */ + assert_memory_equal(BPTR(&buf), client_reset_v2_tls_auth, sizeof(client_reset_v2_tls_auth)); +@@ -317,7 +336,6 @@ test_tls_decrypt_lite_auth(void **ut_state) + assert_int_equal(verdict, VERDICT_INVALID); + free_tls_pre_decrypt_state(&state); + +- free_tls_pre_decrypt_state(&state); + /* Wrong key direction gives a wrong hmac key and should not validate */ + free_key_ctx_bi(&tas.tls_wrap.opt.key_ctx_bi); + free_tas(&tas); +@@ -357,15 +375,12 @@ test_tls_decrypt_lite_none(void **ut_state) + assert_int_equal(verdict, VERDICT_VALID_RESET_V2); + free_tls_pre_decrypt_state(&state); + +- free_tls_pre_decrypt_state(&state); + buf_reset_len(&buf); + buf_write(&buf, client_reset_v2_tls_crypt, sizeof(client_reset_v2_none)); + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_RESET_V2); + free_tls_pre_decrypt_state(&state); + +- free_tls_pre_decrypt_state(&state); +- + /* This is not a reset packet and should trigger the other response */ + buf_reset_len(&buf); + buf_write(&buf, client_ack_tls_auth_randomid, sizeof(client_ack_tls_auth_randomid)); +@@ -443,7 +458,7 @@ test_verify_hmac_tls_auth(void **ut_state) + assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1); + + /* This is a valid packet but containing a random id instead of an HMAC id*/ +- bool valid = check_session_id_hmac(&state, &from.dest, hmac, 30); ++ bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, false); + assert_false(valid); + + free_tls_pre_decrypt_state(&state); +@@ -474,7 +489,7 @@ test_verify_hmac_none(void **ut_state) + verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); + assert_int_equal(verdict, VERDICT_VALID_ACK_V1); + +- bool valid = check_session_id_hmac(&state, &from.dest, hmac, 30); ++ bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); + assert_true(valid); + + free_tls_pre_decrypt_state(&state); +@@ -483,6 +498,51 @@ test_verify_hmac_none(void **ut_state) + hmac_ctx_free(hmac); + } + ++static void ++test_verify_hmac_none_out_of_range_ack(void **ut_state) ++{ ++ hmac_ctx_t *hmac = session_id_hmac_init(); ++ ++ struct link_socket_actual from = { 0 }; ++ from.dest.addr.sa.sa_family = AF_INET; ++ ++ struct tls_auth_standalone tas = { 0 }; ++ struct tls_pre_decrypt_state state = { 0 }; ++ ++ struct buffer buf = alloc_buf(1024); ++ enum first_packet_verdict verdict; ++ ++ tas.tls_wrap.mode = TLS_WRAP_NONE; ++ ++ buf_reset_len(&buf); ++ buf_write(&buf, client_ack_123_none_random_id, sizeof(client_ack_123_none_random_id)); ++ ++ ++ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); ++ assert_int_equal(verdict, VERDICT_VALID_ACK_V1); ++ ++ /* should fail because it acks 2 */ ++ bool valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); ++ assert_false(valid); ++ free_tls_pre_decrypt_state(&state); ++ ++ /* Try test with the control with a too high message id now */ ++ buf_reset_len(&buf); ++ buf_write(&buf, client_control_none_random_id, sizeof(client_control_none_random_id)); ++ ++ verdict = tls_pre_decrypt_lite(&tas, &state, &from, &buf); ++ assert_int_equal(verdict, VERDICT_VALID_CONTROL_V1); ++ ++ /* should fail because it has message id 2 */ ++ valid = check_session_hmac_and_pkt_id(&state, &from.dest, hmac, 30, true); ++ assert_false(valid); ++ ++ free_tls_pre_decrypt_state(&state); ++ free_buf(&buf); ++ hmac_ctx_cleanup(hmac); ++ hmac_ctx_free(hmac); ++} ++ + static hmac_ctx_t * + init_static_hmac(void) + { +@@ -670,6 +730,7 @@ main(void) + cmocka_unit_test(test_calc_session_id_hmac_static), + cmocka_unit_test(test_verify_hmac_none), + cmocka_unit_test(test_verify_hmac_tls_auth), ++ cmocka_unit_test(test_verify_hmac_none_out_of_range_ack), + cmocka_unit_test(test_generate_reset_packet_plain), + cmocka_unit_test(test_generate_reset_packet_tls_auth), + cmocka_unit_test(test_extract_control_message) diff -Nru openvpn-2.6.3/debian/patches/series openvpn-2.6.3/debian/patches/series --- openvpn-2.6.3/debian/patches/series 2025-04-02 15:45:15.000000000 +0000 +++ openvpn-2.6.3/debian/patches/series 2025-11-26 21:54:51.000000000 +0000 @@ -1,6 +1,5 @@ move_log_dir.patch auth-pam_libpam_so_filename.patch -#debian_nogroup_for_sample_files.patch openvpn-pkcs11warn.patch systemd.patch fix-dangling-pointer-in-pkcs11.patch @@ -11,3 +10,6 @@ CVE-2024-5594.patch sample-keys-renew-10-years.patch CVE-2025-2704.patch +CVE-2024-5594-regression-fix.patch +check-message-id.patch +CVE-2025-13086.patch diff -Nru openvpn-2.6.3/debian/salsa-ci.yml openvpn-2.6.3/debian/salsa-ci.yml --- openvpn-2.6.3/debian/salsa-ci.yml 2025-04-02 15:45:15.000000000 +0000 +++ openvpn-2.6.3/debian/salsa-ci.yml 2025-11-26 21:54:51.000000000 +0000 @@ -5,3 +5,12 @@ variables: RELEASE: 'bookworm' + SALSA_CI_DISABLE_GBP_SETUP_GITATTRIBUTES: 1 + # reprotest can only run on sid and doesn't respect $RELEASE so it's + # completely broken for openvpn and we gain nothing by running it + SALSA_CI_DISABLE_REPROTEST: 1 + +# salsa-ci runs on lintian from unstable, it dislikes lots of things from the +# bookworm era :-) +lintian: + allow_failure: true diff -Nru openvpn-2.6.3/debian/tests/control openvpn-2.6.3/debian/tests/control --- openvpn-2.6.3/debian/tests/control 2025-04-02 15:45:15.000000000 +0000 +++ openvpn-2.6.3/debian/tests/control 2025-11-26 21:54:51.000000000 +0000 @@ -4,3 +4,7 @@ Tests: server-setup-with-static-key Restrictions: needs-root, isolation-machine, allow-stderr + +Tests: unit-tests +Depends: libcmocka-dev, @builddeps@ +Restrictions: allow-stderr diff -Nru openvpn-2.6.3/debian/tests/unit-tests openvpn-2.6.3/debian/tests/unit-tests --- openvpn-2.6.3/debian/tests/unit-tests 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.3/debian/tests/unit-tests 2025-11-26 21:54:51.000000000 +0000 @@ -0,0 +1,13 @@ +#!/bin/sh +set -ex + +# This test sets up and builds the openvpn, then runs the unit tests. + +sed -i -e 's/--disable-unit-tests/--enable-unit-tests/' debian/rules + +dh_update_autotools_config +dh_autoreconf +debian/rules override_dh_auto_configure +debian/rules override_dh_auto_build + +make -C tests/unit_tests check