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.