OpenSSL CMS Stack Overflow: The 16-Byte Coffin
Jan 29, 2026·6 min read·20 visits
Executive Summary (TL;DR)
OpenSSL versions 3.0 through 3.6 contain a stack overflow in the CMS module. Parsing a malicious 'AuthEnvelopedData' structure with an oversized initialization vector (IV) smashes the stack before signature verification occurs. This allows unauthenticated DoS or RCE.
A high-severity stack-based buffer overflow in OpenSSL's CMS implementation allows unauthenticated remote attackers to crash applications or potentially execute arbitrary code. The vulnerability stems from a logic error in parsing ASN.1 AEAD parameters, where an unchecked length value leads to a catastrophic write past a fixed 16-byte stack buffer.
The Hook: ASN.1, The Gift That Keeps On Taking
If you've been in security long enough, seeing 'ASN.1 parsing vulnerability' in a report triggers a very specific kind of PTSD. It is the ancient language of the cryptographic gods, and it demands blood. CVE-2025-15467 is the latest sacrifice on the altar of complexity.
This isn't some obscure edge case deep inside an authenticated admin panel. This is in the Cryptographic Message Syntax (CMS) parser—the code responsible for handling S/MIME emails, PKCS#7 structures, and various secure enrollment protocols. If your server accepts signed or encrypted data, it likely touches this code.
The real kicker? This vulnerability triggers before cryptographic verification. In the world of exploit development, this is the holy grail: Pre-Auth Remote Code Execution (or at least a very reliable Denial of Service). The code blindly trusts the structure of the message while trying to figure out how to decrypt it, leading to a classic memory corruption scenario that feels like it belongs in the 1990s, yet here we are in the OpenSSL 3.x era.
The Flaw: Trusting the Probe
The root cause lies in crypto/evp/evp_lib.c, specifically within the evp_cipher_get_asn1_aead_params() function. This function's job is to extract Authenticated Encryption with Associated Data (AEAD) parameters, such as the Initialization Vector (IV) for AES-GCM, from an ASN.1 stream.
The developers made a fatal assumption about how their internal helper function, ossl_asn1_type_get_octetstring_int, behaves. They used a pattern that I like to call 'Probe and Pray'.
First, they call the helper with a NULL buffer to ask, 'Hey, how big is this data?' The helper returns the actual size of the data found in the malicious packet (say, 512 bytes). Crucially, the code took this return value and immediately used it as the size argument for a second call—this time passing a fixed-size stack buffer of only 16 bytes (EVP_MAX_IV_LENGTH).
It’s the programming equivalent of asking a stranger, 'How much water do you have?' and when they say '500 gallons,' you try to pour it all into a shot glass because you assumed they would only give you what fits. The result? The stack gets flooded.
The Code: Anatomy of a Stack Smash
Let's look at the smoking gun. This is the C code responsible for the carnage. I've stripped it down to the relevant logic.
The Vulnerable Code:
// crypto/evp/evp_lib.c
int evp_cipher_get_asn1_aead_params(EVP_CIPHER_CTX *c, ASN1_TYPE *type, ...) {
// A fixed-size stack buffer. EVP_MAX_IV_LENGTH is 16.
unsigned char iv[EVP_MAX_IV_LENGTH];
long i;
// Step 1: Probe the length. Passing NULL as the buffer.
// The helper returns the size of the ASN.1 string (e.g., 512).
i = ossl_asn1_type_get_octetstring_int(type, &tl, NULL, EVP_MAX_IV_LENGTH);
if (i <= 0) return -1;
// Step 2: The fatal copy.
// We tell it to write 'i' bytes (512) into 'iv' (16 bytes).
ossl_asn1_type_get_octetstring_int(type, &tl, iv, i);
// ... execution continues (or crashes) ...
}The fix, implemented by Igor Ustinov, is a lesson in defensive programming. Instead of trusting the probe, they force the copy to respect the buffer limits in a single atomic-style operation.
The Patched Code:
// The Fix: Do it all at once with a hard limit.
i = ossl_asn1_type_get_octetstring_int(type, &tl, iv, EVP_MAX_IV_LENGTH);
// Explicitly check if the result is valid AND fits in the buffer.
if (i <= 0 || i > EVP_MAX_IV_LENGTH)
return -1;By passing iv and EVP_MAX_IV_LENGTH immediately, and checking the return bounds, the overflow is prevented. Simple, effective, and tragically missing from the original implementation.
The Exploit: Building the Bomb
To trigger this, we don't need a complex race condition or heap feng shui. We just need to build a valid-looking CMS AuthEnvelopedData structure that lies about its size—or rather, is brutally honest about being too big.
The target is the GCMParameters sequence. In ASN.1, this looks roughly like SEQUENCE { nonce OCTET STRING, icvlen INTEGER }. The nonce is our IV. OpenSSL expects this to be roughly 12 bytes (for GCM) or up to 16 bytes max.
We create a payload where the nonce is an OCTET STRING of 512 'A's. When d2i_CMS_bio() parses this, it eventually hands the object to evp_cipher_get_asn1_aead_params.
# Conceptual Exploit Logic
nonce = univ.OctetString(b'A' * 512) # The sledgehammer
icvlen = univ.Integer(16)
gcm_params = univ.Sequence()
gcm_params.setComponentByPosition(0, nonce)
gcm_params.setComponentByPosition(1, icvlen)
# Wrap this in CMS AuthEnvelopedData and fire at port 443/25When the overflow happens on the stack, it overwrites the saved return address (on architectures without stack canaries) or triggers the stack smashing protector (on modern systems). On a system without ASLR/NX and Canaries (embedded devices, anyone?), this is trivial RCE. On hardened systems, it is a guaranteed crash (DoS).
The Impact: Why Panic?
The severity here is driven by where this code lives. OpenSSL is the bedrock of the internet. While many web servers (Nginx/Apache) might not parse CMS structures by default during a standard TLS handshake, many application-layer protocols do.
Think about Email Gateways processing S/MIME, Mobile Device Management (MDM) solutions using SCEP or EST, or any custom application that accepts signed documents. These systems are often automated, high-privilege, and internet-facing.
Because the crash happens on the stack, it is extremely violent. A single malicious packet kills the process. If that process is your main ingress controller or a threaded worker, you can effectively take an entire organization offline with a Python script. If you are a sophisticated actor and the target is compiled loosely (no canaries), you own the box.
The Fix: Update or Die
There is no configuration workaround for this. You cannot 'turn off' the vulnerable logic if your application needs to parse CMS data. The logic is baked into libcrypto.
You must upgrade. The fixed versions are:
- OpenSSL 3.6 -> 3.6.1
- OpenSSL 3.5 -> 3.5.5
- OpenSSL 3.4 -> 3.4.4
- OpenSSL 3.3 -> 3.3.6
- OpenSSL 3.0 -> 3.0.19
If you are running OpenSSL 3.1 or 3.2, you are on an End-of-Life branch. You are vulnerable, and no one is coming to save you. Upgrade to a supported LTS branch immediately.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
OpenSSL OpenSSL Software Foundation | 3.6.0 | 3.6.1 |
OpenSSL OpenSSL Software Foundation | 3.5.0 - 3.5.4 | 3.5.5 |
OpenSSL OpenSSL Software Foundation | 3.4.0 - 3.4.3 | 3.4.4 |
OpenSSL OpenSSL Software Foundation | 3.3.0 - 3.3.5 | 3.3.6 |
OpenSSL OpenSSL Software Foundation | 3.0.0 - 3.0.18 | 3.0.19 |
| Attribute | Detail |
|---|---|
| CWE | CWE-787 (Out-of-bounds Write) |
| CVSS v3.1 | 9.8 (Critical) |
| Attack Vector | Network (Pre-Auth) |
| Impact | DoS / RCE |
| EPSS Score | 0.12% |
| Vulnerable Component | crypto/evp/evp_lib.c |
MITRE ATT&CK Mapping
The software writes past the end of the intended buffer, which can corrupt data, crash the program, or lead to code execution.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.