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 <steffan@karger.me>
Signed-off-by: Arne Schwabe <arne@rfc2549.org>

Change-Id: I623733c0476c98f436d19009ee8990693c1579b5
Private-URL: https://github.com/OpenVPN/openvpn-private-issues/issues/111
Acked-by: Gert Doering <gert@greenie.muc.de>
Signed-off-by: Gert Doering <gert@greenie.muc.de>
(cherry picked from commit 18270324a5fd43122ca1b8c29b224c5dd5905429)
This commit is contained in:
Steffan Karger 2026-04-12 13:37:56 +02:00 committed by Gert Doering
parent adece45628
commit 0dc820fe1d
2 changed files with 12 additions and 3 deletions

View file

@ -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;

View file

@ -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);
}