A stack-based buffer overflow exists in GnuPG’s tpm2daemon when handling the PKDECRYPT command for TPM-backed RSA and ECC keys.
Security Advisory: Stack-based buffer overflow in TPM2 PKDECRYPT for RSA
and ECC due to missing ciphertext length validation
Vulnerability Type: Stack-based Buffer Overflow
Affected Software: GnuPG
Severity: High
Date: 18 Jan 2026
Discoverer: OpenAI Security Research (OutboundDisclosures@openai.com)Summary
A stack-based buffer overflow exists in GnuPG’s tpm2daemon when handling
the PKDECRYPT command for TPM-backed RSA and ECC keys. A local attacker
who can access the daemon’s Assuan socket can send an oversized ciphertext
and trigger memory corruption, resulting in a crash and potentially
arbitrary code execution. When a user stores private keys inside a TPM,
GnuPG runs a helper process called tpm2daemon to perform cryptographic
operations on their behalf. Other GnuPG components communicate with this
daemon over Assuan, a local IPC protocol. During a PKDECRYPT request,
tpm2daemon copies the attacker-supplied ciphertext into fixed-size TPM
work buffers without validating that the ciphertext fits. If the supplied
ciphertext is larger than the TPM buffer, the copy operation writes past
the end of the stack buffer and corrupts adjacent stack memory. This
affects both supported TPM decrypt paths: RSA (tpm2_rsa_decrypt) and ECC
(tpm2_ecc_decrypt). Because the overflow occurs on the stack and is
attacker-controlled, it is potentially exploitable for code execution
inside the tpm2daemon process.Affected Versions
- Software Name: GnuPG
- Confirmed Version(s): 2.5.17-beta2, commit:
d97e52cc7fc5cc7bd8629643fdf37105891ae201, date: 2025-12-30T17:52:43+01:00
- Likely Affected: tpm2d
- Introduced Commit (If relevant): N/A
Vulnerability Details:
Product: GnuPGComponents:
- tpm2d/command.c handles the PKDECRYPT Assuan command
- tpm2d/tpm2.c implements the RSA and ECC decrypt paths
Trigger surface:
In tpm2d/command.c, function cmd_pkdecrypt:
- EXTRA is read with assuan_inquire using MAXLEN_KEYDATA = 4096.
- The resulting length is passed straight into the decrypt routines.
Code excerpt:
rc = assuan_inquire (ctx, "EXTRA", &crypto, &cryptolen, MAXLEN_KEYDATA); ... if (type == TPM_ALG_RSA) rc = tpm2_rsa_decrypt (ctrl, tssc, key, pin_cb, crypto, cryptolen, &buf, &buflen); else if (type == TPM_ALG_ECC) rc = tpm2_ecc_decrypt (ctrl, tssc, key, pin_cb, crypto, cryptolen, &buf, &buflen);Root cause:
PKDECRYPT handler passes attacker-controlled ciphertext and length into
decrypt routines without enforcing ciphertext length validation. The
decrypt routines copy attacker-controlled ciphertext into fixed-size
buffers using the attacker-controlled length.RSA path:
In tpm2d/tpm2.c:
- tpm2_rsa_decrypt copies the ciphertext into a fixed-size buffer using
ciphertext_len as the copy length.
- No check ensures ciphertext_len is smaller than the destination buffer.
- The destination buffer has a fixed maximum size, while the Assuan input
limit allows much larger values.
Code excerpt:
/* * apparent gcrypt error: occasionally rsa ciphertext will * be one byte too long and have a leading zero */ if ((ciphertext_len & 1) == 1 && ciphertext[0] == 0) { ciphertext_len--; ciphertext++; } cipherText.size = ciphertext_len; memcpy (cipherText.buffer, ciphertext, ciphertext_len);ECC path:
In tpm2d/tpm2.c:
- tpm2_ecc_decrypt splits the ciphertext into x and y coordinate halves
and copies them into fixed-size buffers.
- No check ensures the coordinate length fits in the x and y destination
buffers.
Code excerpt:
if (ciphertext[0] != 0x04) return GPG_ERR_ENCODING_PROBLEM; if ((ciphertext_len & 0x01) != 1) return GPG_ERR_ENCODING_PROBLEM; len = ciphertext_len >> 1; memcpy (VAL_2B (inPoint.point.x, buffer), ciphertext + 1, len); VAL_2B (inPoint.point.x, size) = len; memcpy (VAL_2B (inPoint.point.y, buffer), ciphertext + 1 + len, len); VAL_2B (inPoint.point.y, size) = len;Exploit scenario story:
- A user stores a private key in a TPM and uses it through GnuPG, which
causes gpg-agent to launch and communicate with tpm2daemon.
- An attacker gains the ability to connect to the user’s tpm2daemon
Assuan socket (for example via a malicious plugin, compromised helper
binary, or any code running as that user).
- The attacker sends a PKDECRYPT request with an oversized ciphertext
payload.
- tpm2daemon copies the ciphertext into a fixed-size TPM buffer,
overflows the stack, and crashes — and with sufficient exploitation work,
may execute attacker-controlled code.Notes:
- The overflow occurs before the PIN prompt and before the TPM operation,
so it can be triggered without interacting with the user once a key is
loadable.
- Default GnuPG socket permissions usually restrict access to the same
user, so this is primarily a local attack. If the socket is exposed by
misconfiguration, the impact increases.Proof-of-Concept (PoC)
POC:
https://drive.google.com/file/d/1CHlmAnnxZGpKl-6ZgFOAwbujT1d8izQt/view?usp=sharingPoC description:
- Builds and runs tpm2daemon --server with AddressSanitizer enabled,
- Starts swtpm, a software TPM, and
- Uses Assuan IMPORT to get valid KEYDATA, then sends PKDECRYPT with
a 4096-byte EXTRA to trigger the overflow.
Files:
- keydata.b64: base64-encoded canonical RSA private-key S-expression used
as IMPORT KEYDATA.
- trigger.py: Assuan client that runs IMPORT then triggers the overflow
via PKDECRYPT.
- Dockerfile: builds an ASan-enabled tpm2daemon and runs the PoC.
Run (Docker)
cd /path/to/poc
docker build -t tpm2-poc .
docker run --rm -it tpm2-pocASan or Debug Output
Expected vulnerable result:
On a vulnerable build, tpm2daemon crashes. With AddressSanitizer enabled,
the crash is reported as a stack buffer overflow. Example captured
2026-01-02:docker run --rm -it bug14-poc
OK GNU Privacy Guard's TPM2 server ready
IMPORT returned 528 bytes of KEYDATA
PKDECRYPT caused tpm2daemon to terminate (expected on vulnerable ASan build)
tpm2daemon exit code: -6
- tpm2daemon stderr (ASan report) ---
tpm2daemon[18]: handler for fd -1 started
tpm2daemon[18]: DBG: asking for PIN 'Please enter the TPM Authorization
passphrase for the key.'
ERROR:esys:src/tss2-esys/esys_iutil.c:395:iesys_handle_to_tpm_handle()
Error: Esys invalid ESAPI handle (ff).
ERROR:esys:src/tss2-esys/esys_iutil.c:1116:esys_GetResourceObject() Unknown
ESYS handle. ErrorCode (0x0007000b)
ERROR:esys:src/tss2-esys/esys_tr.c:606:Esys_TRSess_SetAttributes() Objectnot found ErrorCode (0x0007000b)
18==ERROR: AddressSanitizer: stack-buffer-overflow on address
0xffffa5df1382 at pc 0xffffaaa45124 bp 0xffffa6bfd500 sp 0xffffa6bfcce0
WRITE of size 4096 at 0xffffa5df1382 thread T1#0 0xffffaaa45120 in memcpy../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115
#1 0xaaaab411d948 in tpm2_rsa_decrypt /src/gnupg/tpm2d/tpm2.c:991 #2 0xaaaab411447c in cmd_pkdecrypt /src/gnupg/tpm2d/command.c:327 #3 0xffffaa76a284 (/lib/aarch64-linux-gnu/libassuan.so.0+0xa284)(BuildId: 6fb616e77c997cbcf21f487bc05f9a5ac3686073)
#4 0xffffaa76a8dc in assuan_process(/lib/aarch64-linux-gnu/libassuan.so.0+0xa8dc) (BuildId:
6fb616e77c997cbcf21f487bc05f9a5ac3686073)#5 0xaaaab4115248 in tpm2d_command_handler/src/gnupg/tpm2d/command.c:471
#6 0xaaaab4118220 in start_connection_thread/src/gnupg/tpm2d/tpm2daemon.c:1032
#7 0xffffaa731bf0 (/lib/aarch64-linux-gnu/libnpth.so.0+0x1bf0)(BuildId: 8a14b3b6ed49610935f78cac8e74a3586086f2f9)
#8 0xffffaa9bf3d0 in asan_thread_start../../../../src/libsanitizer/asan/asan_interceptors.cpp:234
#9 0xffffaa475958 (/lib/aarch64-linux-gnu/libc.so.6+0x85958) (BuildId:d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
#10 0xffffaa4db898 (/lib/aarch64-linux-gnu/libc.so.6+0xeb898)(BuildId: d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
Address 0xffffa5df1382 is located in stack of thread T1 at offset 898 in
frame#0 0xaaaab411d7c0 in tpm2_rsa_decrypt /src/gnupg/tpm2d/tpm2.c:971 This frame has 8 object(s): [48, 52) 'ah' (line 976) [64, 70) 'inScheme' (line 974) [96, 104) 'auth' (line 977) [128, 136) 'out' [160, 226) 'label' [272, 338) 'authVal2B' [384, 898) 'cipherText' (line 973) [1040, 1554) 'message' (line 975) <== Memory access at offset 898partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack
unwind mechanism, swapcontext or vfork(longjmp and C++ exceptions *are* supported)Thread T1 created by T0 here:
#0 0xffffaaa3edfc in pthread_create../../../../src/libsanitizer/asan/asan_interceptors.cpp:245
#1 0xffffaa731db4 in npth_create(/lib/aarch64-linux-gnu/libnpth.so.0+0x1db4) (BuildId:
8a14b3b6ed49610935f78cac8e74a3586086f2f9)#2 0xaaaab4117920 in main /src/gnupg/tpm2d/tpm2daemon.c:643 #3 0xffffaa4184c0 (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId:d6c205bda1b6e91815f8fef45bdf56bc2239c37e)
#4 0xffffaa418594 in __libc_start_main(/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId:
d6c205bda1b6e91815f8fef45bdf56bc2239c37e)#5 0xaaaab4113e6c in _start (/poc/tpm2daemon+0x13e6c) (BuildId:adfa9ccbcf3a7930b6886c49b758f8c62e42f344)
Impact
- tpm2daemon can be crashed during decrypt operations, breaking
TPM-backed key usage until the daemon is restarted.
- If exploited, an attacker would gain code execution as the same user
account running tpm2daemon, allowing abuse of that user’s TPM-protected
keys and cryptographic operations.Suggested fix
- Enforce strict ciphertext length validation before copying
attacker-controlled data:
- RSA: Require ciphertext_len == key_size_bytes (after handling
leading-zero padding safely).
- ECC: Require ciphertext_len == 1 + 2 * coord_size for the curve and
reject oversized coordinates.
- Query loaded public-key parameters (RSA modulus size, ECC curve) and
validate input sizes against them.
- Reject invalid sizes before any TPM or memory operations.
- Never copy attacker-controlled ciphertext directly into fixed-size TPM
buffers without validation.
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.