OpenVPN 2.7.8 fixes CVE-2026-84790, a bug where a certificate whose common name contains a hidden NUL byte, such as admin\0impersonator, is treated as belonging to admin. A peer holding such a certificate could pass a --verify-x509-name admin check, and on a server it would be handed admin's per-client configuration. OpenVPN says versions from 2.3.0 onwards are affected. The current 2.6 release doesn't contain the fix.
This post is for anyone running OpenVPN servers or clients that make decisions based on certificate names. It covers how the bug works in the source, who can realistically exploit it, the gap for 2.6 users, a script to check whether your CA has ever issued such a certificate, and the mitigations that don't depend on names.
The short answer
CVE-2026-84790 (CVSS 4.0 score 7.6) affects OpenVPN 2.3.0 through 2.7.7 and is fixed in 2.7.8, released on 7 October 2026. Its CVE record was published on 13 October. The latest 2.6 release, 2.6.23, doesn't include the check. OpenVPN credits Vivek Parikh with the report. Exploiting it requires a certificate signed by a CA your OpenVPN trusts, with a NUL byte embedded in the subject field you match on. If your CA only issues certificates with subjects it chose itself, your exposure is low. If it signs whatever subject a request asks for, upgrade now. Either way, audit what it has already issued.
2.7.8 also fixes two more bugs that are triggered by hostile input from the other side of the connection:
| CVE | What it is | Affected | Fixed in |
|---|---|---|---|
| CVE-2026-84790 | NUL byte in certificate subject lets a peer match another identity | 2.3.0 to 2.7.7 (per OpenVPN) | 2.7.8 |
| CVE-2026-84256 (published 7 Sep) | Windows: crafted certificate subject leads to command execution through cmd.exe argument handling | 2.1_rc10 to 2.6.22, 2.7_alpha1 to 2.7.7 | 2.6.23 (partial), 2.7.8 (adds a second fix) |
| CVE-2026-88964 | Windows and Android clients: integer underflow on a malicious server's pushed DNS search domains, causing a crash or memory disclosure | 2.7_alpha3 to 2.7.7 | 2.7.8 |
One discrepancy to note: the CVE record for CVE-2026-84790 lists only 2.7_alpha1 through 2.7.7, while OpenVPN's announcement says 2.3.0 through 2.7.7. We compared the source and found the same code without the check in 2.6.23. Go by the announcement.
How a NUL byte becomes someone else's name
X.509 certificate fields are length-prefixed byte strings, so a common name can legally carry a \0 in the middle. C strings end at the first \0. Every bug of this kind lives in the gap between those two facts.
According to the fix commit, with the OpenSSL backend, OpenVPN already escaped embedded NULs when it printed the whole subject, but extract_x509_field_ssl() copied an individual field's bytes, NUL included, into a C buffer. In 2.7.7, that extracted field becomes the session's common name through backend_x509_get_username(), and it is formatted with %s, which stops at the NUL. Two consumers of that value matter most:
--verify-x509-namecompares the common name withstrcmp(). The commit message's own example is a certificate foradmin\0impersonatorbeing accepted under--verify-x509-name admin name.--client-config-dirlooks up the per-client file by the common name. So on a server, the impostor gets the realadmin's file: its static VPN address, its routes and anything else pushed from there.
Here is the same truncation in a few lines of Python, using ctypes to give the bytes C-string semantics:
import ctypes
cn = "admin\x00impersonator".encode() # the CN bytes inside the certificate
buf = ctypes.create_string_buffer(cn) # copied into a C char buffer, NUL included
print(len(cn), "bytes in the certificate")
print("C string reads:", buf.value) # what strcmp() and "%s" see
print("matches 'admin':", buf.value == b"admin")
18 bytes in the certificate
C string reads: b'admin'
matches 'admin': True
The fix makes x509_get_subject() refuse any certificate whose subject contains a NUL, and adds the same check where individual fields are extracted, for both the OpenSSL and Mbed TLS backends. Peers presenting such certificates now fail verification outright. The same commit also makes the Mbed TLS backend use the last matching field when a subject has two CNs, to match OpenSSL. If you build anything that keys access on a certificate name, that is worth knowing about.
We didn't run an end-to-end exploit against a live OpenVPN server. The behaviour above comes from the fix commit, the 2.7.7 source, and the commit's own unit tests.
Who can actually exploit this
The attacker needs a certificate your OpenVPN trusts. That limits the bug a lot, and it is why the advisory says "remote authenticated users". In practice the risk depends on how your CA decides what goes in a subject:
- The CA signs the subject the requester asks for. Self-service enrollment portals, automated signing of certificate signing requests (CSRs), and scripts that pass a user-supplied name straight to the CA all fall here. Any user who can get a certificate can ask for
admin\0something. This is the case to worry about. - The CA sets the subject itself, for example from a verified directory username that can't contain control characters. An attacker would first need to compromise the CA or the issuance process, and at that point a NUL byte is the least of your problems.
- Clients checking the server's name. Many deployments sign clients and the server with one CA. Without
--remote-cert-tls server, which requires the server extended key usage, a client certificate namedvpn.example.com\0xcould pass a client's--verify-x509-name vpn.example.comcheck and impersonate the server to other clients.
The 2.6 gap
As of 14 October 2026, OpenVPN's 2.6 branch ends at 2.6.23, released on 23 September 2026. Its changelog has nine security entries covering seven CVEs, including a first fix for CVE-2026-84256, but nothing for CVE-2026-84790. Its ssl_verify_openssl.c lacks both the NUL check in x509_get_subject() and the length check in extract_x509_field_ssl() that 2.7.8 added.
If you run 2.6, which many long-lived servers and distribution packages do, you have three options: move to 2.7.8, check whether your distribution has backported the fix to its 2.6 package, or rely on the mitigations below until a 2.6 release includes it.
Check whether your CA ever issued one
This is the most useful thing to do this week, whatever version you run. The script reads every PEM certificate under a directory, such as Easy-RSA's pki/issued, and flags subject fields containing NUL or other control characters:
#!/usr/bin/env python3
"""Flag certificates whose subject contains NUL or other control characters
(the CVE-2026-84790 pattern). Point it at your CA's issued-certificate store,
e.g. Easy-RSA's pki/issued. Needs: pip install cryptography
Usage: python3 cert_subject_audit.py <dir> [<dir> ...]"""
import pathlib, re, sys
from cryptography import x509
PEM = re.compile(rb"-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----", re.S)
BAD = re.compile(r"[\x00-\x1f\x7f]")
found = scanned = 0
for root in sys.argv[1:]:
for path in pathlib.Path(root).rglob("*"):
if path.suffix.lower() not in {".crt", ".pem", ".cer"} or not path.is_file():
continue
for block in PEM.findall(path.read_bytes()):
scanned += 1
cert = x509.load_pem_x509_certificate(block)
for attr in cert.subject:
if isinstance(attr.value, str) and BAD.search(attr.value):
found += 1
print(f"{path}: serial {cert.serial_number:x} {attr.oid._name}={attr.value!r}")
print(f" C string sees {attr.oid._name}={attr.value.split(chr(0))[0]!r}")
print(f"\n{scanned} certificate(s) scanned, {found} suspicious subject field(s)")
sys.exit(1 if found else 0)
We tested it on a throwaway CA with three client certificates (admin, alice, and one issued with the CN admin\x00impersonator):
pki/issued/mallory.crt: serial 77ed250efa05a89396336414a5c3421d6f147064 commonName='admin\x00impersonator'
C string sees commonName='admin'
3 certificate(s) scanned, 1 suspicious subject field(s)
openssl x509 -noout -subject also shows the byte, as /CN=admin\x00impersonator with the LibreSSL build we used. If you get a hit, revoke the certificate, publish the updated certificate revocation list, and check your OpenVPN logs for connections using that serial.
The script only sees certificates the CA kept copies of. If your CA doesn't archive what it signs, add the same check to the signing step: refuse any CSR whose subject contains a control character.
What to do
- Upgrade to 2.7.8 on servers and clients. That covers all three CVEs above.
- On 2.6, install 2.6.23 now, but don't treat it as complete. It has the first fix for CVE-2026-84256, while 2.7.8 adds a second one (stopping
cmd.exefrom expanding variables in quoted arguments) that isn't in any 2.6 release yet. Treat CVE-2026-84790, and on Windows part of CVE-2026-84256, as open until your package includes those fixes. - Audit and fix issuance. Run the script against your CA's issued certificates, revoke anything it flags, and make the CA reject control characters in requested subjects.
- On clients, add
--remote-cert-tls server. It requires the server's extended key usage, so a client certificate can't stand in for the server whatever its name says. - For small setups, consider
--peer-fingerprint. Available since 2.6.0, it pins the expected peer certificate by SHA-256 fingerprint instead of trusting a name. That sidesteps this whole class of bug.
The pattern behind it
Two of the three CVEs in this release are triggered by a crafted certificate subject. One turns it into a different identity, and on Windows the other turns it into a command. In both cases the subject was treated as text OpenVPN controlled, when it is data a CA copied, possibly from a user. The fix direction is the same in both: refuse what can't be represented safely, rather than trying to escape it after the fact.
To be fair to OpenVPN, the precondition here is real. Most deployments don't hand out certificates with user-chosen subjects, and the exploit can't start from outside your PKI. But identity checks based on string equality are a habit across VPNs, proxies and internal mTLS services. The same audit question applies to all of them: can a name contain a byte that your comparison doesn't see?