Page MenuHome GnuPG

Libgcrypt ECDH buffer overwrite with zeroes
Closed, ResolvedPublic

Description

We received this report on 2026-04-07:

_gcry_ecc_mont_decodepoint() at cipher/ecc-misc.c:441 miscomputes the
destination offset of a zero-padding memset() when decoding an opaque
Curve25519 or X448 point whose byte length is less than half the curve's
coordinate size: [...]

Which affects all Libgcrypt versions >= 1.8.8, GnuPG 2.5 is not affected due to the use of the new KEM API.

Reported-by: Bronson Yen, Calif.io on behalf of Anthropic Coordinated Vulnerability Disclosure program

Event Timeline

werner triaged this task as Unbreak Now! priority.Thu, Apr 9, 8:23 AM
werner created this task.
werner created this object in space Restricted Space.
werner created this object with edit policy "Contributor (Project)".
gniibe lowered the priority of this task from Unbreak Now! to High.Fri, Apr 10, 10:04 AM

Here is the fix:

Note that I also add updating RAWMPILEN, applying suggested padding fix; It becomes more consistent as a whole.

This patch should be applied to all branches of (1.8, 1.9, if really needed) 1.10, 1.11, and master.

gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Apr 13, 6:35 AM

1.8.13 (T8224) and 1.12.2 (T8114) are released

gniibe mentioned this in Unknown Object (Maniphest Task).Mon, Apr 20, 9:40 AM
werner shifted this object from the Restricted Space space to the S1 Public space.Wed, Apr 22, 10:12 AM

This is the original bug report to security at gnupg dated 2026-04-07:

SUMMARY

_gcry_ecc_mont_decodepoint() at cipher/ecc-misc.c:441 miscomputes the
destination offset of a zero-padding memset() when decoding an opaque
Curve25519 or X448 point whose byte length is less than half the curve's
coordinate size:

rawmpi = xtrymalloc (nbytes);              /* 32 or 56 */
...
if (rawmpilen < nbytes)
  memset (rawmpi + nbytes - rawmpilen, 0, nbytes - rawmpilen);
                   ^^^^^^^^^^^^^^^^^^
          should be: rawmpi + rawmpilen

The reverse-copy at lines 436-438 fills rawmpi[0..rawmpilen-1]; the intent
is to zero rawmpi[rawmpilen..nbytes-1]. When rawmpilen < nbytes/2, the
write region [nbytes-rawmpilen, 2*(nbytes-rawmpilen)) overruns the
nbytes-byte allocation by (nbytes - 2*rawmpilen) bytes -- up to 30 zero
bytes past a 32-byte chunk for Curve25519, or 54 past a 56-byte chunk for
X448. Lines 423-430 reject oversized inputs but impose no lower bound.

REACHABILITY

The bug is reachable from the public gcry_pk_decrypt() API in a default
build with no non-default configuration:

gcry_pk_decrypt              src/visibility.c:1038
 -> _gcry_pk_decrypt          cipher/pubkey.c:416
  -> ecc_decrypt_raw          cipher/ecc.c:1515
     sexp_extract_param(l1, NULL, "/e", &data_e, NULL)
       -- the "/" flag extracts the ephemeral point as an OPAQUE MPI
       -- preserving the wire byte length exactly; no length floor
  -> _gcry_ecc_mont_decodepoint   cipher/ecc-misc.c:441
     memset(rawmpi + 31, 0, 31)   -- 30 bytes OOB on 1-byte input

An attacker who sends ECDH ciphertext containing the S-expression
(enc-val (ecdh (s #..#) (e #41#))) -- a deliberately short 1-byte
ephemeral public point -- to a recipient holding a Curve25519 or X448
private key triggers the overflow before any cryptographic validation.

A second path via gcry_mpi_ec_new() Montgomery public-key parsing
(cipher/ecc-curves.c:999) reaches the same sink with an identical
primitive but a weaker threat model (attacker supplies a public key
rather than a ciphertext).

IMPACT

CVSS 3.1: AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H -- 7.5 (HIGH)

The attacker controls the overflow length (2-30 bytes Curve25519, 2-54
bytes X448) and the heap size-class (32-byte or 56-byte glibc tcache bin),
but NOT the written value (fixed 0x00) or the start offset (always
linear-adjacent, 0 bytes past chunk end). On glibc, 30 OOB zero bytes
past a 32-byte request clobber the next chunk's prev_size and size header
plus ~14 bytes of its payload.

  • Heap-metadata corruption / DoS: trivially achievable; zeroing the next chunk's size field aborts on next free/consolidation.
  • RCE: credible via adjacent-object zero-write (flag-clearing, length-nulling, low-byte-pointer-overwrite) with heap grooming; not demonstrated. Modern glibc hardening (safe-linking, top-chunk checks) raises the bar substantially.

We rate this HIGH rather than CRITICAL because the write value is fixed
at zero and the offset is non-steerable.

GnuPG through 2.5.6 forwards the attacker-controlled ephemeral-point
length to gcry_pk_decrypt() unvalidated: sos_read (g10/parse-packet.c)
reads a 2-byte bit-count prefix with an upper bound only, builds the
(enc-val(ecdh(s%m)(e%m))) S-expression with the resulting opaque MPI,
and ships it to gpg-agent's plain PKDECRYPT handler. The overflow fires
in gpg-agent's address space when a malicious OpenPGP message is
decrypted with a cv25519 subkey -- the GnuPG default since 2.1. GnuPG
2.5.7+ (June 2025) is incidentally protected by commit 07e8ca2a, which
reroutes ECDH through a KEM interface with an exact-length check; this
was a refactor, not a security fix. The 2.4.x stable branch and the
2.2.x LTS branch remain on the unguarded path.

A secondary observation: line 464 calls _gcry_mpi_set_buffer with the
short rawmpilen, so the padded bytes are never actually consumed -- the
memset is functionally dead code, which may explain why the bug escaped
test coverage.

AFFECTED VERSIONS

The buggy memset was introduced on master by commit
bbe15758c893dbf546416c1a6bccdad1ab000ad7 (2019-10-23, "ecc: Fix
_gcry_ecc_mont_decodepoint for data by old implementation"). That commit
changed the allocation size from rawmpilen to nbytes and added the
zero-padding memset to fill the new larger buffer -- with the wrong
destination offset.

The same line was backported to STABLE-BRANCH-1-8 by commit
3f48e3ea37adf84aae7335b8367012d70bb3f132 (2021-04-28). The backport's
stated intent was to add the upper-bound length check from master
commit 060c378c, but it copied the surrounding xtrymalloc(nbytes) +
memset refactor along with it.

master:        1.9.0  (2021-01-19) -- 1.12.1 (2026-02-20), HEAD 39aca53
1.8 branch:    1.8.8  (2021-06-02) -- 1.8.12 (2025-10-28)
unaffected:    1.8.0 -- 1.8.7

Every libgcrypt release since January 2021 ships the bug.

REPRODUCTION

The attached zip contains a self-contained run_poc.sh that builds
libgpg-error 1.56 and libgcrypt at 39aca53 with -fsanitize=address,
compiles a small C harness against the resulting libgcrypt.so, and calls
gcry_pk_decrypt() with a Curve25519 private key and the 1-byte ciphertext
above. AddressSanitizer reports:

==NNNN==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
WRITE of size 31 at 0x... thread T0
    #1 ... _gcry_ecc_mont_decodepoint .../cipher/ecc-misc.c:441
    #2 ... ecc_decrypt_raw .../cipher/ecc.c:1531
    #3 ... _gcry_pk_decrypt .../cipher/pubkey.c:416
    #4 ... gcry_pk_decrypt .../src/visibility.c:1038
0x... is located 0 bytes after 32-byte region [0x...,0x...)
allocated by thread T0 here:
    #4 ... _gcry_ecc_mont_decodepoint .../cipher/ecc-misc.c:432

A 32-byte ephemeral point (the baseline) decrypts cleanly with no error.

PROPOSED FIX

Correct the destination offset at cipher/ecc-misc.c:441:

if (rawmpilen < nbytes)
  • memset (rawmpi + nbytes - rawmpilen, 0, nbytes - rawmpilen);

+ memset (rawmpi + rawmpilen, 0, nbytes - rawmpilen);

Since _gcry_mpi_set_buffer at line 464 only consumes rawmpilen bytes,
an equally valid fix is to delete the memset entirely. We additionally
recommend rejecting rawmpilen < nbytes outright for Montgomery curves at
line 429 -- RFC 7748 X25519/X448 points are fixed-length by specification,
and a short encoding is semantically invalid regardless of the padding bug.

A patch file (fix_24.patch) is included in the attached artifacts.

DISCLOSURE

This vulnerability was discovered by Claude (Anthropic) and verified by
human review. We are following coordinated disclosure: we will not
publish details for 90 days from the date of this email (until
2026-07-06), or until you release a fix, whichever comes first. Please
let us know if you need more time.

Artifacts (PoC, patch, full report):
https://drive.google.com/file/d/1Qd1qW-H9GYfMziSfa33Y41NaZFnFTssP/view

Best regards,
Bronson Yen, Calif.io

on behalf of the Anthropic Coordinated Vulnerability Disclosure program