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: GnuPG
>
> Components:
> - `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:
> 1. A user stores a private key in a TPM and uses it through GnuPG, which
> causes `gpg-agent` to launch and communicate with `tpm2daemon`.
> 2. 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).
> 3. The attacker sends a `PKDECRYPT` request with an oversized ciphertext
> payload.
> 4. `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=sharing
>
> PoC 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-poc
>
> ASan 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() Object
> not 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 898
> partially 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.
>