CVE-2026-54890 Overview
CVE-2026-54890 is an integer underflow vulnerability [CWE-191] in the Erlang/OTP External Term Format (ETF) decoder. The BIT_BINARY_EXT tag (77) handler in erts/emulator/beam/external.c accepts an encoding with both length and trailing-bits fields set to zero. Subsequent bitstring size arithmetic underflows an unsigned integer, yielding a value near 2^64 that is passed to the memory allocator. The allocator aborts the entire Erlang node with a fatal message. Any application that decodes ETF from untrusted sources through binary_to_term/1,2 or enif_binary_to_term() is exposed.
Critical Impact
A single malformed ETF payload triggers a VM-level abort that supervision trees, try/catch, and the [safe] option cannot intercept, causing full-node denial of service.
Affected Products
- Erlang/OTP 27.0 through versions before 29.0.4
- Erlang/OTP 28.5.0.4 and 27.3.4.15 (fixed releases on maintenance branches)
- erts runtime system 15.0 through before 17.0.4, 16.4.0.4, and 15.2.7.11
Discovery Timeline
- 2026-07-27 - CVE-2026-54890 published to NVD
- 2026-07-30 - Last updated in NVD database
Technical Details for CVE-2026-54890
Vulnerability Analysis
The defect resides in the ETF decoder path that processes BIT_BINARY_EXT (tag 77) terms. This tag encodes an Erlang bitstring using a byte length plus a trailing-bits count that indicates how many bits in the final byte are significant. The decoder computes the total bit size as (length * 8) - (8 - trailing_bits) using unsigned arithmetic. When an attacker submits a payload with length = 0 and trailing_bits = 0, the expression evaluates to 0 - 8 and wraps to approximately 2^64.
The wrapped value is then passed as an allocation size for a binary buffer. The runtime cannot satisfy the request and aborts with a message such as Cannot allocate 2305843009213693951 bytes of memory (of type binary). The failure occurs inside the emulator itself, not inside a scheduled Erlang process.
Because the abort happens at the C runtime layer, it bypasses every Erlang-level safety mechanism. Supervision trees cannot restart the crashed component. A try/catch around binary_to_term/1 never runs its handler. The [safe] option to binary_to_term/2 only restricts atom creation and does not perform structural validation of binary encodings.
Root Cause
The root cause is missing input validation on the BIT_BINARY_EXT fields combined with unsigned integer arithmetic that underflows without checks. Both length == 0 and trailing_bits == 0 should be rejected as structurally invalid before size computation.
Attack Vector
Any endpoint that calls binary_to_term/1, binary_to_term/2, or the NIF enif_binary_to_term() on network-attacker-controlled input is exposed. Message queues, HTTP handlers, and RPC endpoints that accept ETF payloads can be crashed by a small crafted binary. The Erlang distribution protocol shares the same decoder path, but distribution is expected to run on trusted networks per the OTP Secure Coding Guidelines (DSG-011).
if (ep[-1] == BIT_BINARY_EXT) {
Uint trailing_bits = ep[4];
- if (((trailing_bits == 0) != (nu == 0)) ||
- trailing_bits > 8) {
+ /* We accept a trailing bit count of 8 for backwards
+ * compatibility reasons, even though it's not the most
+ * compact representation. */
+ if (trailing_bits < 1 ||
+ trailing_bits > 8 ||
+ size_in_bits < 8) {
goto error;
}
Source: Erlang/OTP commit dc1bf9344c0ce62717cf60866590cea0242780fd. The patch tightens validation by rejecting trailing_bits outside the range 1-8 and requiring size_in_bits to be at least 8, eliminating the underflow condition.
Detection Methods for CVE-2026-54890
Indicators of Compromise
- Sudden abnormal termination of an Erlang node with a memory allocator abort message referencing an allocation of approximately 2305843009213693951 bytes of type binary.
- erl_crash.dump files created on hosts running Erlang/OTP 27.0 through 29.0.3 that reference the ETF decoder.
- Repeated inbound requests carrying ETF payloads with BIT_BINARY_EXT (byte value 77) followed by four zero length bytes and a zero trailing-bits byte.
Detection Strategies
- Monitor supervisor restart telemetry and process managers (systemd, Kubernetes) for repeated Erlang node exits without corresponding application-level exceptions.
- Inspect application logs for the allocator abort string; VM-level aborts do not produce standard Erlang stack traces.
- Deploy network inspection or WAF rules on services that ingest ETF (for example, RabbitMQ, CouchDB, ejabberd, EMQX) to flag BIT_BINARY_EXT frames with zero-valued size fields.
Monitoring Recommendations
- Alert on unexpected beam.smp process exits and correlate with inbound traffic captured at the same timestamp.
- Track the deployed erts version across the fleet and flag any host running an unpatched release from the affected ranges.
- Enable audit logging on any code path that calls binary_to_term/1,2 or enif_binary_to_term() against externally sourced data.
How to Mitigate CVE-2026-54890
Immediate Actions Required
- Upgrade to Erlang/OTP 29.0.4, 28.5.0.4, or 27.3.4.15, corresponding to erts 17.0.4, 16.4.0.4, or 15.2.7.11 respectively.
- Inventory all applications that call binary_to_term/1,2 or enif_binary_to_term() on data sourced from untrusted networks and prioritize them for patching.
- Restrict exposure of ETF-consuming endpoints to trusted networks until the runtime is upgraded.
Patch Information
The fix is committed in the Erlang/OTP repository as dc1bf9344c0ce62717cf60866590cea0242780fd and released in OTP 29.0.4, 28.5.0.4, and 27.3.4.15. Details are published in the GitHub Security Advisory GHSA-54pw-5645-jh86 and the Erlang Ecosystem Foundation CNA record.
Workarounds
- Wrap ETF-decoding endpoints with a proxy or protocol filter that rejects BIT_BINARY_EXT payloads with a zero length or zero trailing-bits field.
- Replace binary_to_term/1,2 on untrusted input with a strict, application-defined codec (for example, JSON with a validating schema) until the patched runtime is deployed.
- Enforce the OTP Secure Coding Guideline DSG-011 by ensuring Erlang distribution runs only across trusted network segments.
# Verify the installed Erlang/OTP and erts versions
erl -eval 'io:format("OTP ~s / erts ~s~n", [erlang:system_info(otp_release), erlang:system_info(version)]), halt().' -noshell
# Expected patched output examples:
# OTP 29 / erts 17.0.4
# OTP 28 / erts 16.4.0.4
# OTP 27 / erts 15.2.7.11
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

