From 0dc820fe1d0de369d101702151fa06fff0eb360c Mon Sep 17 00:00:00 2001 From: Steffan Karger Date: Sun, 12 Apr 2026 13:37:56 +0200 Subject: [PATCH] tls-crypt-v2: Avoid interpreting opcode as part of WKc The buffer we pass to tls_crypt_v2_extract_client_key contains the entire received control channel packet. We should skip the opcode before trying to read WKC. This logic error is a second bug behind the XlabAI finding, next too the too-strict ASSERT in tls_crypt_unwrap. Also remove a too strict ASSERT in tls_crypt_unwrap. We already check a few lines later for a too short packet and return a proper error ("packet too short"). XlabAI found a way of triggering this ASSERT that requires a tls-crypt-v2 client key that has a specific property (a specific byte need to have a specific value, about 1/256 probability). If an attacker can get hold of such a tls-crypt-v2 client key or observe a handshake using such a key, the attacker can trigger the ASSERT, crashing the server. Setups that do not use tls-crypt-v2 are not affected. Independently, Cisco Talos reported a way to trigger this ASSERT with any tls-crypt-v2 key but this requires the attacker to be also in possession of the private key part of the tls-crypt-v2 client key or to inject packet into a live session of a client session. CVE: 2026-35058 Reported-By: XlabAI Team of Tencent Xuanwu Lab (xlabai@tencent.com) Reported-By: Guannan Wang (wgnbuaa@gmail.com Reported-By: Zhanpeng Liu (pkugenuine@gmail.com) Reported-By: Guancheng Li (lgcpku@gmail.com) Reported-By: Emma Reuter of Cisco ASIG (TALOS-2026-2381) Signed-off-by: Steffan Karger Signed-off-by: Arne Schwabe Change-Id: I623733c0476c98f436d19009ee8990693c1579b5 Private-URL: https://github.com/OpenVPN/openvpn-private-issues/issues/111 Acked-by: Gert Doering Signed-off-by: Gert Doering (cherry picked from commit 18270324a5fd43122ca1b8c29b224c5dd5905429) --- src/openvpn/tls_crypt.c | 4 ++-- tests/unit_tests/openvpn/test_tls_crypt.c | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 50228e78..386aaf12 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -229,7 +229,6 @@ tls_crypt_unwrap(const struct buffer *src, struct buffer *dst, gc_init(&gc); ASSERT(opt); - ASSERT(src->len > 0); ASSERT(ctx->cipher); ASSERT(packet_id_initialized(&opt->packet_id) || (opt->flags & CO_IGNORE_PACKET_ID)); @@ -627,7 +626,8 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, struct buffer wrapped_client_key = *buf; uint16_t net_len = 0; - if (BLEN(&wrapped_client_key) < sizeof(net_len)) + if (!buf_advance(&wrapped_client_key, 1) + || BLEN(&wrapped_client_key) < 1 + sizeof(net_len)) { msg(D_TLS_ERRORS, "Can not read tls-crypt-v2 client key length"); return false; diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index bf5a8cef..fcf6f9a0 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -534,7 +534,16 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) .mode = TLS_WRAP_CRYPT, .tls_crypt_v2_server_key = ctx->server_keys.encrypt, }; - assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL, true)); + + /* a buffer that only contains the wrapped key should fail */ + assert_false(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL, true)); + + /* 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)); + tls_wrap_free(&wrap_ctx); }