Libgcrypt ECDH buffer overwrite with zeroes
Closed, ResolvedPublic

Assigned To
Authored By
• werner
Apr 9 2026, 8:23 AM
Subscribers

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.Apr 9 2026, 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.Apr 10 2026, 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).Apr 13 2026, 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