Page MenuHome GnuPG

libgcrypt: EC least leak failure
Open, HighPublic

Description

When building the "notmuch" email indexer, the configure script tests that gmime can extract a session key, which it does using gcrypt. Since 1.12.0 this frequently, though not always, fails on i386 (32-bit).
This is not changed by applying the patch
https://lists.gnupg.org/pipermail/gcrypt-devel/2026-January/006025.html

The problem is no longer seen after neutering part of
https://git.gnupg.org/cgi-bin/gitweb.cgi?p=libgcrypt.git;a=commit;h=4f56fd8c5e03f389a9f27a5e9206b9dfb49c92e3

Index: mpi/ec.c
--- mpi/ec.c.orig
+++ mpi/ec.c
@@ -305,7 +305,7 @@ ec_mod (gcry_mpi_t w, mpi_ec_t ec)
   else
     _gcry_mpi_mod (w, w, ec->p);
 
-  if ((ec->flags & GCRYECC_FLAG_LEAST_LEAK))
+  if (0 && (ec->flags & GCRYECC_FLAG_LEAST_LEAK))
     w->nlimbs = ec->p->nlimbs;
 }

Event Timeline

werner added a subscriber: werner.

According to the ML @gniibe tried to replicate the problem without success.

Possibly, it is related to the NetBSD failure of T8065.
If importing the secret key fails (which invokes gpg-agent), decryption cannot be succeeded.
I will check OpenBSD implementation of POSIX semaphore, if it's similar to NetBSD one.

No, OpenBSD's implementation of POSIX semaphore is different to NetBSD.
(It doesn't support PSHARED=1.)

We have seen the same thing on amd64 (x86_64) linux: https://bugs.gentoo.org/969501

gniibe renamed this task from libgcrypt: EC least leak failure on 32-bit machine to libgcrypt: EC least leak failure.Fri, Feb 13, 8:10 AM

Please tell us the information of your environment.
What the versions of gpg and gpg-agent?

@gniibe

$ uname -a
Linux mop 6.18.10 #1 SMP PREEMPT_DYNAMIC Wed Feb 11 21:14:57 GMT 2026 x86_64 AMD Ryzen 9 3950X 16-Core Processor AuthenticAMD GNU/Linux

$ gpg --version
gpg (GnuPG) 2.5.17
libgcrypt 1.12.0-unknown
Copyright (C) 2025 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/sam/.gnupg
Supported algorithms:
Pubkey: RSA, Kyber, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

In the configure test from notmuch-0.39, I see (with added set -x):

C compiler supports thread sanitizer... Yes.
Reading libnotmuch version from source... OK.                                                                                                                                                                     Checking for Xapian development files (>= 1.4.0)... Yes (1.4.30).
+ GMIME_MINVER=3.0.3
+ printf 'Checking for GMime development files (>= 3.0.3)... '
Checking for GMime development files (>= 3.0.3)... + pkg-config --exists 'gmime-3.0 >= 3.0.3'
+ printf 'Yes.\n'
Yes.
+ have_gmime=1
++ pkg-config --cflags gmime-3.0
+ gmime_cflags='-I/usr/include/gmime-3.0 -D_LARGEFILE64_SOURCE -pthread -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -I/usr/lib64/libffi/include'
++ pkg-config --libs gmime-3.0
+ gmime_ldflags='-lgmime-3.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0'
+ printf 'Checking for GMime session key extraction support... '
Checking for GMime session key extraction support... + cat                                                                                                                                                        ++ mktemp -d /tmp/notmuch.XXXXXX
+ TEMP_GPG=/tmp/notmuch.UzG3mD
+ cc -g -O2 -I/usr/include/gmime-3.0 -D_LARGEFILE64_SOURCE -pthread -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -I/usr/lib64/libffi/include _check_session_
keys.c -lgmime-3.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -o _check_session_keys
+ GNUPGHOME=/tmp/notmuch.UzG3mD
+ gpg --batch --quiet --import
++ GNUPGHOME=/tmp/notmuch.UzG3mD
++ ./_check_session_keys
decryption failed
+ SESSION_KEY=
+ cat
No.
*** Error: Could not extract session keys from encrypted message.

This is likely due to your GMime having been built against a old
version of GPGME.

Please try to rebuild your version of GMime against a more recent
version of GPGME (at least GPGME 1.8.0).

I can reproduce it using Stuart's script from https://lists.gnupg.org/pipermail/gcrypt-devel/2026-February/006031.html.

For convenience, I've included it below so we are on the same page with debugging added:

#!/bin/sh
set -e

tmp=$(mktemp -d /tmp/notmuchtest.XXXXXXXXX)
cd $tmp

cat << EOF > _check_session_keys.c
#include <gmime/gmime.h>
#include <stdio.h>

int main () {
    GError *error = NULL;
    GMimeParser *parser = NULL;
    GMimeMultipartEncrypted *body = NULL;
    GMimeDecryptResult *decrypt_result = NULL;
    GMimeObject *output = NULL;

    g_mime_init ();
    parser = g_mime_parser_new ();
    g_mime_parser_init_with_stream (parser, g_mime_stream_file_open("basic-encrypted.eml", "r", &error));
    if (error) return !! fprintf (stderr, "failed to instantiate parser with basic-encrypted.eml\n");

    body = GMIME_MULTIPART_ENCRYPTED(g_mime_message_get_mime_part (g_mime_parser_construct_message (parser, NULL)));
    if (body == NULL) return !! fprintf (stderr, "did not find a multipart encrypted message\n");

    output = g_mime_multipart_encrypted_decrypt (body, GMIME_DECRYPT_EXPORT_SESSION_KEY, NULL, &decrypt_result, &error);
    if (error || output == NULL) return !! fprintf (stderr, "decryption failed\n");

    if (decrypt_result == NULL) return !! fprintf (stderr, "no GMimeDecryptResult found\n");
    if (decrypt_result->session_key == NULL) return !! fprintf (stderr, "GMimeDecryptResult has no session key\n");

    printf ("%s\n", decrypt_result->session_key);
    return 0;
}
EOF

cat << EOF > openpgp4-secret-key.asc
-----BEGIN PGP PRIVATE KEY BLOCK-----

lFgEYxhtlxYJKwYBBAHaRw8BAQdA0PoNKr90DaQV1dIK77wbWm4RT+JQzqBkwIjA
HQM9RHYAAQDQ5wSfkOGXvKYroALWgibztISzXS5b8boGXykcHERo6w/ctDtOb3Rt
dWNoIFRlc3QgU3VpdGUgKElOU0VDVVJFISkgPHRlc3Rfc3VpdGVAbm90bXVjaG1h
aWwub3JnPoiQBBMWCAA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEEmjr+
bGAGWhSP1LWKfmq+kkZFzGAFAmMYbZwACgkQfmq+kkZFzGDtrwEAjQRn3xhEomah
wICjQjfi4RKNbvnRViZgosijDBANUAgA/28GrK1tPnQsXWqmuZxQ1Cd5ry4NAnj/
4jsxD3cTbnEHnF0EYxhtlxIKKwYBBAGXVQEFAQEHQEOd3EyCD5qo4+QuHz0lruCG
VM6n6RI4dtAh3cX9uHwiAwEIBwAA/1oe+p5jNjNE5lEj4yTpYjCxCeC98MolbiAy
0yY7526wECqIeAQYFggAIBYhBJo6/mxgBloUj9S1in5qvpJGRcxgBQJjGG2XAhsM
AAoJEH5qvpJGRcxgBdsA/R9ZECoxai5QhOitDIAUZVCRr59Pm1VMPiJOOIla2N1p
AQCNESwJ9IJOdO/06q+bR2GG4WyEkB4VoVBiA3hFx/zZAA==
=uGTo
-----END PGP PRIVATE KEY BLOCK-----
EOF

cat << EOF > basic-encrypted.eml
From: test_suite at notmuchmail.org
To: test_suite at notmuchmail.org
Subject: Here is the password
Date: Sat, 01 Jan 2000 12:00:00 +0000
Message-ID: <basic-encrypted at crypto.notmuchmail.org>
MIME-Version: 1.0
Content-Type: multipart/encrypted; boundary="=-=-=";
        protocol="application/pgp-encrypted"

--=-=-=
Content-Type: application/pgp-encrypted

Version: 1

--=-=-=
Content-Type: application/octet-stream

-----BEGIN PGP MESSAGE-----

hF4DHXHP849rSK8SAQdAYbv9NFaU2Fbd6JbfE87h/yZNyWLJYZ2EseU0WyOz7Agw
/+KTbbIqRcEYhnpQhQXBQ2wqIN5gmdRhaqrj5q0VLV2BOKNJKqXGs/W4DghXwfAu
0oMBqjTd/mMbF0nJLw3bPX+LW47RHQdZ8vUVPlPr0ALg8kqgcfy95Qqy5h796Uyq
xs+I/UUOt7fzTDAw0B4qkRbdSangwYy80N4X43KrAfKSstBH3/7O4285XZr86YhF
rEtsBuwhoXI+DaG3uYZBBMTkzfButmBKHwB2CmWutmVpQL087A==
=lhSz
-----END PGP MESSAGE-----
--=-=-=--
EOF

cc $(pkg-config --cflags gmime-3.0) _check_session_keys.c \
   $(pkg-config --libs gmime-3.0) -o _check_session_keys

export GNUPGHOME=$tmp
cat <<-EOF > ${GNUPGHOME}/gpg-agent.conf
log-file /tmp/notmuch-bug.log
#debug-level guru
EOF
cat <<-EOF > ${GNUPGHOME}/gpg.conf
log-file /tmp/notmuch-bug.log
#debug-level guru
EOF
gpg --debug-all --batch --quiet --import < openpgp4-secret-key.asc
echo "cd $tmp; GNUPGHOME=$tmp ./_check_session_keys"
./_check_session_keys

If I uncomment debug-level guru for gpg-agent.conf, it starts to reliably work?!

I've attached notmuch-bug.log with debug-level guru commented out for gpg-agent:

.

This log was when there were a bunch of stale gpg-agents left running and it hit some fd limit I guess. When I killed those, it worked again, until I disabled logging.

It seems so sensitive to any changes though that I'm not sure it is very useful.

@thesamesam Thanks a lot.
I managed to replicate the failure somehow (for me, it fails at the importing the key).

I'm testing with:

gniibe raised the priority of this task from Low to High.Fri, Feb 13, 9:32 AM

Any hints where to find the actual crypto code which uses libgcrypt?

Maintainer of the FreeBSD notmuch port/package here. The steps below consistently trigger the problem on FreeBSD 16.0 (unreleased main branch), but there are no problems on FreeBSD 15.0. All my testing was on amd64.

FreeBSD 15.0

% uname -a
FreeBSD 15amd64-default 15.0-RELEASE FreeBSD 15.0-RELEASE amd64

$ gpg --version
gpg (GnuPG) 2.4.9
libgcrypt 1.12.0
Copyright (C) 2025 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /root/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

% TEMP_GPG=$(mktemp -d)

% echo $TEMP_GPG
/tmp/tmp.PEU3wdv2Bt

% GNUPGHOME=$TEMP_GPG gpg --batch --import < ./test/openpgp4-secret-key.asc
gpg: keybox '/tmp/tmp.PEU3wdv2Bt/pubring.kbx' created
gpg: /tmp/tmp.PEU3wdv2Bt/trustdb.gpg: trustdb created
gpg: key 7E6ABE924645CC60: public key "Notmuch Test Suite (INSECURE!) <test_suite@notmuchmail.org>" imported
gpg: key 7E6ABE924645CC60: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

% GNUPGHOME=$TEMP_GPG gpgconf --list-dirs
sysconfdir:/usr/local/etc/gnupg
bindir:/usr/local/bin
libexecdir:/usr/local/libexec
libdir:/usr/local/lib/gnupg
datadir:/usr/local/share/gnupg
localedir:/usr/local/share/locale
socketdir:/tmp/tmp.PEU3wdv2Bt
dirmngr-socket:/tmp/tmp.PEU3wdv2Bt/S.dirmngr
keyboxd-socket:/tmp/tmp.PEU3wdv2Bt/S.keyboxd
agent-ssh-socket:/tmp/tmp.PEU3wdv2Bt/S.gpg-agent.ssh
agent-extra-socket:/tmp/tmp.PEU3wdv2Bt/S.gpg-agent.extra
agent-browser-socket:/tmp/tmp.PEU3wdv2Bt/S.gpg-agent.browser
agent-socket:/tmp/tmp.PEU3wdv2Bt/S.gpg-agent
homedir:/tmp/tmp.PEU3wdv2Bt

% ls -l $TEMP_GPG/private-keys-v1.d
total 9
-rw-------  1 nobody wheel 445 Feb 12 17:15 6834B116B69ED3E621D7DFE71A1C99D640F79A2F.key
-rw-------  1 nobody wheel 436 Feb 12 17:15 96DAC0CE918E52E8318204FA889B1BDFE262B7D4.key

% GNUPGHOME=$TEMP_GPG gpg --decrypt ./test/corpora/crypto/basic-encrypted.eml
gpg: encrypted with cv25519 key, ID 1D71CFF38F6B48AF, created 2022-09-07
      "Notmuch Test Suite (INSECURE!) <test_suite@notmuchmail.org>"
Content-Type: text/plain

The password is "abcd1234!", please do not tell anyone.

FreeBSD 16

% uname -a
FreeBSD asn.ftfl.ca 16.0-CURRENT FreeBSD 16.0-CURRENT main-n283746-d255b40e2dcc GENERIC amd64

% gpg --version
gpg (GnuPG) 2.4.9
libgcrypt 1.12.0
Copyright (C) 2025 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/jrm/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

% TEMP_GPG=$(mktemp -d)

% echo $TEMP_GPG
/tmp/tmp.eCDFy7X48V

% GNUPGHOME=$TEMP_GPG gpg --batch --import < $HOME/scm/nm/notmuch.git/test/openpgp4-secret-key.asc
gpg: keybox '/tmp/tmp.eCDFy7X48V/pubring.kbx' created
gpg: /tmp/tmp.eCDFy7X48V/trustdb.gpg: trustdb created
gpg: key 7E6ABE924645CC60: public key "Notmuch Test Suite (INSECURE!) <test_suite@notmuchmail.org>" imported
gpg: key 7E6ABE924645CC60: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

% GNUPGHOME=$TEMP_GPG gpgconf --list-dirs
sysconfdir:/usr/local/etc/gnupg
bindir:/usr/local/bin
libexecdir:/usr/local/libexec
libdir:/usr/local/lib/gnupg
datadir:/usr/local/share/gnupg
localedir:/usr/local/share/locale
socketdir:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux
dirmngr-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.dirmngr
keyboxd-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.keyboxd
agent-ssh-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.gpg-agent.ssh
agent-extra-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.gpg-agent.extra
agent-browser-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.gpg-agent.browser
agent-socket:/var/run/user/1001/gnupg/d.yguniof64cdiw3ijtz5ayhux/S.gpg-agent
homedir:/tmp/tmp.eCDFy7X48V

% ls -l $TEMP_GPG/private-keys-v1.d
total 9
-rw-------  1 jrm wheel 445 Feb 12 13:05 6834B116B69ED3E621D7DFE71A1C99D640F79A2F.key
-rw-------  1 jrm wheel 436 Feb 12 13:05 96DAC0CE918E52E8318204FA889B1BDFE262B7D4.key

% GNUPGHOME=$TEMP_GPG gpg --decrypt $HOME/scm/nm/notmuch.git/test/corpora/crypto/basic-encrypted.eml
gpg: encrypted with cv25519 key, ID 1D71CFF38F6B48AF, created 2022-09-07
      "Notmuch Test Suite (INSECURE!) <test_suite@notmuchmail.org>"
gpg: public key decryption failed: Bad secret key
gpg: decryption failed: Bad secret key

Agent Debug Log

2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey info: Montgomery/Standard
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey name: Curve25519
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    p:+7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    a:+01db41
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    b:+01
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey  g.X:+09
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey  g.Y:+5f51e65e475f794b1fe122d388b72eb36dc2b28192839e4dd6163a5d81312c14
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey  g.Z:+01
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    n:+1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    h:+08
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    q: [264 bit]
2026-02-13 17:09:08 gpg-agent[15945] DBG:                   40439ddc4c820f9aa8e3e42e1f3d25aee08654cea7e9123876d021ddc5fdb87c \
2026-02-13 17:09:08 gpg-agent[15945] DBG:                   22
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    d:+5a1efa9e63363344e65123e324e96230b109e0bdf0ca256e2032d3263be76eb0
2026-02-13 17:09:08 gpg-agent[15945] DBG: Bad check: Point 'G' does not belong to curve 'E'!
2026-02-13 17:09:08 gpg-agent[15945] DBG: ecc_testkey    => Bad secret key
2026-02-13 17:09:08 gpg-agent[15945] failed to convert unprotected openpgp key: Bad secret key

Before finding this report, I investigated a naive local patch that worked around the problem by disabling the GCRYECC_FLAG_LEAST_LEAK flag for key validation.

--- cipher/ecc.c.orig	2025-09-23 13:14:22 UTC
+++ cipher/ecc.c
@@ -894,7 +894,7 @@ ecc_check_secret_key (gcry_sexp_t keyparms)
 ecc_check_secret_key (gcry_sexp_t keyparms)
 {
   gcry_err_code_t rc;
-  int flags = GCRYECC_FLAG_LEAST_LEAK;
+  int flags = 0;
   mpi_ec_t ec = NULL;
 
   /*

Applying @gniibe's patch against 1.12.0 fixed the problem. The patch applies cleanly except for one small hunk in mpi/ec.c:

% cat ./mpi/ec.c.rej
@@ -1231,7 +1229,7 @@
             mpi_set (y, point->y);
             mpi_resize (y, ctx->p->nlimbs);
             y->nlimbs = ctx->p->nlimbs;
-            z3 = mpi_new (0);
+            z3 = mpi_new (ctx->nbits);
             ec_mulm_lli (z3, z2, z1, ctx); /* z3 = z^(-3) mod p  */
             ec_mulm_lli (y, y, z3, ctx);
             mpi_free (z3);

FWIW: Okay, gmime is still a wrapper around gpgme. After decryption it has the ability to get the used session key from the gpgme result structure. Thus, I have been on the wrong trail. The actual problem is not gpgme but more GnuPG's use of Libgcrypt or an actual regression in Libgcrypt. Well, Friday 13th.