CVE-2026-35058
A reachable assertion vulnerability exists in the TLS Crypt v2 Client Key Extraction functionality of OpenVPN 2.6.x and 2.8_git. A specially crafted network packets can lead to a denial of service. An attacker can send a sequence of malicious packets to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
OpenVPN 2.6.x
OpenVPN 2.8_git
OpenVPN - https://github.com/OpenVPN
7.7 - CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H
CWE-617 - Reachable Assertion
OpenVPN is a virtual private network (VPN) system that implements techniques to create secure point-to-point or site-to-site connections in routed or bridged configurations and remote access facilities. It implements both client and server applications.
OpenVPN’s --tls-crypt-v2 feature provides per-client pre-authentication keys that protect the control channel from unauthenticated access. It is commonly deployed in enterprise and commercial VPN products as a defense-in-depth mechanism against DoS attacks targeting the TLS handshake. Because “tls-crypt-v2” is positioned as a mitigation layer against denial of services, vulnerabilities in its implementation will have an elevated impact.
CVE-2025-2704 (patched 2025-04-01 in commit 82ee2fe4) fixed a pre-authentication memory exhaustion in tls_crypt_v2_extract_client_key() (tls_crypt.c). The fix added an !initial_packet guard to skip full WKC processing on re-sent P_CONTROL_WKC_V1 packets — packets arriving after a session has already been established. This guard strips the WKC from the buffer so that the caller can process the remainder as a normal P_CONTROL_V1 packet. However, the guard does not verify that any data remains after stripping.
Two locations in the codebase combine to produce the crash:
// tls_crypt.c
tls_crypt_v2_extract_client_key(), !initial_packet path
if (!initial_packet)
{
/* ... comment about ignoring WKC on resend ... */
/* Remove client key from buffer so tls-crypt code can unwrap message */
ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); // [1]
return true; // [2]
}
At [1], buf_inc_len() reduces buf->len by BLEN(&wrapped_client_key). When an attacker sets the WKC length field in the packet equal to the total packet length, this call reduces buf->len to zero. At [2], the function returns true (success) without checking whether the buffer is now empty, causing the caller to proceed as if a valid control message is present.
The caller, read_control_auth() (ssl_pkt.c:236), passes the now-empty buffer to tls_crypt_unwrap(), which immediately hits an unconditional assertion:
// tls_crypt.c:219
ASSERT(src->len > 0); // [3] src->len == 0 → SIGABRT
At [3], the ASSERT macro calls abort(), sending SIGABRT to the process and killing the server instantly. All connected VPN sessions are terminated.
The minimum attack packet is 11 bytes:
Offset Field Size Value
------ ----- ---- -----
0 Opcode|KeyID 1 0x58 (P_CONTROL_WKC_V1 << 3 | 0)
1 Session ID 8 Attacker's session ID (from handshake)
9 WKC length 2 htons(11) — equals total packet length
The !initial_packet path does not cryptographically validate the WKC. It assumes the WKC is a harmless resend of a previously validated key and strips wkc_len bytes without inspecting the content. Because crypto validation would occur in tls_crypt_unwrap() which is called after the strip the attack packet requires no valid encryption, HMAC, or WKC content beyond the correct opcode and a matching session ID.
The attack requires a three-packet setup to establish the session context needed for initial_packet to be false the packet flow is P_CONTROL_HARD_RESET_CLIENT_V3 message, then a P_CONTROL_HARD_RESET_SERVER_V2 message and finally the P_CONTROL_WKC_V1 message.
Packets 1 and 3 require a valid “tls-crypt-v2” client key. Packet 3 creates the multi-instance and advances the key state to S_START. Packet 4 (the attack) is processed with initial_packet=false because the session state is now S_START, triggering the vulnerable code path. The attack can be repeated as quickly as the server restarts.
The attack prerequisites are: (1) server has --tls-crypt-v2 enabled in server mode, and (2) attacker possesses any valid “tls-crypt-v2” client key for that server including a legitimately issued, leaked, or revoked key.
To trigger this vulnerability, the server must be configured “tls-crypt-v2” support which requires the generation of a TLS Crypt v2 keypair for the client and server. This can be done with the following command-line.
$ /path/to/openvpn --genkey tls-crypt-v2-server $tls_crypt_v2_server_key
$ /path/to/openvpn --genkey tls-crypt-v2-client $tls_crypt_v2_client_key
Usage of TLS also requires a PKI to be configured. This can be done with the easyrsa script found at https://github.com/openvpn/easy-rsa. To build a CA, the following commands can be used:
$ /path/to/easyrsa --pki=./pki init-pki
$ /path/to/easyrsa --pki=./pki gen-dh
$ /path/to/easyrsa --pki=./pki build-ca
Next, certificates for the server and client can be generated with the following commands. These will generate certificate requests and store the signed certificate in ./pki/issued with the key for the certificate stored at ./pki/private.
$ /path/to/easyrsa --pki=./pki gen-req server
$ /path/to/easyrsa --pki=./pki sign-req server server
$ /path/to/easyrsa --pki=./pki gen-req client
$ /path/to/easyrsa --pki=./pki sign-req client client
The server can be run with the following command-line to to configure the parameters required to trigger this vulnerability.
$ /path/to/openvpn --dev tun --proto udp --port $port --tls-server --ca pki/ca.crt --cert pki/issued/server.crt --key pki/private/server.key --dh pki/dh.pem --tls-crypt-v2 tls-crypt-v2.server.key --topology subnet --ifconfig 10.8.0.1 255.255.255.0
The proof-of-concept also includes a server configuration that can be used to run the OpenVPN server with the correct options.
$ /path/to/openvpn --config=/path/to/server.conf
Once the server is running, the proof-of-concept can be run with the following command-line.
$ python3 poc.py $host $port $tls_crypt_v2_server_key
Server log (--verb 4) confirming the crash:
2026-03-31 21:07:03 udp4:127.0.0.1:60803 Control Channel: using tls-crypt-v2 key
2026-03-31 21:07:03 udp4:127.0.0.1:60803 control channel security already setup ignoring wrapped key part of packet.
2026-03-31 21:07:03 udp4:127.0.0.1:60803 Assertion failed at tls_crypt.c:219 (src->len > 0)
2026-03-31 21:07:03 udp4:127.0.0.1:60803 Exiting due to fatal error
2026-03-31 21:07:03 udp4:127.0.0.1:60803 Closing tun/tap interface
The server was killed on the first attack packet. Total time from first packet to crash: less than 1 second.
Test environment: Linux 5.15.0, OpenVPN 2.8_git (master a04a3ced), ASAN-instrumented build. Also confirmed against the CVE-2025-2704 patched Debian package (2.6.12-0ubuntu0.24.04.3~bpo22.04.1).
The recommended fix is to validate that stripping the WKC leaves a non-empty buffer in the !initial_packet path of tls_crypt_v2_extract_client_key().
2026-04-20 - Initial Vendor Contact
2026-04-20 - Vendor Disclosure
2026-04-26 - Vendor Patch Release
2026-04-27 - Public Release
Discovered by Emma Reuter of Cisco ASIG.