CVE-2026-65634 Overview
CVE-2026-65634 is an algorithmic complexity vulnerability in the Erlang/OTP asn1 library's OBJECT IDENTIFIER (OID) decoder. A remote unauthenticated attacker can send a crafted OID during the TLS handshake to trigger quadratic CPU work in the decoder. Because the vulnerable code path runs inside public_key:pkix_decode_cert/2 before any signature or trust chain verification, any Erlang service that parses peer TLS certificates is exposed. This includes default TLS clients that parse the server certificate and mutual-TLS servers that parse client certificates. A single DER-encoded OID with roughly 262 KB of continuation bytes can consume approximately 13 seconds of CPU on typical hardware. The weakness is tracked as CWE-407: Inefficient Algorithmic Complexity.
Critical Impact
Unauthenticated attackers can exhaust CPU on Erlang TLS clients and mTLS servers by sending a single crafted certificate during the handshake.
Affected Products
- Erlang/OTP 17.0 through versions before OTP 27.3.4.18
- Erlang/OTP 28.x before 28.5.0.7
- Erlang/OTP 29.x before 29.1.1 (corresponding to asn1 3.0 through before 5.3.4.3, 5.4.3.1, and 5.5.2)
Discovery Timeline
- 2026-09-22 - CVE-2026-65634 published to NVD
- 2026-09-22 - Last updated in NVD database
Technical Details for CVE-2026-65634
Vulnerability Analysis
The flaw lives in the BER OID decoder asn1rtt_ber:dec_subidentifiers/3 in lib/asn1/src/asn1rtt_ber.erl and the equivalent PER helper asn1rtt_per_common:dec_subidentifiers/3 in lib/asn1/src/asn1rtt_per_common.erl. Both accumulate a base-128 subidentifier into an unbounded integer using the expression (Av bsl 7) + H per continuation byte. Each shift and addition on the growing accumulator is linear in the number of bits already accumulated, producing quadratic total work in the length of a single subidentifier. The JER helper asn1rtt_jer:json2oid/1 in lib/asn1/src/asn1rtt_jer.erl exhibits the same class of unbounded-integer parsing when decoding a dot-separated OID from JSON.
Root Cause
The decoders never bound the size of a single OID arc. Erlang's arbitrary-precision integers make each bsl 7 operation on the accumulator progressively more expensive. Attackers can weaponize this to force disproportionate CPU consumption from a small network payload.
Attack Vector
The vulnerable decoder is generated into every ASN.1 module containing an OBJECT IDENTIFIER, including OTP-PUB-KEY, which is invoked during X.509 certificate parsing via public_key:pkix_decode_cert/2. Because certificate parsing runs before signature or trust chain validation, an unauthenticated peer can deliver the malicious OID inside a certificate presented during the TLS handshake. Repeated connections amplify the CPU cost into a denial-of-service condition.
%% Patch excerpt: bound the number of continuation bytes
%% Source: https://github.com/erlang/otp/commit/0fe2c02fdc06fab1c63be9db1a7456993d20f838
decode_tag(Buffer) ->
decode_tag(Buffer, 0, 0).
decode_tag(<<0:1, PartialTag:7, Buffer/binary>>, N, TagAck) when N < 32 ->
TagNo = (TagAck bsl 7) bor PartialTag,
{TagNo, Buffer};
decode_tag(<<_:1, PartialTag:7, Buffer/binary>>, N, TagAck) when N < 32 ->
TagAck1 = (TagAck bsl 7) bor PartialTag,
decode_tag(Buffer, N + 1, TagAck1).
Source: GitHub Commit 0fe2c02. The patch adds an N < 32 guard that rejects overlong tag components before the accumulator can grow.
Detection Methods for CVE-2026-65634
Indicators of Compromise
- Erlang scheduler threads pinned at 100% CPU shortly after inbound TLS connections from untrusted peers.
- TLS handshakes that stall or time out on the certificate parsing stage without reaching ssl:connection_information/1.
- Inbound X.509 certificates containing OBJECT IDENTIFIER arcs with abnormally long chains of continuation bytes (high bit set).
Detection Strategies
- Instrument public_key:pkix_decode_cert/2 call sites and log any invocation that exceeds a small millisecond budget.
- Perform inline DER inspection at TLS terminators or reverse proxies to reject certificates whose OID subidentifier length exceeds a sane bound (for example, 32 bytes).
- Correlate spikes in scheduler utilization with source IPs opening new TLS or mTLS sessions to identify probable attackers.
Monitoring Recommendations
- Track per-node Erlang scheduler utilization and run-queue length; sustained saturation without traffic growth warrants investigation.
- Alert on TLS handshake latency percentiles regressing on services that accept peer certificates, especially mTLS endpoints.
- Retain packet captures or JA3/JA4 fingerprints for handshake failures to enable retrospective analysis of malicious clients.
How to Mitigate CVE-2026-65634
Immediate Actions Required
- Upgrade Erlang/OTP to 27.3.4.18, 28.5.0.7, or 29.1.1 (or a later release), which correspond to asn15.3.4.3, 5.4.3.1, and 5.5.2 respectively.
- Rebuild and redeploy any release that vendors its own asn1 beam files; simply updating the runtime is insufficient if generated modules are shipped inside the release.
- Audit all Erlang services that terminate TLS or perform mTLS and prioritize internet-exposed nodes.
Patch Information
The fix is available in the upstream commit erlang/otp@0fe2c02 and documented in the GitHub Security Advisory GHSA-qghx-23m5-r55m. Additional detail is available in the Erlang Ecosystem Foundation CNA record and the OSV vulnerability record. The patch caps OID tag components at 32 continuation bytes and rejects overlong JSON-encoded OID components in json2oid/1.
Workarounds
- Terminate TLS at a non-Erlang proxy that validates certificate structure and rejects malformed OIDs before the traffic reaches Erlang nodes.
- Disable optional mTLS on services that do not strictly require it to reduce the attack surface for unauthenticated clients.
- Apply per-source connection rate limiting and CPU cgroups to contain scheduler exhaustion until the runtime upgrade is deployed.
# Verify the installed OTP version meets the fixed release
erl -eval 'io:format("~s~n", [erlang:system_info(otp_release)]), halt().' -noshell
# Confirm the asn1 application version loaded at runtime
erl -eval 'application:load(asn1), {ok, V} = application:get_key(asn1, vsn), io:format("asn1 vsn: ~s~n", [V]), halt().' -noshell
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

