Version in base suite: 2.6.14-1+deb13u1 Version in overlay suite: 2.6.14-1+deb13u2 Base version: openvpn_2.6.14-1+deb13u2 Target version: openvpn_2.6.14-1+deb13u3 Base file: /srv/ftp-master.debian.org/ftp/pool/main/o/openvpn/openvpn_2.6.14-1+deb13u2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/o/openvpn/openvpn_2.6.14-1+deb13u3.dsc changelog | 19 ++ patches/CVE-2026-11771.patch | 42 +++++ patches/CVE-2026-12932.patch | 343 +++++++++++++++++++++++++++++++++++++++++++ patches/CVE-2026-12996.patch | 52 ++++++ patches/CVE-2026-13117.patch | 42 +++++ patches/CVE-2026-13122.patch | 315 +++++++++++++++++++++++++++++++++++++++ patches/CVE-2026-13698.patch | 249 +++++++++++++++++++++++++++++++ patches/series | 6 8 files changed, 1068 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzk1kqldv/openvpn_2.6.14-1+deb13u2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpzk1kqldv/openvpn_2.6.14-1+deb13u3.dsc: no acceptable signature found diff -Nru openvpn-2.6.14/debian/changelog openvpn-2.6.14/debian/changelog --- openvpn-2.6.14/debian/changelog 2026-04-27 07:22:24.000000000 +0000 +++ openvpn-2.6.14/debian/changelog 2026-07-01 20:20:55.000000000 +0000 @@ -1,3 +1,22 @@ +openvpn (2.6.14-1+deb13u3) trixie-security; urgency=high + + * Cherry-pick upstream security patches from the 2.6.21 release + - CVE-2026-12996: Fix use-after-free bug in ack_write_buf(), triggerable by + a well-timed sequence of control channel + authentication packets + - CVE-2026-13117: Fix use-after-free bug in tls_wrap_reneg(), triggerable + by suitable sequence of dynamic tls-crypt control-channel packets + - CVE-2026-13122: Fix server crash on reception of suitably malformed + auth-token, if --auth-gen-token external-auth is active + - CVE-2026-12932: Fix memory-leak in tls-crypt-v2 client key handling that + could lead to out-of-memory situations and subsequent server crashes + - CVE-2026-11771: Fix possible 1-byte buffer overrun on NTLMv2 proxy + responses. + - CVE-2026-13698: Fix another memory leak on reception of suitable + tls-crypt-v2 packets that could lead to an out of memory situation and + server crash + + -- Bernhard Schmidt Wed, 01 Jul 2026 22:20:55 +0200 + openvpn (2.6.14-1+deb13u2) trixie-security; urgency=medium * Cherry-pick upstream security patches diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-11771.patch openvpn-2.6.14/debian/patches/CVE-2026-11771.patch --- openvpn-2.6.14/debian/patches/CVE-2026-11771.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-11771.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,42 @@ +From e1bf48ab80a22598000a7ab83d3688f07d139cc7 Mon Sep 17 00:00:00 2001 +From: Gert Doering +Date: Sat, 20 Jun 2026 12:21:58 +0200 +Subject: [PATCH] Fix 1-byte buffer overrun on NTLMv2 proxy responses. + +An attacker controlling an HTTP proxy (or performing MITM on the +plaintext pre-TLS proxy connection) can trigger a single 0-byte +overrun to a buffer on the stack by sending a crafted NTLM Type +2 challenge response. + +The effects of this depend on memory layout, but could possibly lead +to a crashing OpenVPN client. + +Reported-by: Tristan Madani (@TristanInSec) +CVE: 2026-11771 +Github: OpenVPN/openvpn-private-issues#116 + +Change-Id: Iac54e6772b2c26a09227fd638d24d6e2aa35cec6 +Signed-off-by: Gert Doering +Acked-by: Arne Schwabe +Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1713 +Message-Id: <20260618123729.18337-1-gert@greenie.muc.de> +URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg37218.html +Signed-off-by: Gert Doering +(cherry picked from commit 04309bfe0313c09edd02c29945893b9d7e2ca920) +--- + src/openvpn/ntlm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/openvpn/ntlm.c b/src/openvpn/ntlm.c +index 6da43593dce..c74844125b4 100644 +--- a/src/openvpn/ntlm.c ++++ b/src/openvpn/ntlm.c +@@ -333,7 +333,7 @@ ntlm_phase_3(const struct http_proxy_info *p, const char *phase_2, + if ((flags & 0x00800000) == 0x00800000) + { + tib_len = buf2[0x28]; /* Get Target Information block size */ +- if (tib_len + 0x1c + 16 > sizeof(ntlmv2_response)) ++ if (tib_len + 0x1c + 16 >= sizeof(ntlmv2_response)) + { + msg(M_WARN, "NTLM: target information buffer too long for response (len=%d)", tib_len); + return NULL; diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-12932.patch openvpn-2.6.14/debian/patches/CVE-2026-12932.patch --- openvpn-2.6.14/debian/patches/CVE-2026-12932.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-12932.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,343 @@ +From cd47c629a3f6efce43673e76cad5e26c9a67f223 Mon Sep 17 00:00:00 2001 +From: Arne Schwabe +Date: Tue, 23 Jun 2026 16:42:48 +0200 +Subject: [PATCH] Clean up metadata handling in tls_crypt_v2_extract_client_key + +This makes the metadata a local variable instead of a member of the +wrap_context struct. Also always ensure that this buffer is freed to +avoid any leak of the metadata buffer. + +This touches the check methods. Ensure that they still work as +intended by adding unit tests for both script and age checks. + +CVE: 2026-12932 +Github: OpenVPN/openvpn-private-issues#133 +Github: OpenVPN/openvpn-private-issues#136 +Github: OpenVPN/openvpn-private-issues#139 + +Reported-By: Valton Tahiri + +Backported to 2.6. Since 2.6 is missing the check age checks, these +parts are left out. The unit tests still have some of the infrastructure +to test this part too (creating metadata with specific date) but this +allow the patch to be closer to the 2.7 variant than throwing out that +extra code and reworking it in a more minimal way. + +Change-Id: Icc8ab7fed9c123367c7c54e3409a72b5ec9cc0f0 +Acked-by: Gert Doering +--- + src/openvpn/ssl.h | 1 - + src/openvpn/ssl_common.h | 1 - + src/openvpn/ssl_pkt.c | 1 - + src/openvpn/tls_crypt.c | 34 +++-- + tests/unit_tests/openvpn/test_tls_crypt.c | 166 +++++++++++++++++++++- + 5 files changed, 183 insertions(+), 20 deletions(-) + +diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h +index 1e15e26321a..5fffa9a8831 100644 +--- a/src/openvpn/ssl.h ++++ b/src/openvpn/ssl.h +@@ -489,7 +489,6 @@ tls_wrap_free(struct tls_wrap_ctx *tls_wrap) + free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi); + } + +- free_buf(&tls_wrap->tls_crypt_v2_metadata); + free_buf(&tls_wrap->work); + secure_memzero(&tls_wrap->original_wrap_keydata, sizeof(tls_wrap->original_wrap_keydata)); + } +diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h +index 8b6ae3f2430..aa9c5575dcc 100644 +--- a/src/openvpn/ssl_common.h ++++ b/src/openvpn/ssl_common.h +@@ -275,7 +275,6 @@ struct tls_wrap_ctx + struct key_ctx tls_crypt_v2_server_key; /**< Decrypts client keys */ + const struct buffer *tls_crypt_v2_wkc; /**< Wrapped client key, + * sent to server */ +- struct buffer tls_crypt_v2_metadata; /**< Received from client */ + bool cleanup_key_ctx; /**< opt.key_ctx_bi is owned by + * this context */ + /** original key data to be xored in to the key for dynamic tls-crypt. +diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c +index cf1ce172732..cd124a4c954 100644 +--- a/src/openvpn/ssl_pkt.c ++++ b/src/openvpn/ssl_pkt.c +@@ -286,7 +286,6 @@ void + free_tls_pre_decrypt_state(struct tls_pre_decrypt_state *state) + { + free_buf(&state->newbuf); +- free_buf(&state->tls_wrap_tmp.tls_crypt_v2_metadata); + if (state->tls_wrap_tmp.cleanup_key_ctx) + { + free_key_ctx_bi(&state->tls_wrap_tmp.opt.key_ctx_bi); +diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c +index 4457f82930e..40baeedc571 100644 +--- a/src/openvpn/tls_crypt.c ++++ b/src/openvpn/tls_crypt.c +@@ -34,6 +34,7 @@ + #include "run_command.h" + #include "session_id.h" + #include "ssl.h" ++#include "buffer.h" + + #include "tls_crypt.h" + +@@ -550,13 +551,14 @@ tls_crypt_v2_unwrap_client_key(struct key2 *client_key, struct buffer *metadata, + } + + static bool +-tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx, +- const struct tls_options *opt) ++tls_crypt_v2_verify_metadata(const struct buffer *tls_crypt_v2_metadata, const struct tls_options *opt) + { + bool ret = false; + struct gc_arena gc = gc_new(); + const char *tmp_file = NULL; +- struct buffer metadata = ctx->tls_crypt_v2_metadata; ++ ++ struct buffer metadata = *tls_crypt_v2_metadata; ++ + int metadata_type = buf_read_u8(&metadata); + if (metadata_type < 0) + { +@@ -663,16 +665,19 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, + return true; + } + +- ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); +- if (!tls_crypt_v2_unwrap_client_key(&ctx->original_wrap_keydata, +- &ctx->tls_crypt_v2_metadata, +- wrapped_client_key, +- &ctx->tls_crypt_v2_server_key)) ++ struct buffer tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); ++ if (!tls_crypt_v2_unwrap_client_key(&ctx->original_wrap_keydata, &tls_crypt_v2_metadata, ++ wrapped_client_key, &ctx->tls_crypt_v2_server_key)) + { + msg(D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key"); +- secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata)); +- return false; ++ goto error; ++ } ++ ++ if (opt && opt->tls_crypt_v2_verify_script && !tls_crypt_v2_verify_metadata(&tls_crypt_v2_metadata, opt)) ++ { ++ goto error; + } ++ free_buf(&tls_crypt_v2_metadata); + + /* Load the decrypted key */ + ctx->mode = TLS_WRAP_CRYPT; +@@ -685,12 +690,11 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, + /* Remove client key from buffer so tls-crypt code can unwrap message */ + ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); + +- if (opt && opt->tls_crypt_v2_verify_script) +- { +- return tls_crypt_v2_verify_metadata(ctx, opt); +- } +- + return true; ++error: ++ secure_memzero(&ctx->original_wrap_keydata, sizeof(ctx->original_wrap_keydata)); ++ free_buf(&tls_crypt_v2_metadata); ++ return false; + } + + void +diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c +index fcf6f9a00eb..3ac37e89bdf 100644 +--- a/tests/unit_tests/openvpn/test_tls_crypt.c ++++ b/tests/unit_tests/openvpn/test_tls_crypt.c +@@ -102,6 +102,28 @@ int + __wrap_parse_line(const char *line, char **p, const int n, const char *file, + const int line_num, int msglevel, struct gc_arena *gc) + { ++ if (strcmp(line, "/usr/bin/true") == 0) ++ { ++ p[0] = "/usr/bin/true"; ++ return 1; ++ } ++ if (strcmp(line, "/usr/bin/false") == 0) ++ { ++ p[0] = "/usr/bin/false"; ++ return 1; ++ } ++ if (strcmp(line, "/bin/true") == 0) ++ { ++ p[0] = "/bin/true"; ++ return 1; ++ } ++ if (strcmp(line, "/bin/false") == 0) ++ { ++ p[0] = "/bin/false"; ++ return 1; ++ } ++ ++ + p[0] = PATH1 PATH2; + p[1] = PARAM1; + p[2] = PARAM2; +@@ -459,8 +481,11 @@ test_tls_crypt_v2_setup(void **state) + static int + test_tls_crypt_v2_teardown(void **state) + { +- struct test_tls_crypt_v2_context *ctx = +- (struct test_tls_crypt_v2_context *) *state; ++ /* Ensure that if we modified now by using update_time, it is back to the ++ * 0 state for any other test that relies on it. */ ++ now = 0; ++ ++ struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state; + + free_key_ctx_bi(&ctx->server_keys); + free_key_ctx_bi(&ctx->client_key); +@@ -629,6 +654,139 @@ tls_crypt_v2_wrap_unwrap_dst_too_small(void **state) + assert_true(0 == BLEN(&ctx->unwrapped_metadata)); + } + ++static struct buffer ++create_metadata_days_ago(struct gc_arena *gc, int days_ago) ++{ ++ struct buffer metadata = alloc_buf_gc(1 + sizeof(int64_t), gc); ++ ++ assert_int_not_equal(now, 0); ++ ++ int64_t create_time = now - (days_ago * 24 * 60 * 60); ++ assert_true(create_time > 0); ++ ++ int64_t timestamp = htonll((uint64_t)create_time); ++ ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1)); ++ ASSERT(buf_write(&metadata, ×tamp, sizeof(timestamp))); ++ ++ return metadata; ++} ++ ++/** ++ * Creates a minimal buffer that allows tls_crypt_v2_extract_client_key ++ * to extract the client key. This will add metadata with a tls-crypt key ++ * created with the given days_ago. ++ */ ++static struct buffer ++create_client_key_input(struct test_tls_crypt_v2_context *ctx, int days_ago) ++{ ++ struct buffer metadata = create_metadata_days_ago(&ctx->gc, days_ago); ++ ++ buf_reset_len(&ctx->wkc); ++ assert_true(tls_crypt_v2_wrap_client_key(&ctx->wkc, &ctx->client_key2, &metadata, ++ &ctx->server_keys.encrypt, &ctx->gc)); ++ ++ /* add a opcode in front of the key to make it valid to extract */ ++ struct buffer pseudo_packet = alloc_buf_gc(buf_len(&ctx->wkc) + 1, &ctx->gc); ++ ++ buf_write_u8(&pseudo_packet, 0x50); ++ buf_copy(&pseudo_packet, &ctx->wkc); ++ ++ return pseudo_packet; ++} ++ ++/** ++ * Check that unwrapping an invalid key fails as expected and does not ++ * leave extra memory around (via ASAN) ++ */ ++static void ++tls_crypt_v2_wrap_unwrap_invalid(void **state) ++{ ++ update_time(); ++ ++ struct test_tls_crypt_v2_context *ctx = (struct test_tls_crypt_v2_context *)*state; ++ ++ struct tls_wrap_ctx wrap_ctx = { ++ .mode = TLS_WRAP_CRYPT, ++ .tls_crypt_v2_server_key = ctx->server_keys.encrypt, ++ }; ++ ++ struct buffer tmp = create_client_key_input(ctx, 20); ++ ++ /* Make the wrapped key invalid by flipping a few bits */ ++ buf_bptr(&tmp)[buf_len(&tmp) - 20] ^= 0x55; ++ ++ assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, NULL, true)); ++ tls_wrap_free(&wrap_ctx); ++} ++ ++ ++static void ++tls_crypt_v2_unwrap_checks(void **state) ++{ ++ update_time(); ++ ++ struct test_tls_crypt_v2_context *ctx = *state; ++ ++ ++ struct tls_wrap_ctx wrap_ctx = { ++ .mode = TLS_WRAP_CRYPT, ++ .tls_crypt_v2_server_key = ctx->server_keys.encrypt, ++ }; ++ ++ struct tls_options tls_options = { 0 }; ++ ++ /* without extra checks, this should just work */ ++ struct buffer tmp = create_client_key_input(ctx, 29); ++ assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ tls_wrap_free(&wrap_ctx); ++ ++ /* Use /bin/true as verify script */ ++ script_security_set(2); ++ tls_options.tls_crypt_v2_verify_script = "/usr/bin/true"; ++ /* Some Linux system only have /bin/true */ ++ if (access(tls_options.tls_crypt_v2_verify_script, X_OK) != 0) ++ { ++ tls_options.tls_crypt_v2_verify_script = "/bin/true"; ++ } ++ ++ tls_options.tmp_dir = "/tmp"; ++ ++ /* Since we override rand_bytes the tmpfile name is non-random as well */ ++ const char *non_random_tmpfile = "/tmp/openvpn_tls_crypt_v2_metadata__706050403020100706050403020100.tmp"; ++ unlink(non_random_tmpfile); ++ ++ expect_string(__wrap_buffer_write_file, filename, non_random_tmpfile); ++ ++ /* We do not write the first byte (type) to the file but rather to a ++ * metadata_type environment variable */ ++ struct buffer expected_metadata = create_metadata_days_ago(&ctx->gc, 29); ++ buf_advance(&expected_metadata, 1); ++ ++ expect_memory(__wrap_buffer_write_file, pem, buf_bptr(&expected_metadata), buf_len(&expected_metadata)); ++ will_return(__wrap_buffer_write_file, true); ++ ++ tmp = create_client_key_input(ctx, 29); ++ assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ tls_wrap_free(&wrap_ctx); ++ ++ tls_options.tls_crypt_v2_verify_script = "/usr/bin/false"; ++ /* Some Linux system only have /bin/false */ ++ if (access(tls_options.tls_crypt_v2_verify_script, X_OK) != 0) ++ { ++ tls_options.tls_crypt_v2_verify_script = "/bin/false"; ++ } ++ ++ expect_string(__wrap_buffer_write_file, filename, non_random_tmpfile); ++ expect_memory(__wrap_buffer_write_file, pem, buf_bptr(&expected_metadata), buf_len(&expected_metadata)); ++ will_return(__wrap_buffer_write_file, true); ++ ++ tmp = create_client_key_input(ctx, 29); ++ assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ tls_wrap_free(&wrap_ctx); ++ ++ script_security_set(0); ++} ++ + static void + test_tls_crypt_v2_write_server_key_file(void **state) + { +@@ -724,6 +882,10 @@ main(void) + cmocka_unit_test_setup_teardown(test_tls_crypt_secure_reneg_key, + test_tls_crypt_setup, + test_tls_crypt_teardown), ++ cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_invalid, ++ test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown), ++ cmocka_unit_test_setup_teardown(tls_crypt_v2_unwrap_checks, ++ test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown), + cmocka_unit_test(test_tls_crypt_v2_write_server_key_file), + cmocka_unit_test(test_tls_crypt_v2_write_client_key_file), + cmocka_unit_test(test_tls_crypt_v2_write_client_key_file_metadata), diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-12996.patch openvpn-2.6.14/debian/patches/CVE-2026-12996.patch --- openvpn-2.6.14/debian/patches/CVE-2026-12996.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-12996.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,52 @@ +From ea01c6544d00e5124ef26d6bc85d7663dfbb4a03 Mon Sep 17 00:00:00 2001 +From: Max Fillinger +Date: Fri, 22 May 2026 16:55:13 +0200 +Subject: [PATCH] Fix ack_write_buf use after free +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If the active TLS session has a pending dedicated ACK packet, +tls_multi_process sets to_link to ks->ack_write_buf. If in the same +execution of tls_multi_process, the initializing session reaches the +authenticated stage, the active session will be freed which leaves +to_link.data pointing to freed memory. + +This commit extends check_session_buf_not_used so that it also checks +ks->ack_write_buf for all key states. + +CVE: 2026-12996 +Github: OpenVPN/openvpn-private-issues#121 +Github: OpenVPN/openvpn-private-issues#127 +Reported-By: Hcamael +Reported-By: 章鱼哥 (www.aipyaipy.com) +Github: OpenVPN/openvpn-private-issues#131 +Reported-By: Haiyang Huang +Github: OpenVPN/openvpn-private-issues#132 +Reported-By: Haruki Oyama (Waseda University) + +Change-Id: Ia6aee772999d87b45a51bf5855c4f266ad7fb3f4 +Signed-off-by: Max Fillinger +Acked-By: Arne Schwabe +(cherry picked from master commit) +--- + src/openvpn/ssl.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c +index aaf40319fa5..a466d4ebbaf 100644 +--- a/src/openvpn/ssl.c ++++ b/src/openvpn/ssl.c +@@ -3271,6 +3271,12 @@ check_session_buf_not_used(struct buffer *to_link, struct tls_session *session) + goto used; + } + } ++ if (ks->ack_write_buf.data == dataptr) ++ { ++ msg(M_INFO, "Warning buffer of freed TLS session is still in use (session->key[%d].ack_write_buf)", i); ++ ++ goto used; ++ } + } + return; + diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-13117.patch openvpn-2.6.14/debian/patches/CVE-2026-13117.patch --- openvpn-2.6.14/debian/patches/CVE-2026-13117.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-13117.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,42 @@ +From 7cfd5c09b2fb802ec4043bef04fbe8dc7978ed72 Mon Sep 17 00:00:00 2001 +From: Max Fillinger +Date: Fri, 22 May 2026 14:34:57 +0200 +Subject: [PATCH] Fix tls_wrap_reneg use after free + +When dynamic tls-crypt is active, it is possible for tls_multi_process +to set to_link to session->tls_wrap_reneg.work and later free that +session, leaving to_link.data pointing to freed memory. + +This is not caught by the function check_session_buf_not_used because it +checks only tls_wrap, not tls_wrap_reneg. This commit adds that check. + +CVE: 2026-13117 +Github: OpenVPN/openvpn-private-issues#119 +Github: OpenVPN/openvpn-private-issues#125 +Reported-By: Trace37 Labs (https://github.com/trace37labs) +Github: OpenVPN/openvpn-private-issues#131 +Reported-By: Haiyang Huang +Signed-off-by: Max Fillinger +Acked-By: Arne Schwabe +(cherry picked from commit cd536fffdef1f2bd54b3b848bd257917061637e8) +--- + src/openvpn/ssl.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c +index 0a6be2ddbbe..342957ea02d 100644 +--- a/src/openvpn/ssl.c ++++ b/src/openvpn/ssl.c +@@ -3240,6 +3240,12 @@ check_session_buf_not_used(struct buffer *to_link, struct tls_session *session) + "still in use (tls_wrap.work.data)"); + goto used; + } ++ if (session->tls_wrap_reneg.work.data == dataptr) ++ { ++ msg(M_INFO, "Warning buffer of freed TLS session is " ++ "still in use (tls_wrap_reneg.work.data)"); ++ goto used; ++ } + + for (int i = 0; i < KS_SIZE; i++) + { diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-13122.patch openvpn-2.6.14/debian/patches/CVE-2026-13122.patch --- openvpn-2.6.14/debian/patches/CVE-2026-13122.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-13122.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,315 @@ +From ee119b24b3178b8402b087a1f3f0930be25c5618 Mon Sep 17 00:00:00 2001 +From: Arne Schwabe +Date: Wed, 20 May 2026 23:07:34 +0000 +Subject: [PATCH] Ensure we only get the session from valid tokens for + external-auth + +This also improves is_auth_token to check for the correct length and +adds a few unit tests. + +This fixes an assertion when a user would send auth token with the +right prefix but overall too short length. + +Reported-By: Haiyang Huang +CVE: 2026-13122 +Github: OpenVPN/openvpn-private-issues#118 +Github: OpenVPN/openvpn-private-issues#128 (2.7+master) +Github: OpenVPN/openvpn-private-issues#141 (2.6) + +Acked-by: MaxF +Acked-by: Gert Doering + +cherry picked from master commit. Adjusted for formatting +differences in 2.6 and kept spelling mistake (old_tsamp instead +of old_stamp) to keep avoid more changes. + +Change-Id: I053cca5f42e3841cef8934c2114a8a159fbc0c29 +--- + doc/man-sections/server-options.rst | 4 +- + src/openvpn/auth_token.c | 35 +++++++-- + src/openvpn/auth_token.h | 9 +-- + src/openvpn/ssl_verify.c | 8 +- + tests/unit_tests/openvpn/test_auth_token.c | 91 +++++++++++++++++++--- + 5 files changed, 118 insertions(+), 29 deletions(-) + +diff --git a/doc/man-sections/server-options.rst b/doc/man-sections/server-options.rst +index bfcc987c0a4..da2c282767b 100644 +--- a/doc/man-sections/server-options.rst ++++ b/doc/man-sections/server-options.rst +@@ -87,7 +87,9 @@ fast hardware. SSL/TLS authentication must be used in this mode. + external-auth option unless the client could authenticate in another + acceptable way (e.g. client certificate), otherwise returning success + will lead to authentication bypass (as does returning success on a wrong +- password from a script). ++ password from a script). In the case that Expired token is accepted ++ the token will keep the session id and start time from the original ++ (expired) token. + + --auth-gen-token-secret file + Specifies a file that holds a secret for the HMAC used in +diff --git a/src/openvpn/auth_token.c b/src/openvpn/auth_token.c +index c4b59b9946d..bcfdfb49ef6 100644 +--- a/src/openvpn/auth_token.c ++++ b/src/openvpn/auth_token.c +@@ -19,14 +19,33 @@ + const char *auth_token_pem_name = "OpenVPN auth-token server key"; + + #define AUTH_TOKEN_SESSION_ID_LEN 12 +-#define AUTH_TOKEN_SESSION_ID_BASE64_LEN (AUTH_TOKEN_SESSION_ID_LEN * 8 / 6) ++#define AUTH_TOKEN_SESSION_ID_BASE64_LEN (OPENVPN_BASE64_LENGTH(AUTH_TOKEN_SESSION_ID_LEN)) + +-#if AUTH_TOKEN_SESSION_ID_LEN % 3 +-#error AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3 +-#endif ++/* We want our token to be a multiple of 3 bytes to avoid the base64 padding */ ++static_assert(AUTH_TOKEN_SESSION_ID_LEN % 3 == 0, "AUTH_TOKEN_SESSION_ID_LEN needs to be multiple a 3"); + ++#define AUTH_TOKEN_HMAC_LEN SHA256_DIGEST_LENGTH + /* Size of the data of the token (not b64 encoded and without prefix) */ +-#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + 32) ++#define TOKEN_DATA_LEN (2 * sizeof(int64_t) + AUTH_TOKEN_SESSION_ID_LEN + AUTH_TOKEN_HMAC_LEN) ++#define TOKEN_DATA_BASE64_LEN (OPENVPN_BASE64_LENGTH(TOKEN_DATA_LEN)) ++ ++ ++#define TOTAL_SESSION_TOKEN_LEN (strlen(SESSION_ID_PREFIX) + TOKEN_DATA_BASE64_LEN) ++ ++/* Ensure that TOKEN_DATA_LEN is a multiple of 3 so the we avoid the base64 ++ * padding */ ++static_assert(TOKEN_DATA_LEN % 3 == 0, "TOKEN_DATA_BASE64_LEN is not a multiple of 3"); ++ ++bool ++is_auth_token(const char *password) ++{ ++ if (strlen(password) != TOTAL_SESSION_TOKEN_LEN) ++ { ++ return false; ++ } ++ ++ return (memcmp_constant_time(SESSION_ID_PREFIX, password, strlen(SESSION_ID_PREFIX)) == 0); ++} + + static struct key_type + auth_token_kt(void) +@@ -172,7 +191,7 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi) + + if (multi->auth_token_initial) + { +- /* Just enough space to fit 8 bytes+ 1 extra to decode a non-padded ++ /* Just enough space to fit 8 bytes + 1 extra to decode a non-padded + * base64 string (multiple of 3 bytes). 9 bytes => 12 bytes base64 + * bytes + */ +@@ -182,7 +201,7 @@ generate_auth_token(const struct user_pass *up, struct tls_multi *multi) + char *initial_token_copy = string_alloc(multi->auth_token_initial, &gc); + + char *old_sessid = initial_token_copy + strlen(SESSION_ID_PREFIX); +- char *old_tsamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_LEN*8/6; ++ char *old_tsamp_initial = old_sessid + AUTH_TOKEN_SESSION_ID_BASE64_LEN; + + /* + * We null terminate the old token just after the session ID to let +@@ -290,7 +309,7 @@ check_hmac_token(hmac_ctx_t *ctx, const uint8_t *b64decoded, const char *usernam + hmac_ctx_final(ctx, hmac_output); + + const uint8_t *hmac = b64decoded + TOKEN_DATA_LEN - 256/8; +- return memcmp_constant_time(&hmac_output, hmac, 32) == 0; ++ return memcmp_constant_time(&hmac_output, hmac, AUTH_TOKEN_HMAC_LEN) == 0; + } + + unsigned int +diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h +index e024f9527bc..2ab82c3a7a8 100644 +--- a/src/openvpn/auth_token.h ++++ b/src/openvpn/auth_token.h +@@ -123,12 +123,9 @@ void wipe_auth_token(struct tls_multi *multi); + * @param password + * @return whether the password string starts with the session token prefix + */ +-static inline bool +-is_auth_token(const char *password) +-{ +- return (memcmp_constant_time(SESSION_ID_PREFIX, password, +- strlen(SESSION_ID_PREFIX)) == 0); +-} ++bool ++is_auth_token(const char *password); ++ + /** + * Checks if a client should be sent a new auth token to update its + * current auth-token +diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c +index 08672eb8b68..a47cdc94282 100644 +--- a/src/openvpn/ssl_verify.c ++++ b/src/openvpn/ssl_verify.c +@@ -1625,10 +1625,10 @@ verify_user_pass(struct user_pass *up, struct tls_multi *multi, + { + ks->auth_token_state_flags = verify_auth_token(up, multi, session); + +- /* If this is the first time we see an auth-token in this multi session, +- * save it as initial auth token. This ensures using the +- * same session ID and initial timestamp in new tokens */ +- if (!multi->auth_token_initial) ++ /* If this is a valid token and the first time we see an auth-token ++ * in this multi session, save it as initial auth token. This ensures ++ * using the same session ID and initial timestamp in new tokens */ ++ if (!multi->auth_token_initial && (ks->auth_token_state_flags & AUTH_TOKEN_HMAC_OK)) + { + multi->auth_token_initial = strdup(up->password); + } +diff --git a/tests/unit_tests/openvpn/test_auth_token.c b/tests/unit_tests/openvpn/test_auth_token.c +index 3a3cb69b5b0..6d8dbf60bd8 100644 +--- a/tests/unit_tests/openvpn/test_auth_token.c ++++ b/tests/unit_tests/openvpn/test_auth_token.c +@@ -80,6 +80,8 @@ static const char *random_key = "-----BEGIN OpenVPN auth-token server key-----\n + + static const char *random_token = "SESS_ID_AT_ThhRItzOKNKrh3dfAAAAAFwzHpwAAAAAXDMenDdrq0RoH3dkA1f7O3wO+7kZcx2DusVZrRmFlWQM9HOb"; + ++static const char *last_session_state_value; ++static char last_session_id_value[64]; + + static int + setup(void **state) +@@ -109,6 +111,10 @@ setup(void **state) + init_key_ctx(&ctx->multi.opt.auth_token_key, &key, &ctx->kt, false, "TEST"); + + now = 0; ++ ++ CLEAR(last_session_id_value); ++ last_session_state_value = NULL; ++ + return 0; + } + +@@ -258,13 +264,17 @@ auth_token_test_known_keys(void **state) + AUTH_TOKEN_HMAC_OK); + } + +-static const char *lastsesion_statevalue; ++ + void + setenv_str(struct env_set *es, const char *name, const char *value) + { + if (streq(name, "session_state")) + { +- lastsesion_statevalue = value; ++ last_session_state_value = value; ++ } ++ else if (streq(name, "session_id")) ++ { ++ strncpy(last_session_id_value, value, sizeof(last_session_id_value)); + } + } + +@@ -342,28 +352,49 @@ auth_token_test_env(void **state) + ks->auth_token_state_flags = 0; + ctx->multi.auth_token = NULL; + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "Initial"); ++ assert_string_equal(last_session_state_value, "Initial"); + ++ /* This generated a fresh token with a new session ID, ++ * check that this session id is part of the multi initial token and ++ * found at the right index */ ++ char *idx = strstr(ctx->multi.auth_token_initial, last_session_id_value); ++ assert_non_null(idx); ++ ++ ptrdiff_t offset = idx - ctx->multi.auth_token_initial; ++ assert_int_equal(offset, 11); ++ ++ CLEAR(last_session_id_value); + ks->auth_token_state_flags = 0; +- strcpy(ctx->up.password, now0key0); ++ strcpy(ctx->up.password, "somepassword"); ++ ++ free(ctx->multi.auth_token_initial); ++ ctx->multi.auth_token_initial = strdup(now0key0); ++ + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "Invalid"); ++ assert_string_equal(last_session_state_value, "Initial"); ++ assert_string_equal(last_session_id_value, "0123456789abcdef"); ++ ++ CLEAR(last_session_id_value); ++ free(ctx->multi.auth_token_initial); ++ ctx->multi.auth_token_initial = NULL; + + ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK; ++ strcpy(ctx->up.password, now0key0); + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "Authenticated"); ++ assert_string_equal(last_session_state_value, "Authenticated"); ++ assert_string_equal(last_session_id_value, "0123456789abcdef"); + + ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED; + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "Expired"); ++ assert_string_equal(last_session_state_value, "Expired"); + + ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_VALID_EMPTYUSER; + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "AuthenticatedEmptyUser"); ++ assert_string_equal(last_session_state_value, "AuthenticatedEmptyUser"); + + ks->auth_token_state_flags = AUTH_TOKEN_HMAC_OK|AUTH_TOKEN_EXPIRED|AUTH_TOKEN_VALID_EMPTYUSER; + add_session_token_env(ctx->session, &ctx->multi, &ctx->up); +- assert_string_equal(lastsesion_statevalue, "ExpiredEmptyUser"); ++ assert_string_equal(last_session_state_value, "ExpiredEmptyUser"); + } + + static void +@@ -390,6 +421,45 @@ auth_token_test_random_keys(void **state) + } + + ++static void ++auth_token_test_is_auth_token(void **state) ++{ ++ /* Known-good tokens are recognised */ ++ assert_true(is_auth_token(now0key0)); ++ assert_true(is_auth_token(random_token)); ++ ++ /* Empty and clearly too-short strings are rejected */ ++ assert_false(is_auth_token("")); ++ assert_false(is_auth_token("foobar")); ++ assert_false(is_auth_token(SESSION_ID_PREFIX)); ++ assert_false(is_auth_token("SESS_ID_AT_Something_wild")); ++ ++ /* Since all auth token have the same length, get the length from ++ * now0key0 */ ++ const size_t auth_token_len = strlen(now0key0); ++ ++ /* Right length, incorrect prefix */ ++ char buf[256] = { 0 }; ++ assert_true(auth_token_len < sizeof(buf)); ++ memset(buf, 'A', auth_token_len); ++ buf[auth_token_len] = '\0'; ++ assert_false(is_auth_token(buf)); ++ ++ /* Correct prefix to ensure the previous test is valid */ ++ memcpy(buf, "SESS_ID_AT_", strlen("SESS_ID_AT_")); ++ assert_true(is_auth_token(buf)); ++ ++ /* Correct prefix, one byte too short */ ++ buf[auth_token_len - 1] = '\0'; ++ assert_false(is_auth_token(buf)); ++ ++ /* Correct prefix, one byte too long */ ++ strcpy(buf, now0key0); ++ buf[auth_token_len] = 'A'; ++ buf[auth_token_len + 1] = '\0'; ++ assert_false(is_auth_token(buf)); ++} ++ + static void + auth_token_test_key_load(void **state) + { +@@ -418,7 +488,8 @@ main(void) + cmocka_unit_test_setup_teardown(auth_token_test_random_keys, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_key_load, setup, teardown), + cmocka_unit_test_setup_teardown(auth_token_test_timeout, setup, teardown), +- cmocka_unit_test_setup_teardown(auth_token_test_session_mismatch, setup, teardown) ++ cmocka_unit_test_setup_teardown(auth_token_test_session_mismatch, setup, teardown), ++ cmocka_unit_test(auth_token_test_is_auth_token), + }; + + #if defined(ENABLE_CRYPTO_OPENSSL) diff -Nru openvpn-2.6.14/debian/patches/CVE-2026-13698.patch openvpn-2.6.14/debian/patches/CVE-2026-13698.patch --- openvpn-2.6.14/debian/patches/CVE-2026-13698.patch 1970-01-01 00:00:00.000000000 +0000 +++ openvpn-2.6.14/debian/patches/CVE-2026-13698.patch 2026-07-01 20:20:55.000000000 +0000 @@ -0,0 +1,249 @@ +From 4357bb37542c5045994a8ff80c730484326e5e39 Mon Sep 17 00:00:00 2001 +From: Arne Schwabe +Date: Thu, 18 Jun 2026 16:33:43 +0200 +Subject: [PATCH] Ensure tls-crypt keys are not setup twice + +Commit 82ee2fe4b42d already did this when the session id stayed the same +but forgot the other code path that could also lead to tls-crypt keys to be +setup. + +This approach was a bit too fragile as it missed some other code path +that might trigger the same behaviour. This commit changes the logic +to directly infer if the key is already initialised instead of taking +a proxy (like key state as the previous commit did). + +The fix in commit 7a6ab5773 (#121, #127) made sure that we do not try +to extract the tls-crypt-v2 key multiple times and made the unit test +basically not work as the extraction was skipped and then could also +not fail anymore. I could work around it in the unit test but improving +tls_wrap_free felt preferable. + +(Backported from master commit e287547d85151) + +CVE: 2026-13698 +Reported-By: Max Fillinger +Github: OpenVPN/openvpn-private-issues#137 +Github: OpenVPN/openvpn-private-issues#138 +Github: OpenVPN/openvpn-private-issues#147 (2.6 backport) + +Change-Id: I3b5e4e84762aa253d46e69103f7b1e84ebefca1d +Signed-off-by: Arne Schwabe +Acked-By: Max Fillinger +Acked-By: Gert Doering +--- + src/openvpn/ssl.c | 6 +++--- + src/openvpn/ssl.h | 1 + + src/openvpn/ssl_pkt.c | 7 +++---- + src/openvpn/ssl_pkt.h | 4 +--- + src/openvpn/tls_crypt.c | 8 ++++---- + src/openvpn/tls_crypt.h | 7 +------ + tests/unit_tests/openvpn/test_tls_crypt.c | 12 ++++++------ + 7 files changed, 19 insertions(+), 26 deletions(-) + +diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c +index a466d4ebbaf..0a6be2ddbbe 100644 +--- a/src/openvpn/ssl.c ++++ b/src/openvpn/ssl.c +@@ -3812,7 +3812,7 @@ tls_pre_decrypt(struct tls_multi *multi, + } + + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from, +- session->opt, true)) ++ session->opt)) + { + goto error; + } +@@ -3881,7 +3881,7 @@ tls_pre_decrypt(struct tls_multi *multi, + if (op == P_CONTROL_SOFT_RESET_V1 && ks->state >= S_GENERATED_KEYS) + { + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), +- from, session->opt, false)) ++ from, session->opt)) + { + goto error; + } +@@ -3912,7 +3912,7 @@ tls_pre_decrypt(struct tls_multi *multi, + } + + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), +- from, session->opt, initial_packet)) ++ from, session->opt)) + { + /* if an initial packet in read_control_auth, we rather + * error out than anything else */ +diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h +index 5fffa9a8831..e2a3754b4ff 100644 +--- a/src/openvpn/ssl.h ++++ b/src/openvpn/ssl.h +@@ -487,6 +487,7 @@ tls_wrap_free(struct tls_wrap_ctx *tls_wrap) + if (tls_wrap->cleanup_key_ctx) + { + free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi); ++ tls_wrap->cleanup_key_ctx = false; + } + + free_buf(&tls_wrap->work); +diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c +index cd124a4c954..96c1403790e 100644 +--- a/src/openvpn/ssl_pkt.c ++++ b/src/openvpn/ssl_pkt.c +@@ -200,8 +200,7 @@ bool + read_control_auth(struct buffer *buf, + struct tls_wrap_ctx *ctx, + const struct link_socket_actual *from, +- const struct tls_options *opt, +- bool initial_packet) ++ const struct tls_options *opt) + { + struct gc_arena gc = gc_new(); + bool ret = false; +@@ -209,7 +208,7 @@ read_control_auth(struct buffer *buf, + const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT; + if ((opcode == P_CONTROL_HARD_RESET_CLIENT_V3 + || opcode == P_CONTROL_WKC_V1) +- && !tls_crypt_v2_extract_client_key(buf, ctx, opt, initial_packet)) ++ && !tls_crypt_v2_extract_client_key(buf, ctx, opt)) + { + msg(D_TLS_ERRORS, + "TLS Error: can not extract tls-crypt-v2 client key from %s", +@@ -373,7 +372,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas, + * into newbuf or just setting newbuf to point to the start of control + * message */ + bool status = read_control_auth(&state->newbuf, &state->tls_wrap_tmp, +- from, NULL, true); ++ from, NULL); + + if (!status) + { +diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h +index 1df691c369a..9e23869f32e 100644 +--- a/src/openvpn/ssl_pkt.h ++++ b/src/openvpn/ssl_pkt.h +@@ -217,15 +217,13 @@ write_control_auth(struct tls_session *session, + * @param ctx control channel security context + * @param from incoming link socket address + * @param opt tls options struct for the session +- * @param initial_packet whether this is the initial packet for the connection + * @return if the packet was successfully processed + */ + bool + read_control_auth(struct buffer *buf, + struct tls_wrap_ctx *ctx, + const struct link_socket_actual *from, +- const struct tls_options *opt, +- bool initial_packet); ++ const struct tls_options *opt); + + + /** +diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c +index 40baeedc571..ded77f9815a 100644 +--- a/src/openvpn/tls_crypt.c ++++ b/src/openvpn/tls_crypt.c +@@ -613,8 +613,7 @@ tls_crypt_v2_verify_metadata(const struct buffer *tls_crypt_v2_metadata, const s + bool + tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx, +- const struct tls_options *opt, +- bool initial_packet) ++ const struct tls_options *opt) + { + if (!ctx->tls_crypt_v2_server_key.cipher) + { +@@ -644,7 +643,8 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, + return false; + } + +- if (!initial_packet) ++ /* Check if this context already owns an initialised key */ ++ if (ctx->cleanup_key_ctx == true) + { + /* This might be a harmless resend of the packet but it is better to + * just ignore the WKC part than trying to setup tls-crypt keys again. +@@ -657,7 +657,7 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, + * and this is resend. So return the normal part of the packet, + * basically transforming the CONTROL_WKC_V1 into a normal CONTROL_V1 + * packet*/ +- msg(D_TLS_ERRORS, "control channel security already setup ignoring " ++ msg(D_TLS_ERRORS, "Control channel security already setup. Ignoring " + "wrapped key part of packet."); + + /* Remove client key from buffer so tls-crypt code can unwrap message */ +diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h +index 331c0c060a7..8c87e2080a1 100644 +--- a/src/openvpn/tls_crypt.h ++++ b/src/openvpn/tls_crypt.h +@@ -207,16 +207,11 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, + * message. + * @param ctx tls-wrap context to be initialized with the client key. + * +- * @param initial_packet whether this is the initial packet of the +- * connection. Only in these scenarios unwrapping +- * of a tls-crypt-v2 key is allowed +- * + * @returns true if a key was successfully extracted. + */ + bool tls_crypt_v2_extract_client_key(struct buffer *buf, + struct tls_wrap_ctx *ctx, +- const struct tls_options *opt, +- bool initial_packet); ++ const struct tls_options *opt); + + /** + * Generate a tls-crypt-v2 server key, and write to file. +diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c +index 3ac37e89bdf..d84bbeca251 100644 +--- a/tests/unit_tests/openvpn/test_tls_crypt.c ++++ b/tests/unit_tests/openvpn/test_tls_crypt.c +@@ -561,13 +561,13 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) + }; + + /* a buffer that only contains the wrapped key should fail */ +- assert_false(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL, true)); ++ assert_false(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL)); + + /* add a opcode in front of the key to make it valid to extract */ + struct buffer wkcop = alloc_buf_gc(buf_len(&ctx->wkc) + 1, &ctx->gc); + buf_write_u8(&wkcop, 0x50); + buf_copy(&wkcop, &ctx->wkc); +- assert_true(tls_crypt_v2_extract_client_key(&wkcop, &wrap_ctx, NULL, true)); ++ assert_true(tls_crypt_v2_extract_client_key(&wkcop, &wrap_ctx, NULL)); + + tls_wrap_free(&wrap_ctx); + } +@@ -715,7 +715,7 @@ tls_crypt_v2_wrap_unwrap_invalid(void **state) + /* Make the wrapped key invalid by flipping a few bits */ + buf_bptr(&tmp)[buf_len(&tmp) - 20] ^= 0x55; + +- assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, NULL, true)); ++ assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, NULL)); + tls_wrap_free(&wrap_ctx); + } + +@@ -737,7 +737,7 @@ tls_crypt_v2_unwrap_checks(void **state) + + /* without extra checks, this should just work */ + struct buffer tmp = create_client_key_input(ctx, 29); +- assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options)); + tls_wrap_free(&wrap_ctx); + + /* Use /bin/true as verify script */ +@@ -766,7 +766,7 @@ tls_crypt_v2_unwrap_checks(void **state) + will_return(__wrap_buffer_write_file, true); + + tmp = create_client_key_input(ctx, 29); +- assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ assert_true(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options)); + tls_wrap_free(&wrap_ctx); + + tls_options.tls_crypt_v2_verify_script = "/usr/bin/false"; +@@ -781,7 +781,7 @@ tls_crypt_v2_unwrap_checks(void **state) + will_return(__wrap_buffer_write_file, true); + + tmp = create_client_key_input(ctx, 29); +- assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options, true)); ++ assert_false(tls_crypt_v2_extract_client_key(&tmp, &wrap_ctx, &tls_options)); + tls_wrap_free(&wrap_ctx); + + script_security_set(0); diff -Nru openvpn-2.6.14/debian/patches/series openvpn-2.6.14/debian/patches/series --- openvpn-2.6.14/debian/patches/series 2026-04-27 07:22:24.000000000 +0000 +++ openvpn-2.6.14/debian/patches/series 2026-07-01 20:20:55.000000000 +0000 @@ -7,3 +7,9 @@ CVE-2025-13086.patch CVE-2026-35058.patch CVE-2026-40215.patch +CVE-2026-11771.patch +CVE-2026-12932.patch +CVE-2026-13122.patch +CVE-2026-12996.patch +CVE-2026-13698.patch +CVE-2026-13117.patch