A crafted CMS (S/MIME) EnvelopedData message carrying an oversized wrapped session key (AES Key Wrap ciphertext) can cause a stack buffer overflow in gpg-agent during PKDECRYPT --kem=CMS handling.
Security Advisory: gpg-agent stack buffer overflow via PKDECRYPT --kem=CMS
(ECC KEM)
Vulnerability Type: Stack-based Buffer Overflow
Affected Software: GnuPG
Severity: High
Date: 18 Jan 2026
Discoverer: OpenAI Security Research (OutboundDisclosures@openai.com)Summary
A crafted CMS (S/MIME) EnvelopedData message carrying an oversized wrapped
session key (AES Key Wrap ciphertext) can cause a stack buffer overflow in
gpg-agent during PKDECRYPT --kem=CMS handling. The root cause is an
unbounded session key length derived from attacker-controlled ciphertext
and used as the AES Key Wrap output length when decrypting into a
fixed-size stack buffer. libgcrypt’s AES-KW unwrap copies
attacker-controlled bytes into the caller-provided output buffer before
performing integrity checks, so the stack overflow occurs even if later CMS
or key-wrap integrity validation fails.Affected Versions
- Software Name: GnuPG
- Confirmed Version(s): 2.5.17-beta2, commit:
d97e52cc7fc5cc7bd8629643fdf37105891ae201, date: 2025-12-30T17:52:43+01:00
- Likely Affected: gpg-agent, gpgsm
- Introduced Commit (If relevant): N/A
Vulnerability Details:
Component: gpg-agent (agent) / S/MIME (gpgsm)Affected code:
- agent/pkdecrypt.c in the PKDECRYPT --kem=CMS path (agent_kem_decrypt →
ECC KEM unwrap)
- Reachable via sm/call-agent.c sending PKDECRYPT --kem=CMS
Root cause:
agent_kem_decrypt() trusts an attacker-supplied CMS wrapped-key length to
size an AES Key Wrap unwrap into a fixed 256-byte stack buffer.Execution flow:
- gpgsm parses the CMS encryptedKey and forwards it to gpg-agent via
Assuan (PKDECRYPT --kem=CMS).
- gpg-agent derives encrypted_sessionkey_len = (nbits + 7) / 8 from an
opaque MPI.
- It computes sessionkey_len = encrypted_sessionkey_len - 8 (AES-KW
removes one 64-bit block).
- It calls gcry_cipher_decrypt with sessionkey as the output buffer, where
sessionkey is a fixed-size stack array (unsigned char sessionkey[256]).
- libgcrypt’s AES-KW unwrap begins by copying inbuflen - 8 bytes from the
ciphertext into the output buffer using memmove, overflowing the stack when
sessionkey_len exceeds 256.Relevant code snippet (agent/pkdecrypt.c):
gcry_cipher_hd_t hd;
unsigned char sessionkey[256];
size_t sessionkey_len;encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi,
&nbits);
encrypted_sessionkey_len = (nbits+7)/8;err = gcry_cipher_open (&hd, algo, GCRY_CIPHER_MODE_AESWRAP, 0);
if (is_pgp && encrypted_sessionkey[0] != encrypted_sessionkey_len - 1)
{ err = gpg_error (GPG_ERR_INV_DATA); goto leave; }err = gcry_cipher_setkey (hd, kek, kek_len);
sessionkey_len = encrypted_sessionkey_len - 8 - !!is_pgp;
if (!err)err = gcry_cipher_decrypt (hd, sessionkey, sessionkey_len, encrypted_sessionkey + !!is_pgp, encrypted_sessionkey_len - !!is_pgp);gcry_cipher_close (hd);
Relevant libgcrypt behavior (cipher/cipher-aeswrap.c):
If outbuflen != inbuflen - 8, return error. Otherwise:
memmove(outbuf, inbuf + 8, inbuflen - 8);This initial memmove copies attacker-controlled bytes into the output
buffer before any integrity checks.Trigger conditions
- Recipient uses ECC KEM for CMS (for example
dhSinglePass-stdDH-sha1kdf-scheme, sha256, sha384, or sha512 variants with
AES-KW).
- The CMS encryptedKey (wrapped CEK) is attacker-controlled, a multiple of
8 bytes, and large enough that encrypted_sessionkey_len - 8 > 256 (for
example encrypted_sessionkey_len >= 272; 400 bytes in the proof of concept).
- gpgsm forwards the ciphertext to gpg-agent via PKDECRYPT --kem=CMS with
no length gate on the CMS path.
- This issue is not reachable in the KEM_PGP path because a 1-byte length
prefix check effectively caps encrypted_sessionkey_len to 256 bytes.
Proof-of-Concept (PoC)
A Docker-based reproducer and AddressSanitizer crash bundle is available at:
https://drive.google.com/file/d/1eC-VOCmfeqCFq26tGm6SXD3YUgIRD9lK/viewPoC behavior:
- Decrypting a CMS message with a 400-byte wrapped key reliably triggers a
stack buffer overflow in gpg-agent.
- gpgsm forwards the crafted CMS message to gpg-agent, which crashes during
PKDECRYPT --kem=CMS handling.
Observed output excerpt:
gpgsm: encrypted to nistp256 key 9A0D49D91EBEA6C59E9CE32C053CC090B01500BE
gpgsm: error decrypting session key: End of file
gpgsm: decrypting session key failed: End of file
gpgsm: message decryption failed: End of file <GpgSM>
[GNUPG:] FAILURE gpgsm-exit 50331649ASan or Debug Output
AddressSanitizer reports a stack-buffer-overflow originating from memmove
in libgcrypt during AES-KW unwrap, writing beyond the 256-byte sessionkey
stack buffer in ecc_kem_decrypt. The overflow occurs before CMS or AES-KW
integrity checks complete.Impact
- Reliable denial of service via gpg-agent crash when decrypting crafted
CMS messages.
- gpg-agent is a long-running, high-value process performing private-key
operations, so memory corruption represents a plausible code-execution
primitive depending on platform and hardening.
- Malicious senders who encrypt CMS messages themselves can fully control
the bytes written past the stack buffer because they derive the same KEK as
the victim.
- Passive or man-in-the-middle attackers can also trigger the overflow and
partially influence overwritten data due to the initial ciphertext copy
performed before integrity checks.Recommendation
- Add strict bounds checks before computing or using sessionkey_len.
- Reject CMS inputs where sessionkey_len exceeds the size of the sessionkey
buffer, or allocate the buffer dynamically after validating length.
- Validate CEK sizes against CMS expectations (legitimate CEKs are 16, 24,
or 32 bytes for AES-128/192/256).
- Require AES-KW ciphertext lengths consistent with those CEK sizes.
- Additional hardening: check encrypted_sessionkey for NULL, ensure
early-error paths close cipher handles, and prefer secure memory for key
material.Timeline
- January 2026: Discovered
- January 2026: Reproduced and confirmed
- January 2026: Advisory drafted
This information is being shared by OpenAI solely for the purpose of
improving security and reducing potential harm. This information is
presented as-is. We make no representations or warranties, express or
implied, as to the completeness, accuracy, or fitness for any particular
purpose of the information. This includes, without limitation, any
suggestions or ideas presented on how to remedy or mitigate an identified
vulnerability, including whether such suggestions or ideas would be
effective and/or could have other negative impacts.
OpenAI disclaims any liability for direct or indirect damages arising from
the reliance on, or use, misuse, or interpretation of this information. Any
references to third-party systems, services, or entities are included
solely for identification purposes and do not imply endorsement,
responsibility, or attribution.